Skip to main content

gitkraft_core/features/theme/
presets.rs

1//! All 27 preset themes — the **single source of truth** for every colour
2//! used by both the GUI and TUI front-ends.
3//!
4//! Each public function returns a fully-populated [`AppTheme`] with concrete
5//! RGB values. The [`theme_by_index`] dispatcher maps a `0..=26` index to the
6//! matching constructor; out-of-range indices silently fall back to `default()`.
7
8use super::types::{AppTheme, Rgb};
9
10// ── Theme catalogue ───────────────────────────────────────────────────────────
11
12/// Ordered theme names. The position in this slice **is** the canonical index.
13pub const THEME_NAMES: &[&str] = &[
14    "Default",
15    "Grape",
16    "Ocean",
17    "Sunset",
18    "Forest",
19    "Rose",
20    "Mono",
21    "Neon",
22    "Dracula",
23    "Nord",
24    "Solarized Dark",
25    "Solarized Light",
26    "Gruvbox Dark",
27    "Gruvbox Light",
28    "Catppuccin Latte",
29    "Catppuccin Frappé",
30    "Catppuccin Macchiato",
31    "Catppuccin Mocha",
32    "Tokyo Night",
33    "Tokyo Night Storm",
34    "Tokyo Night Light",
35    "Kanagawa Wave",
36    "Kanagawa Dragon",
37    "Kanagawa Lotus",
38    "Moonfly",
39    "Nightfly",
40    "Oxocarbon",
41    "Cyberpunk",
42];
43
44/// Total number of themes.
45pub const THEME_COUNT: usize = 28;
46
47/// Get a theme by index (0-based). Returns `default()` for out-of-range.
48pub fn theme_by_index(index: usize) -> AppTheme {
49    match index {
50        0 => default(),
51        1 => grape(),
52        2 => ocean(),
53        3 => sunset(),
54        4 => forest(),
55        5 => rose(),
56        6 => mono(),
57        7 => neon(),
58        8 => dracula(),
59        9 => nord(),
60        10 => solarized_dark(),
61        11 => solarized_light(),
62        12 => gruvbox_dark(),
63        13 => gruvbox_light(),
64        14 => catppuccin_latte(),
65        15 => catppuccin_frappe(),
66        16 => catppuccin_macchiato(),
67        17 => catppuccin_mocha(),
68        18 => tokyo_night(),
69        19 => tokyo_night_storm(),
70        20 => tokyo_night_light(),
71        21 => kanagawa_wave(),
72        22 => kanagawa_dragon(),
73        23 => kanagawa_lotus(),
74        24 => moonfly(),
75        25 => nightfly(),
76        26 => oxocarbon(),
77        27 => cyberpunk(),
78        _ => default(),
79    }
80}
81
82/// Find theme index by name (case-insensitive ASCII). Returns `0` (Default) if
83/// no match is found.
84pub fn theme_index_by_name(name: &str) -> usize {
85    THEME_NAMES
86        .iter()
87        .position(|n| n.eq_ignore_ascii_case(name))
88        .unwrap_or(0)
89}
90
91// ── Theme definitions ─────────────────────────────────────────────────────────
92
93pub fn default() -> AppTheme {
94    AppTheme {
95        is_dark: true,
96        background: Rgb::new(13, 17, 23),
97        surface: Rgb::new(22, 27, 34),
98        border: Rgb::new(48, 54, 61),
99        selection: Rgb::new(40, 60, 80),
100        text_primary: Rgb::new(230, 237, 243),
101        text_secondary: Rgb::new(139, 148, 158),
102        text_muted: Rgb::new(72, 79, 88),
103        accent: Rgb::new(88, 166, 255),
104        success: Rgb::new(63, 185, 80),
105        warning: Rgb::new(210, 154, 34),
106        error: Rgb::new(248, 83, 73),
107        diff_add: Rgb::new(63, 185, 80),
108        diff_del: Rgb::new(248, 83, 73),
109        diff_context: Rgb::new(139, 148, 158),
110        diff_hunk: Rgb::new(88, 166, 255),
111        graph_colors: [
112            Rgb::new(88, 166, 255),
113            Rgb::new(63, 185, 80),
114            Rgb::new(248, 83, 73),
115            Rgb::new(210, 154, 34),
116            Rgb::new(188, 140, 255),
117            Rgb::new(0, 200, 180),
118            Rgb::new(255, 140, 60),
119            Rgb::new(255, 130, 160),
120        ],
121    }
122}
123
124pub fn grape() -> AppTheme {
125    AppTheme {
126        is_dark: true,
127        background: Rgb::new(24, 18, 36),
128        surface: Rgb::new(35, 28, 50),
129        border: Rgb::new(60, 50, 80),
130        selection: Rgb::new(50, 40, 70),
131        text_primary: Rgb::new(220, 210, 240),
132        text_secondary: Rgb::new(160, 140, 200),
133        text_muted: Rgb::new(100, 85, 130),
134        accent: Rgb::new(180, 130, 255),
135        success: Rgb::new(130, 230, 160),
136        warning: Rgb::new(240, 200, 100),
137        error: Rgb::new(255, 100, 120),
138        diff_add: Rgb::new(130, 230, 160),
139        diff_del: Rgb::new(255, 100, 120),
140        diff_context: Rgb::new(100, 85, 130),
141        diff_hunk: Rgb::new(150, 160, 255),
142        graph_colors: [
143            Rgb::new(180, 130, 255),
144            Rgb::new(130, 230, 160),
145            Rgb::new(255, 100, 120),
146            Rgb::new(240, 200, 100),
147            Rgb::new(100, 180, 255),
148            Rgb::new(255, 160, 200),
149            Rgb::new(120, 220, 220),
150            Rgb::new(255, 170, 100),
151        ],
152    }
153}
154
155pub fn ocean() -> AppTheme {
156    AppTheme {
157        is_dark: true,
158        background: Rgb::new(15, 25, 35),
159        surface: Rgb::new(22, 35, 48),
160        border: Rgb::new(40, 60, 80),
161        selection: Rgb::new(30, 55, 75),
162        text_primary: Rgb::new(200, 220, 240),
163        text_secondary: Rgb::new(100, 170, 200),
164        text_muted: Rgb::new(70, 100, 130),
165        accent: Rgb::new(80, 200, 180),
166        success: Rgb::new(80, 220, 120),
167        warning: Rgb::new(220, 200, 80),
168        error: Rgb::new(255, 100, 100),
169        diff_add: Rgb::new(80, 220, 120),
170        diff_del: Rgb::new(255, 100, 100),
171        diff_context: Rgb::new(70, 100, 130),
172        diff_hunk: Rgb::new(80, 200, 180),
173        graph_colors: [
174            Rgb::new(80, 200, 180),
175            Rgb::new(80, 220, 120),
176            Rgb::new(255, 100, 100),
177            Rgb::new(220, 200, 80),
178            Rgb::new(100, 160, 255),
179            Rgb::new(200, 130, 255),
180            Rgb::new(255, 180, 80),
181            Rgb::new(255, 130, 180),
182        ],
183    }
184}
185
186pub fn sunset() -> AppTheme {
187    AppTheme {
188        is_dark: true,
189        background: Rgb::new(35, 18, 18),
190        surface: Rgb::new(50, 28, 28),
191        border: Rgb::new(80, 45, 45),
192        selection: Rgb::new(70, 35, 35),
193        text_primary: Rgb::new(240, 220, 210),
194        text_secondary: Rgb::new(200, 150, 120),
195        text_muted: Rgb::new(140, 100, 80),
196        accent: Rgb::new(255, 140, 60),
197        success: Rgb::new(180, 220, 80),
198        warning: Rgb::new(255, 200, 50),
199        error: Rgb::new(255, 80, 80),
200        diff_add: Rgb::new(180, 220, 80),
201        diff_del: Rgb::new(255, 80, 80),
202        diff_context: Rgb::new(140, 100, 80),
203        diff_hunk: Rgb::new(255, 180, 100),
204        graph_colors: [
205            Rgb::new(255, 140, 60),
206            Rgb::new(180, 220, 80),
207            Rgb::new(255, 80, 80),
208            Rgb::new(255, 200, 50),
209            Rgb::new(200, 120, 255),
210            Rgb::new(80, 200, 200),
211            Rgb::new(255, 130, 160),
212            Rgb::new(100, 180, 255),
213        ],
214    }
215}
216
217pub fn forest() -> AppTheme {
218    AppTheme {
219        is_dark: true,
220        background: Rgb::new(18, 28, 18),
221        surface: Rgb::new(28, 40, 28),
222        border: Rgb::new(50, 70, 50),
223        selection: Rgb::new(35, 55, 35),
224        text_primary: Rgb::new(210, 230, 210),
225        text_secondary: Rgb::new(140, 180, 140),
226        text_muted: Rgb::new(80, 110, 80),
227        accent: Rgb::new(80, 180, 80),
228        success: Rgb::new(100, 220, 100),
229        warning: Rgb::new(220, 200, 80),
230        error: Rgb::new(220, 90, 70),
231        diff_add: Rgb::new(100, 220, 100),
232        diff_del: Rgb::new(220, 90, 70),
233        diff_context: Rgb::new(80, 110, 80),
234        diff_hunk: Rgb::new(120, 180, 120),
235        graph_colors: [
236            Rgb::new(80, 180, 80),
237            Rgb::new(100, 220, 100),
238            Rgb::new(220, 90, 70),
239            Rgb::new(220, 200, 80),
240            Rgb::new(100, 170, 220),
241            Rgb::new(180, 130, 200),
242            Rgb::new(200, 180, 100),
243            Rgb::new(140, 210, 200),
244        ],
245    }
246}
247
248pub fn rose() -> AppTheme {
249    AppTheme {
250        is_dark: true,
251        background: Rgb::new(30, 18, 25),
252        surface: Rgb::new(45, 28, 38),
253        border: Rgb::new(75, 45, 60),
254        selection: Rgb::new(60, 35, 50),
255        text_primary: Rgb::new(240, 220, 230),
256        text_secondary: Rgb::new(200, 140, 170),
257        text_muted: Rgb::new(130, 90, 110),
258        accent: Rgb::new(255, 130, 160),
259        success: Rgb::new(130, 220, 150),
260        warning: Rgb::new(240, 200, 100),
261        error: Rgb::new(255, 80, 100),
262        diff_add: Rgb::new(130, 220, 150),
263        diff_del: Rgb::new(255, 80, 100),
264        diff_context: Rgb::new(130, 90, 110),
265        diff_hunk: Rgb::new(220, 150, 200),
266        graph_colors: [
267            Rgb::new(255, 130, 160),
268            Rgb::new(130, 220, 150),
269            Rgb::new(255, 80, 100),
270            Rgb::new(240, 200, 100),
271            Rgb::new(130, 170, 255),
272            Rgb::new(200, 140, 255),
273            Rgb::new(80, 210, 210),
274            Rgb::new(255, 170, 100),
275        ],
276    }
277}
278
279pub fn mono() -> AppTheme {
280    AppTheme {
281        is_dark: true,
282        background: Rgb::new(20, 20, 20),
283        surface: Rgb::new(35, 35, 35),
284        border: Rgb::new(60, 60, 60),
285        selection: Rgb::new(50, 50, 50),
286        text_primary: Rgb::new(220, 220, 220),
287        text_secondary: Rgb::new(160, 160, 160),
288        text_muted: Rgb::new(100, 100, 100),
289        accent: Rgb::new(180, 180, 180),
290        success: Rgb::new(180, 220, 180),
291        warning: Rgb::new(220, 200, 160),
292        error: Rgb::new(220, 140, 140),
293        diff_add: Rgb::new(180, 220, 180),
294        diff_del: Rgb::new(220, 140, 140),
295        diff_context: Rgb::new(100, 100, 100),
296        diff_hunk: Rgb::new(180, 180, 180),
297        graph_colors: [
298            Rgb::new(180, 180, 180),
299            Rgb::new(180, 220, 180),
300            Rgb::new(220, 140, 140),
301            Rgb::new(220, 200, 160),
302            Rgb::new(140, 180, 220),
303            Rgb::new(200, 160, 200),
304            Rgb::new(200, 200, 140),
305            Rgb::new(160, 210, 210),
306        ],
307    }
308}
309
310pub fn neon() -> AppTheme {
311    AppTheme {
312        is_dark: true,
313        background: Rgb::new(10, 10, 18),
314        surface: Rgb::new(18, 18, 30),
315        border: Rgb::new(30, 30, 60),
316        selection: Rgb::new(25, 25, 50),
317        text_primary: Rgb::new(220, 240, 255),
318        text_secondary: Rgb::new(0, 200, 255),
319        text_muted: Rgb::new(60, 80, 120),
320        accent: Rgb::new(0, 255, 200),
321        success: Rgb::new(0, 255, 100),
322        warning: Rgb::new(255, 255, 0),
323        error: Rgb::new(255, 0, 80),
324        diff_add: Rgb::new(0, 255, 100),
325        diff_del: Rgb::new(255, 0, 80),
326        diff_context: Rgb::new(60, 80, 120),
327        diff_hunk: Rgb::new(200, 0, 255),
328        graph_colors: [
329            Rgb::new(0, 255, 200),
330            Rgb::new(0, 255, 100),
331            Rgb::new(255, 0, 80),
332            Rgb::new(255, 255, 0),
333            Rgb::new(0, 200, 255),
334            Rgb::new(200, 0, 255),
335            Rgb::new(255, 100, 0),
336            Rgb::new(255, 0, 200),
337        ],
338    }
339}
340
341pub fn dracula() -> AppTheme {
342    AppTheme {
343        is_dark: true,
344        background: Rgb::new(40, 42, 54),
345        surface: Rgb::new(68, 71, 90),
346        border: Rgb::new(98, 114, 164),
347        selection: Rgb::new(68, 71, 90),
348        text_primary: Rgb::new(248, 248, 242),
349        text_secondary: Rgb::new(189, 147, 249),
350        text_muted: Rgb::new(98, 114, 164),
351        accent: Rgb::new(189, 147, 249),
352        success: Rgb::new(80, 250, 123),
353        warning: Rgb::new(241, 250, 140),
354        error: Rgb::new(255, 85, 85),
355        diff_add: Rgb::new(80, 250, 123),
356        diff_del: Rgb::new(255, 85, 85),
357        diff_context: Rgb::new(98, 114, 164),
358        diff_hunk: Rgb::new(139, 233, 253),
359        graph_colors: [
360            Rgb::new(189, 147, 249),
361            Rgb::new(80, 250, 123),
362            Rgb::new(255, 85, 85),
363            Rgb::new(241, 250, 140),
364            Rgb::new(139, 233, 253),
365            Rgb::new(255, 121, 198),
366            Rgb::new(255, 184, 108),
367            Rgb::new(98, 114, 164),
368        ],
369    }
370}
371
372pub fn nord() -> AppTheme {
373    AppTheme {
374        is_dark: true,
375        background: Rgb::new(46, 52, 64),
376        surface: Rgb::new(59, 66, 82),
377        border: Rgb::new(76, 86, 106),
378        selection: Rgb::new(59, 66, 82),
379        text_primary: Rgb::new(216, 222, 233),
380        text_secondary: Rgb::new(129, 161, 193),
381        text_muted: Rgb::new(76, 86, 106),
382        accent: Rgb::new(136, 192, 208),
383        success: Rgb::new(163, 190, 140),
384        warning: Rgb::new(235, 203, 139),
385        error: Rgb::new(191, 97, 106),
386        diff_add: Rgb::new(163, 190, 140),
387        diff_del: Rgb::new(191, 97, 106),
388        diff_context: Rgb::new(76, 86, 106),
389        diff_hunk: Rgb::new(129, 161, 193),
390        graph_colors: [
391            Rgb::new(136, 192, 208),
392            Rgb::new(163, 190, 140),
393            Rgb::new(191, 97, 106),
394            Rgb::new(235, 203, 139),
395            Rgb::new(129, 161, 193),
396            Rgb::new(180, 142, 173),
397            Rgb::new(208, 135, 112),
398            Rgb::new(143, 188, 187),
399        ],
400    }
401}
402
403pub fn solarized_dark() -> AppTheme {
404    AppTheme {
405        is_dark: true,
406        background: Rgb::new(0, 43, 54),
407        surface: Rgb::new(7, 54, 66),
408        border: Rgb::new(88, 110, 117),
409        selection: Rgb::new(7, 54, 66),
410        text_primary: Rgb::new(131, 148, 150),
411        text_secondary: Rgb::new(42, 161, 152),
412        text_muted: Rgb::new(88, 110, 117),
413        accent: Rgb::new(38, 139, 210),
414        success: Rgb::new(133, 153, 0),
415        warning: Rgb::new(181, 137, 0),
416        error: Rgb::new(220, 50, 47),
417        diff_add: Rgb::new(133, 153, 0),
418        diff_del: Rgb::new(220, 50, 47),
419        diff_context: Rgb::new(88, 110, 117),
420        diff_hunk: Rgb::new(42, 161, 152),
421        graph_colors: [
422            Rgb::new(38, 139, 210),
423            Rgb::new(133, 153, 0),
424            Rgb::new(220, 50, 47),
425            Rgb::new(181, 137, 0),
426            Rgb::new(42, 161, 152),
427            Rgb::new(108, 113, 196),
428            Rgb::new(203, 75, 22),
429            Rgb::new(211, 54, 130),
430        ],
431    }
432}
433
434pub fn solarized_light() -> AppTheme {
435    AppTheme {
436        is_dark: false,
437        background: Rgb::new(253, 246, 227),
438        surface: Rgb::new(238, 232, 213),
439        border: Rgb::new(147, 161, 161),
440        selection: Rgb::new(238, 232, 213),
441        text_primary: Rgb::new(101, 123, 131),
442        text_secondary: Rgb::new(42, 161, 152),
443        text_muted: Rgb::new(147, 161, 161),
444        accent: Rgb::new(38, 139, 210),
445        success: Rgb::new(133, 153, 0),
446        warning: Rgb::new(181, 137, 0),
447        error: Rgb::new(220, 50, 47),
448        diff_add: Rgb::new(133, 153, 0),
449        diff_del: Rgb::new(220, 50, 47),
450        diff_context: Rgb::new(147, 161, 161),
451        diff_hunk: Rgb::new(42, 161, 152),
452        graph_colors: [
453            Rgb::new(38, 139, 210),
454            Rgb::new(133, 153, 0),
455            Rgb::new(220, 50, 47),
456            Rgb::new(181, 137, 0),
457            Rgb::new(42, 161, 152),
458            Rgb::new(108, 113, 196),
459            Rgb::new(203, 75, 22),
460            Rgb::new(211, 54, 130),
461        ],
462    }
463}
464
465pub fn gruvbox_dark() -> AppTheme {
466    AppTheme {
467        is_dark: true,
468        background: Rgb::new(40, 40, 40),
469        surface: Rgb::new(60, 56, 54),
470        border: Rgb::new(80, 73, 69),
471        selection: Rgb::new(60, 56, 54),
472        text_primary: Rgb::new(235, 219, 178),
473        text_secondary: Rgb::new(250, 189, 47),
474        text_muted: Rgb::new(146, 131, 116),
475        accent: Rgb::new(254, 128, 25),
476        success: Rgb::new(184, 187, 38),
477        warning: Rgb::new(250, 189, 47),
478        error: Rgb::new(251, 73, 52),
479        diff_add: Rgb::new(184, 187, 38),
480        diff_del: Rgb::new(251, 73, 52),
481        diff_context: Rgb::new(146, 131, 116),
482        diff_hunk: Rgb::new(142, 192, 124),
483        graph_colors: [
484            Rgb::new(254, 128, 25),
485            Rgb::new(184, 187, 38),
486            Rgb::new(251, 73, 52),
487            Rgb::new(250, 189, 47),
488            Rgb::new(131, 165, 152),
489            Rgb::new(211, 134, 155),
490            Rgb::new(142, 192, 124),
491            Rgb::new(69, 133, 136),
492        ],
493    }
494}
495
496pub fn gruvbox_light() -> AppTheme {
497    AppTheme {
498        is_dark: false,
499        background: Rgb::new(251, 241, 199),
500        surface: Rgb::new(213, 196, 161),
501        border: Rgb::new(168, 153, 132),
502        selection: Rgb::new(213, 196, 161),
503        text_primary: Rgb::new(60, 56, 54),
504        text_secondary: Rgb::new(215, 153, 33),
505        text_muted: Rgb::new(146, 131, 116),
506        accent: Rgb::new(214, 93, 14),
507        success: Rgb::new(121, 116, 14),
508        warning: Rgb::new(215, 153, 33),
509        error: Rgb::new(157, 0, 6),
510        diff_add: Rgb::new(121, 116, 14),
511        diff_del: Rgb::new(157, 0, 6),
512        diff_context: Rgb::new(146, 131, 116),
513        diff_hunk: Rgb::new(104, 157, 106),
514        graph_colors: [
515            Rgb::new(214, 93, 14),
516            Rgb::new(121, 116, 14),
517            Rgb::new(157, 0, 6),
518            Rgb::new(215, 153, 33),
519            Rgb::new(69, 133, 136),
520            Rgb::new(177, 98, 134),
521            Rgb::new(104, 157, 106),
522            Rgb::new(7, 102, 120),
523        ],
524    }
525}
526
527pub fn catppuccin_latte() -> AppTheme {
528    AppTheme {
529        is_dark: false,
530        // Base
531        background: Rgb::new(239, 241, 245),
532        // Surface 0
533        surface: Rgb::new(204, 208, 218),
534        // Surface 2
535        border: Rgb::new(172, 176, 190),
536        // Surface 1
537        selection: Rgb::new(188, 192, 204),
538        // Text
539        text_primary: Rgb::new(76, 79, 105),
540        // Subtext 1
541        text_secondary: Rgb::new(92, 95, 119),
542        // Overlay 0
543        text_muted: Rgb::new(156, 160, 176),
544        // Mauve
545        accent: Rgb::new(136, 57, 239),
546        // Green
547        success: Rgb::new(64, 160, 43),
548        // Yellow
549        warning: Rgb::new(223, 142, 29),
550        // Red
551        error: Rgb::new(210, 15, 57),
552        diff_add: Rgb::new(64, 160, 43),
553        diff_del: Rgb::new(210, 15, 57),
554        // Overlay 0
555        diff_context: Rgb::new(156, 160, 176),
556        // Teal
557        diff_hunk: Rgb::new(23, 146, 153),
558        graph_colors: [
559            Rgb::new(136, 57, 239), // Mauve
560            Rgb::new(64, 160, 43),  // Green
561            Rgb::new(210, 15, 57),  // Red
562            Rgb::new(223, 142, 29), // Yellow
563            Rgb::new(23, 146, 153), // Teal
564            Rgb::new(30, 102, 245), // Blue
565            Rgb::new(230, 69, 83),  // Maroon
566            Rgb::new(32, 159, 181), // Sapphire
567        ],
568    }
569}
570
571pub fn catppuccin_frappe() -> AppTheme {
572    AppTheme {
573        is_dark: true,
574        // Base
575        background: Rgb::new(48, 52, 70),
576        // Surface 0
577        surface: Rgb::new(65, 69, 89),
578        // Surface 2
579        border: Rgb::new(98, 104, 128),
580        // Surface 1
581        selection: Rgb::new(81, 87, 109),
582        // Text
583        text_primary: Rgb::new(198, 208, 245),
584        // Subtext 1
585        text_secondary: Rgb::new(181, 191, 226),
586        // Overlay 0
587        text_muted: Rgb::new(115, 121, 148),
588        // Mauve
589        accent: Rgb::new(202, 158, 230),
590        // Green
591        success: Rgb::new(166, 209, 137),
592        // Yellow
593        warning: Rgb::new(229, 200, 144),
594        // Red
595        error: Rgb::new(231, 130, 132),
596        diff_add: Rgb::new(166, 209, 137),
597        diff_del: Rgb::new(231, 130, 132),
598        // Overlay 0
599        diff_context: Rgb::new(115, 121, 148),
600        // Teal
601        diff_hunk: Rgb::new(129, 200, 190),
602        graph_colors: [
603            Rgb::new(202, 158, 230), // Mauve
604            Rgb::new(166, 209, 137), // Green
605            Rgb::new(231, 130, 132), // Red
606            Rgb::new(229, 200, 144), // Yellow
607            Rgb::new(140, 170, 238), // Blue
608            Rgb::new(244, 184, 228), // Pink
609            Rgb::new(129, 200, 190), // Teal
610            Rgb::new(239, 159, 118), // Peach
611        ],
612    }
613}
614
615pub fn catppuccin_macchiato() -> AppTheme {
616    AppTheme {
617        is_dark: true,
618        // Base
619        background: Rgb::new(36, 39, 58),
620        // Surface 0
621        surface: Rgb::new(54, 58, 79),
622        // Surface 2
623        border: Rgb::new(91, 96, 120),
624        // Surface 1
625        selection: Rgb::new(73, 77, 100),
626        // Text
627        text_primary: Rgb::new(202, 211, 245),
628        // Subtext 1
629        text_secondary: Rgb::new(184, 192, 224),
630        // Overlay 0
631        text_muted: Rgb::new(110, 115, 141),
632        // Mauve
633        accent: Rgb::new(198, 160, 246),
634        // Green
635        success: Rgb::new(166, 218, 149),
636        // Yellow
637        warning: Rgb::new(238, 212, 159),
638        // Red
639        error: Rgb::new(237, 135, 150),
640        diff_add: Rgb::new(166, 218, 149),
641        diff_del: Rgb::new(237, 135, 150),
642        // Overlay 0
643        diff_context: Rgb::new(110, 115, 141),
644        // Teal
645        diff_hunk: Rgb::new(139, 213, 202),
646        graph_colors: [
647            Rgb::new(198, 160, 246), // Mauve
648            Rgb::new(166, 218, 149), // Green
649            Rgb::new(237, 135, 150), // Red
650            Rgb::new(238, 212, 159), // Yellow
651            Rgb::new(138, 173, 244), // Blue
652            Rgb::new(245, 189, 230), // Pink
653            Rgb::new(139, 213, 202), // Teal
654            Rgb::new(245, 169, 127), // Peach
655        ],
656    }
657}
658
659pub fn catppuccin_mocha() -> AppTheme {
660    AppTheme {
661        is_dark: true,
662        // Base
663        background: Rgb::new(30, 30, 46),
664        // Surface 0
665        surface: Rgb::new(49, 50, 68),
666        // Surface 2
667        border: Rgb::new(88, 91, 112),
668        // Surface 1
669        selection: Rgb::new(69, 71, 90),
670        // Text
671        text_primary: Rgb::new(205, 214, 244),
672        // Subtext 1
673        text_secondary: Rgb::new(186, 194, 222),
674        // Overlay 0
675        text_muted: Rgb::new(108, 112, 134),
676        // Mauve
677        accent: Rgb::new(203, 166, 247),
678        // Green
679        success: Rgb::new(166, 227, 161),
680        // Yellow
681        warning: Rgb::new(249, 226, 175),
682        // Red
683        error: Rgb::new(243, 139, 168),
684        diff_add: Rgb::new(166, 227, 161),
685        diff_del: Rgb::new(243, 139, 168),
686        // Overlay 0
687        diff_context: Rgb::new(108, 112, 134),
688        // Blue
689        diff_hunk: Rgb::new(137, 180, 250),
690        graph_colors: [
691            Rgb::new(203, 166, 247), // Mauve
692            Rgb::new(166, 227, 161), // Green
693            Rgb::new(243, 139, 168), // Red
694            Rgb::new(249, 226, 175), // Yellow
695            Rgb::new(137, 180, 250), // Blue
696            Rgb::new(245, 194, 231), // Pink
697            Rgb::new(148, 226, 213), // Teal
698            Rgb::new(250, 179, 135), // Peach
699        ],
700    }
701}
702
703pub fn tokyo_night() -> AppTheme {
704    AppTheme {
705        is_dark: true,
706        background: Rgb::new(26, 27, 38),
707        surface: Rgb::new(41, 46, 66),
708        border: Rgb::new(59, 66, 97),
709        selection: Rgb::new(41, 46, 66),
710        text_primary: Rgb::new(192, 202, 245),
711        text_secondary: Rgb::new(122, 162, 247),
712        text_muted: Rgb::new(86, 95, 137),
713        accent: Rgb::new(122, 162, 247),
714        success: Rgb::new(158, 206, 106),
715        warning: Rgb::new(224, 175, 104),
716        error: Rgb::new(247, 118, 142),
717        diff_add: Rgb::new(158, 206, 106),
718        diff_del: Rgb::new(247, 118, 142),
719        diff_context: Rgb::new(86, 95, 137),
720        diff_hunk: Rgb::new(187, 154, 247),
721        graph_colors: [
722            Rgb::new(122, 162, 247),
723            Rgb::new(158, 206, 106),
724            Rgb::new(247, 118, 142),
725            Rgb::new(224, 175, 104),
726            Rgb::new(187, 154, 247),
727            Rgb::new(255, 117, 127),
728            Rgb::new(115, 218, 202),
729            Rgb::new(255, 158, 100),
730        ],
731    }
732}
733
734pub fn tokyo_night_storm() -> AppTheme {
735    AppTheme {
736        is_dark: true,
737        background: Rgb::new(36, 40, 59),
738        surface: Rgb::new(45, 49, 75),
739        border: Rgb::new(59, 66, 97),
740        selection: Rgb::new(45, 49, 75),
741        text_primary: Rgb::new(192, 202, 245),
742        text_secondary: Rgb::new(122, 162, 247),
743        text_muted: Rgb::new(86, 95, 137),
744        accent: Rgb::new(122, 162, 247),
745        success: Rgb::new(158, 206, 106),
746        warning: Rgb::new(224, 175, 104),
747        error: Rgb::new(247, 118, 142),
748        diff_add: Rgb::new(158, 206, 106),
749        diff_del: Rgb::new(247, 118, 142),
750        diff_context: Rgb::new(86, 95, 137),
751        diff_hunk: Rgb::new(187, 154, 247),
752        graph_colors: [
753            Rgb::new(122, 162, 247),
754            Rgb::new(158, 206, 106),
755            Rgb::new(247, 118, 142),
756            Rgb::new(224, 175, 104),
757            Rgb::new(187, 154, 247),
758            Rgb::new(255, 117, 127),
759            Rgb::new(115, 218, 202),
760            Rgb::new(255, 158, 100),
761        ],
762    }
763}
764
765pub fn tokyo_night_light() -> AppTheme {
766    AppTheme {
767        is_dark: false,
768        background: Rgb::new(213, 214, 219),
769        surface: Rgb::new(208, 213, 227),
770        border: Rgb::new(132, 140, 176),
771        selection: Rgb::new(208, 213, 227),
772        text_primary: Rgb::new(52, 59, 88),
773        text_secondary: Rgb::new(46, 126, 233),
774        text_muted: Rgb::new(132, 140, 176),
775        accent: Rgb::new(46, 126, 233),
776        success: Rgb::new(72, 94, 48),
777        warning: Rgb::new(140, 108, 62),
778        error: Rgb::new(143, 57, 85),
779        diff_add: Rgb::new(72, 94, 48),
780        diff_del: Rgb::new(143, 57, 85),
781        diff_context: Rgb::new(132, 140, 176),
782        diff_hunk: Rgb::new(90, 74, 120),
783        graph_colors: [
784            Rgb::new(46, 126, 233),
785            Rgb::new(72, 94, 48),
786            Rgb::new(143, 57, 85),
787            Rgb::new(140, 108, 62),
788            Rgb::new(90, 74, 120),
789            Rgb::new(166, 77, 121),
790            Rgb::new(15, 130, 130),
791            Rgb::new(180, 90, 50),
792        ],
793    }
794}
795
796pub fn kanagawa_wave() -> AppTheme {
797    AppTheme {
798        is_dark: true,
799        background: Rgb::new(31, 31, 40),
800        surface: Rgb::new(42, 42, 55),
801        border: Rgb::new(84, 84, 88),
802        selection: Rgb::new(42, 42, 55),
803        text_primary: Rgb::new(220, 215, 186),
804        text_secondary: Rgb::new(126, 156, 216),
805        text_muted: Rgb::new(114, 113, 105),
806        accent: Rgb::new(126, 156, 216),
807        success: Rgb::new(118, 148, 106),
808        warning: Rgb::new(220, 165, 97),
809        error: Rgb::new(195, 64, 67),
810        diff_add: Rgb::new(118, 148, 106),
811        diff_del: Rgb::new(195, 64, 67),
812        diff_context: Rgb::new(114, 113, 105),
813        diff_hunk: Rgb::new(210, 126, 153),
814        graph_colors: [
815            Rgb::new(126, 156, 216),
816            Rgb::new(118, 148, 106),
817            Rgb::new(195, 64, 67),
818            Rgb::new(220, 165, 97),
819            Rgb::new(210, 126, 153),
820            Rgb::new(160, 140, 200),
821            Rgb::new(106, 149, 137),
822            Rgb::new(228, 104, 118),
823        ],
824    }
825}
826
827pub fn kanagawa_dragon() -> AppTheme {
828    AppTheme {
829        is_dark: true,
830        background: Rgb::new(24, 21, 21),
831        surface: Rgb::new(40, 39, 39),
832        border: Rgb::new(80, 80, 78),
833        selection: Rgb::new(40, 39, 39),
834        text_primary: Rgb::new(197, 201, 197),
835        text_secondary: Rgb::new(139, 164, 176),
836        text_muted: Rgb::new(166, 166, 156),
837        accent: Rgb::new(139, 164, 176),
838        success: Rgb::new(135, 169, 135),
839        warning: Rgb::new(200, 170, 109),
840        error: Rgb::new(195, 64, 67),
841        diff_add: Rgb::new(135, 169, 135),
842        diff_del: Rgb::new(195, 64, 67),
843        diff_context: Rgb::new(166, 166, 156),
844        diff_hunk: Rgb::new(210, 126, 153),
845        graph_colors: [
846            Rgb::new(139, 164, 176),
847            Rgb::new(135, 169, 135),
848            Rgb::new(195, 64, 67),
849            Rgb::new(200, 170, 109),
850            Rgb::new(210, 126, 153),
851            Rgb::new(160, 140, 200),
852            Rgb::new(106, 149, 137),
853            Rgb::new(228, 104, 118),
854        ],
855    }
856}
857
858pub fn kanagawa_lotus() -> AppTheme {
859    AppTheme {
860        is_dark: false,
861        background: Rgb::new(245, 240, 215),
862        surface: Rgb::new(231, 219, 160),
863        border: Rgb::new(196, 178, 138),
864        selection: Rgb::new(231, 219, 160),
865        text_primary: Rgb::new(84, 84, 100),
866        text_secondary: Rgb::new(77, 105, 155),
867        text_muted: Rgb::new(196, 178, 138),
868        accent: Rgb::new(77, 105, 155),
869        success: Rgb::new(111, 137, 78),
870        warning: Rgb::new(119, 113, 63),
871        error: Rgb::new(195, 64, 67),
872        diff_add: Rgb::new(111, 137, 78),
873        diff_del: Rgb::new(195, 64, 67),
874        diff_context: Rgb::new(196, 178, 138),
875        diff_hunk: Rgb::new(160, 154, 190),
876        graph_colors: [
877            Rgb::new(77, 105, 155),
878            Rgb::new(111, 137, 78),
879            Rgb::new(195, 64, 67),
880            Rgb::new(119, 113, 63),
881            Rgb::new(160, 154, 190),
882            Rgb::new(155, 80, 117),
883            Rgb::new(75, 130, 120),
884            Rgb::new(180, 100, 55),
885        ],
886    }
887}
888
889pub fn moonfly() -> AppTheme {
890    AppTheme {
891        is_dark: true,
892        background: Rgb::new(8, 8, 8),
893        surface: Rgb::new(23, 23, 23),
894        border: Rgb::new(50, 50, 50),
895        selection: Rgb::new(23, 50, 80),
896        text_primary: Rgb::new(189, 189, 189),
897        text_secondary: Rgb::new(128, 160, 255),
898        text_muted: Rgb::new(99, 99, 99),
899        accent: Rgb::new(174, 129, 255),
900        success: Rgb::new(130, 170, 60),
901        warning: Rgb::new(230, 170, 50),
902        error: Rgb::new(255, 83, 112),
903        diff_add: Rgb::new(130, 170, 60),
904        diff_del: Rgb::new(255, 83, 112),
905        diff_context: Rgb::new(99, 99, 99),
906        diff_hunk: Rgb::new(128, 160, 255),
907        graph_colors: [
908            Rgb::new(174, 129, 255),
909            Rgb::new(130, 170, 60),
910            Rgb::new(255, 83, 112),
911            Rgb::new(230, 170, 50),
912            Rgb::new(128, 160, 255),
913            Rgb::new(255, 100, 180),
914            Rgb::new(80, 200, 180),
915            Rgb::new(255, 160, 80),
916        ],
917    }
918}
919
920pub fn nightfly() -> AppTheme {
921    AppTheme {
922        is_dark: true,
923        background: Rgb::new(1, 13, 32),
924        surface: Rgb::new(5, 22, 50),
925        border: Rgb::new(30, 50, 80),
926        selection: Rgb::new(10, 40, 80),
927        text_primary: Rgb::new(195, 204, 219),
928        text_secondary: Rgb::new(130, 170, 255),
929        text_muted: Rgb::new(99, 117, 150),
930        accent: Rgb::new(130, 170, 255),
931        success: Rgb::new(161, 217, 147),
932        warning: Rgb::new(236, 196, 100),
933        error: Rgb::new(252, 57, 49),
934        diff_add: Rgb::new(161, 217, 147),
935        diff_del: Rgb::new(252, 57, 49),
936        diff_context: Rgb::new(99, 117, 150),
937        diff_hunk: Rgb::new(174, 129, 255),
938        graph_colors: [
939            Rgb::new(130, 170, 255),
940            Rgb::new(161, 217, 147),
941            Rgb::new(252, 57, 49),
942            Rgb::new(236, 196, 100),
943            Rgb::new(174, 129, 255),
944            Rgb::new(255, 100, 180),
945            Rgb::new(33, 200, 170),
946            Rgb::new(255, 160, 90),
947        ],
948    }
949}
950
951pub fn oxocarbon() -> AppTheme {
952    AppTheme {
953        is_dark: true,
954        background: Rgb::new(22, 22, 22),
955        surface: Rgb::new(38, 38, 38),
956        border: Rgb::new(57, 57, 57),
957        selection: Rgb::new(38, 38, 38),
958        text_primary: Rgb::new(240, 240, 240),
959        text_secondary: Rgb::new(78, 154, 232),
960        text_muted: Rgb::new(82, 82, 82),
961        accent: Rgb::new(78, 154, 232),
962        success: Rgb::new(66, 190, 101),
963        warning: Rgb::new(190, 149, 255),
964        error: Rgb::new(238, 83, 120),
965        diff_add: Rgb::new(66, 190, 101),
966        diff_del: Rgb::new(238, 83, 120),
967        diff_context: Rgb::new(82, 82, 82),
968        diff_hunk: Rgb::new(51, 177, 255),
969        graph_colors: [
970            Rgb::new(78, 154, 232),
971            Rgb::new(66, 190, 101),
972            Rgb::new(238, 83, 120),
973            Rgb::new(190, 149, 255),
974            Rgb::new(51, 177, 255),
975            Rgb::new(255, 104, 159),
976            Rgb::new(8, 189, 186),
977            Rgb::new(255, 164, 90),
978        ],
979    }
980}
981
982/// Cyberpunk — inspired by Cyberpunk 2077's signature electric yellow
983/// accent with cyan neon highlights against a deep dark background.
984pub fn cyberpunk() -> AppTheme {
985    AppTheme {
986        is_dark: true,
987        background: Rgb::new(10, 10, 16),
988        surface: Rgb::new(20, 20, 30),
989        border: Rgb::new(45, 45, 55),
990        selection: Rgb::new(50, 48, 20),
991        text_primary: Rgb::new(230, 230, 220),
992        text_secondary: Rgb::new(0, 210, 235),
993        text_muted: Rgb::new(90, 90, 100),
994        accent: Rgb::new(252, 238, 10),
995        success: Rgb::new(0, 220, 180),
996        warning: Rgb::new(255, 150, 0),
997        error: Rgb::new(255, 50, 70),
998        diff_add: Rgb::new(0, 220, 180),
999        diff_del: Rgb::new(255, 50, 70),
1000        diff_context: Rgb::new(90, 90, 100),
1001        diff_hunk: Rgb::new(0, 210, 235),
1002        graph_colors: [
1003            Rgb::new(252, 238, 10),
1004            Rgb::new(0, 210, 235),
1005            Rgb::new(0, 220, 180),
1006            Rgb::new(255, 150, 0),
1007            Rgb::new(180, 60, 255),
1008            Rgb::new(255, 80, 120),
1009            Rgb::new(100, 255, 220),
1010            Rgb::new(255, 200, 60),
1011        ],
1012    }
1013}
1014
1015// ── Tests ─────────────────────────────────────────────────────────────────────────
1016
1017#[cfg(test)]
1018mod tests {
1019    use super::*;
1020
1021    #[test]
1022    fn all_themes_resolve() {
1023        for i in 0..THEME_COUNT {
1024            let t = theme_by_index(i);
1025            // Every theme should have non-zero text
1026            assert!(
1027                t.text_primary.r > 0 || t.text_primary.g > 0 || t.text_primary.b > 0,
1028                "theme index {i} has zero text_primary"
1029            );
1030        }
1031    }
1032
1033    #[test]
1034    fn theme_count_matches_names() {
1035        assert_eq!(THEME_NAMES.len(), THEME_COUNT);
1036    }
1037
1038    #[test]
1039    fn index_by_name_round_trips() {
1040        for (i, name) in THEME_NAMES.iter().enumerate() {
1041            assert_eq!(
1042                theme_index_by_name(name),
1043                i,
1044                "round-trip failed for '{name}'"
1045            );
1046        }
1047    }
1048
1049    #[test]
1050    fn unknown_name_returns_zero() {
1051        assert_eq!(theme_index_by_name("nonexistent"), 0);
1052    }
1053
1054    #[test]
1055    fn out_of_range_returns_default() {
1056        let d = default();
1057        let oob = theme_by_index(999);
1058        assert_eq!(d.background, oob.background);
1059        assert_eq!(d.accent, oob.accent);
1060    }
1061
1062    #[test]
1063    fn graph_colors_populated_for_all_themes() {
1064        for i in 0..THEME_COUNT {
1065            let t = theme_by_index(i);
1066            // Every theme must supply exactly 8 graph lane colours
1067            assert_eq!(
1068                t.graph_colors.len(),
1069                8,
1070                "theme index {i} does not have 8 graph_colors"
1071            );
1072            // At least two distinct colours among the 8 lanes
1073            let first = t.graph_colors[0];
1074            let all_same = t.graph_colors.iter().all(|c| *c == first);
1075            assert!(
1076                !all_same,
1077                "theme index {i} has all identical graph lane colours"
1078            );
1079        }
1080    }
1081
1082    #[test]
1083    fn graph_colors_channels_nonzero() {
1084        for i in 0..THEME_COUNT {
1085            let t = theme_by_index(i);
1086            for (lane, c) in t.graph_colors.iter().enumerate() {
1087                // Each lane colour should have at least one non-zero channel
1088                // (pure black would be invisible on dark themes)
1089                assert!(
1090                    c.r > 0 || c.g > 0 || c.b > 0,
1091                    "theme {i} graph_colors[{lane}] is pure black"
1092                );
1093            }
1094        }
1095    }
1096}