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