返回 CodeWhale
themes.rs
根目录 / crates / palette / src / themes.rs
1 //! Named theme presets and theme resolution.
2
3 use ratatui::style::Color;
4
5 use super::detect::PaletteMode;
6 use super::tokens::*;
7
8 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
9 pub struct UiTheme {
10 pub name: &'static str,
11 pub mode: PaletteMode,
12 // Surface hierarchy
13 pub surface_bg: Color,
14 pub panel_bg: Color,
15 pub elevated_bg: Color,
16 pub composer_bg: Color,
17 pub selection_bg: Color,
18 pub header_bg: Color,
19 pub footer_bg: Color,
20 /// Text hierarchy
21 pub text_dim: Color,
22 pub text_hint: Color,
23 pub text_muted: Color,
24 pub text_body: Color,
25 pub text_soft: Color,
26 pub border: Color,
27 // Accent roles
28 pub accent_primary: Color,
29 pub accent_secondary: Color,
30 pub accent_action: Color,
31 // Error / destructive
32 pub error_fg: Color,
33 pub error_hover: Color,
34 pub error_surface: Color,
35 pub error_border: Color,
36 pub error_text: Color,
37 // Status roles (warning / success / info)
38 pub warning: Color,
39 pub success: Color,
40 pub info: Color,
41 // Mode badge colors (act/plan/operate; mode_yolo kept for legacy theme data).
42 // These must be distinct from one another and from the general accent,
43 // human, warning, danger, and success lanes: the terminal backend receives
44 // only the final `Color` and needs that identity to perform semantic ANSI
45 // adaptation.
46 pub mode_agent: Color,
47 pub mode_yolo: Color,
48 pub mode_plan: Color,
49 pub mode_operate: Color,
50 // Permission posture colors (Ask / Auto-Review / Full Access). These are
51 // explicit theme slots because the warm permission ramp is independent
52 // from the cool mode ramp.
53 pub permission_ask: Color,
54 pub permission_auto_review: Color,
55 pub permission_full_access: Color,
56 // Footer statusline colors
57 pub status_ready: Color,
58 pub status_working: Color,
59 pub status_warning: Color,
60 // Diff colors
61 pub diff_added_fg: Color,
62 pub diff_deleted_fg: Color,
63 pub diff_added_bg: Color,
64 pub diff_deleted_bg: Color,
65 // Tool cell colors
66 pub tool_running: Color,
67 pub tool_success: Color,
68 pub tool_failed: Color,
69 }
70
71 pub const UI_THEME: UiTheme = UiTheme {
72 name: "whale",
73 mode: PaletteMode::Dark,
74 surface_bg: WHALE_BG,
75 panel_bg: WHALE_PANEL,
76 elevated_bg: SURFACE_ELEVATED,
77 composer_bg: WHALE_COMPOSER,
78 selection_bg: SELECTION_BG,
79 header_bg: WHALE_CHROME,
80 footer_bg: WHALE_CHROME,
81 text_dim: TEXT_DIM,
82 text_hint: TEXT_HINT,
83 text_muted: TEXT_MUTED,
84 text_body: TEXT_BODY,
85 text_soft: TEXT_SOFT,
86 border: BORDER_COLOR,
87 accent_primary: WHALE_ACTION,
88 accent_secondary: WHALE_LIVE,
89 accent_action: WHALE_HUMAN,
90 error_fg: WHALE_ERROR,
91 error_hover: Color::Rgb(
92 WHALE_ERROR_HOVER_RGB.0,
93 WHALE_ERROR_HOVER_RGB.1,
94 WHALE_ERROR_HOVER_RGB.2,
95 ),
96 error_surface: Color::Rgb(
97 WHALE_ERROR_SURFACE_RGB.0,
98 WHALE_ERROR_SURFACE_RGB.1,
99 WHALE_ERROR_SURFACE_RGB.2,
100 ),
101 error_border: Color::Rgb(
102 WHALE_ERROR_BORDER_RGB.0,
103 WHALE_ERROR_BORDER_RGB.1,
104 WHALE_ERROR_BORDER_RGB.2,
105 ),
106 error_text: Color::Rgb(
107 WHALE_ERROR_TEXT_RGB.0,
108 WHALE_ERROR_TEXT_RGB.1,
109 WHALE_ERROR_TEXT_RGB.2,
110 ),
111 warning: STATUS_WARNING,
112 success: Color::Rgb(
113 WHALE_SUCCESS_RGB.0,
114 WHALE_SUCCESS_RGB.1,
115 WHALE_SUCCESS_RGB.2,
116 ),
117 info: WHALE_ACTION,
118 mode_agent: MODE_AGENT,
119 mode_yolo: MODE_YOLO,
120 mode_plan: MODE_PLAN,
121 mode_operate: MODE_OPERATE,
122 permission_ask: TEXT_REASONING,
123 permission_auto_review: WHALE_HUMAN,
124 permission_full_access: STATUS_WARNING,
125 status_ready: TEXT_MUTED,
126 status_working: WHALE_LIVE,
127 status_warning: STATUS_WARNING,
128 diff_added_fg: DIFF_ADDED,
129 diff_deleted_fg: WHALE_ERROR,
130 diff_added_bg: DIFF_ADDED_BG,
131 diff_deleted_bg: DIFF_DELETED_BG,
132 tool_running: WHALE_LIVE,
133 tool_success: Color::Rgb(
134 WHALE_WORKING_GREEN_RGB.0,
135 WHALE_WORKING_GREEN_RGB.1,
136 WHALE_WORKING_GREEN_RGB.2,
137 ),
138 tool_failed: WHALE_ERROR,
139 }
140 .with_terminal_native_shell();
141
142 /// The underwater theme: the one theme that owns a painted ground. The ocean
143 /// ramp (`crate::tui::ocean::OceanRamp`) paints the water column over these
144 /// surfaces; the values below are what shows through before the first paint
145 /// and on rows the ramp does not reach. Ink/accent roles match the dark whale
146 /// pair so text contrast is identical.
147 pub const UNDERWATER_UI_THEME: UiTheme = UiTheme {
148 name: "underwater",
149 mode: PaletteMode::Dark,
150 surface_bg: Color::Rgb(0x0a, 0x1e, 0x33),
151 panel_bg: Color::Rgb(0x0d, 0x22, 0x3a),
152 elevated_bg: Color::Rgb(0x10, 0x2a, 0x45),
153 composer_bg: Color::Rgb(0x0d, 0x22, 0x3a),
154 selection_bg: SELECTION_BG,
155 header_bg: Color::Rgb(0x06, 0x13, 0x20),
156 footer_bg: Color::Rgb(0x06, 0x13, 0x20),
157 text_dim: TEXT_DIM,
158 text_hint: TEXT_HINT,
159 text_muted: TEXT_MUTED,
160 text_body: TEXT_BODY,
161 text_soft: TEXT_SOFT,
162 border: BORDER_COLOR,
163 accent_primary: WHALE_ACTION,
164 accent_secondary: WHALE_LIVE,
165 accent_action: WHALE_HUMAN,
166 error_fg: WHALE_ERROR,
167 error_hover: Color::Rgb(
168 WHALE_ERROR_HOVER_RGB.0,
169 WHALE_ERROR_HOVER_RGB.1,
170 WHALE_ERROR_HOVER_RGB.2,
171 ),
172 error_surface: Color::Rgb(
173 WHALE_ERROR_SURFACE_RGB.0,
174 WHALE_ERROR_SURFACE_RGB.1,
175 WHALE_ERROR_SURFACE_RGB.2,
176 ),
177 error_border: Color::Rgb(
178 WHALE_ERROR_BORDER_RGB.0,
179 WHALE_ERROR_BORDER_RGB.1,
180 WHALE_ERROR_BORDER_RGB.2,
181 ),
182 error_text: Color::Rgb(
183 WHALE_ERROR_TEXT_RGB.0,
184 WHALE_ERROR_TEXT_RGB.1,
185 WHALE_ERROR_TEXT_RGB.2,
186 ),
187 warning: STATUS_WARNING,
188 success: Color::Rgb(
189 WHALE_SUCCESS_RGB.0,
190 WHALE_SUCCESS_RGB.1,
191 WHALE_SUCCESS_RGB.2,
192 ),
193 info: WHALE_ACTION,
194 mode_agent: MODE_AGENT,
195 mode_yolo: MODE_YOLO,
196 mode_plan: MODE_PLAN,
197 mode_operate: MODE_OPERATE,
198 permission_ask: TEXT_REASONING,
199 permission_auto_review: WHALE_HUMAN,
200 permission_full_access: STATUS_WARNING,
201 status_ready: TEXT_MUTED,
202 status_working: WHALE_LIVE,
203 status_warning: STATUS_WARNING,
204 diff_added_fg: DIFF_ADDED,
205 diff_deleted_fg: WHALE_ERROR,
206 diff_added_bg: DIFF_ADDED_BG,
207 diff_deleted_bg: DIFF_DELETED_BG,
208 tool_running: WHALE_LIVE,
209 tool_success: Color::Rgb(
210 WHALE_WORKING_GREEN_RGB.0,
211 WHALE_WORKING_GREEN_RGB.1,
212 WHALE_WORKING_GREEN_RGB.2,
213 ),
214 tool_failed: WHALE_ERROR,
215 };
216
217 /// The underwater-retro theme: the flat phosphor-teal look of the legacy
218 /// deepsea era as a selectable theme.
219 /// Same ink and accent roles as underwater so text contrast is identical;
220 /// the ground is flat near-black blue with no ombre paint, so the ocean ramp
221 /// leaves it alone.
222 pub const UNDERWATER_RETRO_UI_THEME: UiTheme = UiTheme {
223 name: "underwater-retro",
224 mode: PaletteMode::Dark,
225 surface_bg: Color::Rgb(0x05, 0x10, 0x19),
226 panel_bg: Color::Rgb(0x07, 0x15, 0x22),
227 elevated_bg: Color::Rgb(0x0a, 0x1c, 0x2c),
228 composer_bg: Color::Rgb(0x07, 0x15, 0x22),
229 selection_bg: SELECTION_BG,
230 header_bg: Color::Rgb(0x03, 0x0c, 0x13),
231 footer_bg: Color::Rgb(0x03, 0x0c, 0x13),
232 text_dim: TEXT_DIM,
233 text_hint: TEXT_HINT,
234 text_muted: TEXT_MUTED,
235 text_body: TEXT_BODY,
236 text_soft: TEXT_SOFT,
237 border: Color::Rgb(0x14, 0x50, 0x5e),
238 accent_primary: WHALE_ACTION,
239 accent_secondary: WHALE_LIVE,
240 accent_action: WHALE_HUMAN,
241 error_fg: WHALE_ERROR,
242 error_hover: Color::Rgb(
243 WHALE_ERROR_HOVER_RGB.0,
244 WHALE_ERROR_HOVER_RGB.1,
245 WHALE_ERROR_HOVER_RGB.2,
246 ),
247 error_surface: Color::Rgb(
248 WHALE_ERROR_SURFACE_RGB.0,
249 WHALE_ERROR_SURFACE_RGB.1,
250 WHALE_ERROR_SURFACE_RGB.2,
251 ),
252 error_border: Color::Rgb(
253 WHALE_ERROR_BORDER_RGB.0,
254 WHALE_ERROR_BORDER_RGB.1,
255 WHALE_ERROR_BORDER_RGB.2,
256 ),
257 error_text: Color::Rgb(
258 WHALE_ERROR_TEXT_RGB.0,
259 WHALE_ERROR_TEXT_RGB.1,
260 WHALE_ERROR_TEXT_RGB.2,
261 ),
262 warning: STATUS_WARNING,
263 success: Color::Rgb(
264 WHALE_SUCCESS_RGB.0,
265 WHALE_SUCCESS_RGB.1,
266 WHALE_SUCCESS_RGB.2,
267 ),
268 info: WHALE_ACTION,
269 mode_agent: MODE_AGENT,
270 mode_yolo: MODE_YOLO,
271 mode_plan: MODE_PLAN,
272 mode_operate: MODE_OPERATE,
273 permission_ask: TEXT_REASONING,
274 permission_auto_review: WHALE_HUMAN,
275 permission_full_access: STATUS_WARNING,
276 status_ready: TEXT_MUTED,
277 status_working: WHALE_LIVE,
278 status_warning: STATUS_WARNING,
279 diff_added_fg: DIFF_ADDED,
280 diff_deleted_fg: WHALE_ERROR,
281 diff_added_bg: DIFF_ADDED_BG,
282 diff_deleted_bg: DIFF_DELETED_BG,
283 tool_running: WHALE_LIVE,
284 tool_success: Color::Rgb(
285 WHALE_WORKING_GREEN_RGB.0,
286 WHALE_WORKING_GREEN_RGB.1,
287 WHALE_WORKING_GREEN_RGB.2,
288 ),
289 tool_failed: WHALE_ERROR,
290 };
291
292 pub const LIGHT_UI_THEME: UiTheme = UiTheme {
293 name: "whale-light",
294 mode: PaletteMode::Light,
295 surface_bg: LIGHT_SURFACE,
296 panel_bg: LIGHT_PANEL,
297 elevated_bg: LIGHT_ELEVATED,
298 composer_bg: LIGHT_PANEL,
299 selection_bg: LIGHT_SELECTION_BG,
300 header_bg: LIGHT_SURFACE,
301 footer_bg: LIGHT_SURFACE,
302 text_dim: LIGHT_TEXT_HINT,
303 text_hint: LIGHT_TEXT_HINT,
304 text_muted: LIGHT_TEXT_MUTED,
305 text_body: LIGHT_TEXT_BODY,
306 text_soft: LIGHT_TEXT_SOFT,
307 border: LIGHT_BORDER,
308 accent_primary: LIGHT_ACTION,
309 accent_secondary: LIGHT_LIVE,
310 accent_action: LIGHT_HUMAN,
311 error_fg: LIGHT_DANGER,
312 error_hover: Color::Rgb(201, 71, 120), // #C94778
313 error_surface: LIGHT_ERROR,
314 error_border: LIGHT_DANGER,
315 error_text: Color::Rgb(109, 22, 56), // #6D1638
316 warning: LIGHT_WARNING,
317 success: LIGHT_SUCCESS_FG,
318 info: LIGHT_ACTION,
319 mode_agent: LIGHT_MODE_AGENT,
320 mode_yolo: LIGHT_MODE_YOLO,
321 mode_plan: LIGHT_MODE_PLAN,
322 mode_operate: LIGHT_OPERATE,
323 permission_ask: Color::Rgb(146, 64, 14),
324 permission_auto_review: LIGHT_HUMAN,
325 permission_full_access: LIGHT_WARNING,
326 status_ready: LIGHT_TEXT_MUTED,
327 status_working: LIGHT_LIVE,
328 status_warning: LIGHT_WARNING,
329 diff_added_fg: Color::Rgb(22, 101, 52), // green
330 diff_deleted_fg: LIGHT_DANGER,
331 diff_added_bg: Color::Rgb(223, 247, 231), // light green
332 diff_deleted_bg: LIGHT_ERROR,
333 tool_running: LIGHT_LIVE,
334 tool_success: LIGHT_SUCCESS_FG,
335 tool_failed: LIGHT_DANGER,
336 }
337 .with_terminal_native_shell();
338
339 /// Shoreline — the warm charcoal alternative and the terminal's half of the
340 /// product-client palette. Fresh 0.10.0 terminal installs use Underwater.
341 ///
342 /// The GPUI desktop window and this terminal now cite the same slots, so the
343 /// two clients read as one product: a warm charcoal ground, one raised plate
344 /// for cards and the composer, one restrained blue for action and selection,
345 /// and the whale's ivory ink. It does **not** call
346 /// [`UiTheme::with_terminal_native_shell`] — the field is painted, the way the
347 /// desktop window paints it — and `adapt::theme_remap_active` remaps every
348 /// direct terminal constant onto these slots, so nothing navy-tuned leaks
349 /// through.
350 ///
351 /// The terminal's `underwater` default keeps the painted
352 /// water column, the ambient life and the ombre.
353 ///
354 /// Every pair `contrast::theme_contrast_violations` audits clears its floor.
355 pub const SHORELINE_UI_THEME: UiTheme = UiTheme {
356 name: "shoreline",
357 mode: PaletteMode::Dark,
358 surface_bg: Color::Rgb(
359 SHORELINE_SURFACE_RGB.0,
360 SHORELINE_SURFACE_RGB.1,
361 SHORELINE_SURFACE_RGB.2,
362 ),
363 panel_bg: Color::Rgb(
364 SHORELINE_PANEL_RGB.0,
365 SHORELINE_PANEL_RGB.1,
366 SHORELINE_PANEL_RGB.2,
367 ),
368 elevated_bg: Color::Rgb(
369 SHORELINE_ELEVATED_RGB.0,
370 SHORELINE_ELEVATED_RGB.1,
371 SHORELINE_ELEVATED_RGB.2,
372 ),
373 composer_bg: Color::Rgb(
374 SHORELINE_COMPOSER_RGB.0,
375 SHORELINE_COMPOSER_RGB.1,
376 SHORELINE_COMPOSER_RGB.2,
377 ),
378 selection_bg: Color::Rgb(
379 SHORELINE_SELECTION_RGB.0,
380 SHORELINE_SELECTION_RGB.1,
381 SHORELINE_SELECTION_RGB.2,
382 ),
383 header_bg: Color::Rgb(
384 SHORELINE_CHROME_RGB.0,
385 SHORELINE_CHROME_RGB.1,
386 SHORELINE_CHROME_RGB.2,
387 ),
388 footer_bg: Color::Rgb(
389 SHORELINE_CHROME_RGB.0,
390 SHORELINE_CHROME_RGB.1,
391 SHORELINE_CHROME_RGB.2,
392 ),
393 text_dim: Color::Rgb(
394 SHORELINE_TEXT_DIM_RGB.0,
395 SHORELINE_TEXT_DIM_RGB.1,
396 SHORELINE_TEXT_DIM_RGB.2,
397 ),
398 text_hint: Color::Rgb(
399 SHORELINE_TEXT_HINT_RGB.0,
400 SHORELINE_TEXT_HINT_RGB.1,
401 SHORELINE_TEXT_HINT_RGB.2,
402 ),
403 text_muted: Color::Rgb(
404 SHORELINE_TEXT_MUTED_RGB.0,
405 SHORELINE_TEXT_MUTED_RGB.1,
406 SHORELINE_TEXT_MUTED_RGB.2,
407 ),
408 text_body: Color::Rgb(
409 SHORELINE_TEXT_BODY_RGB.0,
410 SHORELINE_TEXT_BODY_RGB.1,
411 SHORELINE_TEXT_BODY_RGB.2,
412 ),
413 text_soft: Color::Rgb(
414 SHORELINE_TEXT_SOFT_RGB.0,
415 SHORELINE_TEXT_SOFT_RGB.1,
416 SHORELINE_TEXT_SOFT_RGB.2,
417 ),
418 border: Color::Rgb(
419 SHORELINE_BORDER_RGB.0,
420 SHORELINE_BORDER_RGB.1,
421 SHORELINE_BORDER_RGB.2,
422 ),
423 accent_primary: Color::Rgb(
424 SHORELINE_ACTION_RGB.0,
425 SHORELINE_ACTION_RGB.1,
426 SHORELINE_ACTION_RGB.2,
427 ),
428 accent_secondary: Color::Rgb(
429 SHORELINE_LIVE_RGB.0,
430 SHORELINE_LIVE_RGB.1,
431 SHORELINE_LIVE_RGB.2,
432 ),
433 accent_action: Color::Rgb(
434 SHORELINE_HUMAN_RGB.0,
435 SHORELINE_HUMAN_RGB.1,
436 SHORELINE_HUMAN_RGB.2,
437 ),
438 error_fg: Color::Rgb(
439 SHORELINE_ERROR_RGB.0,
440 SHORELINE_ERROR_RGB.1,
441 SHORELINE_ERROR_RGB.2,
442 ),
443 error_hover: Color::Rgb(
444 SHORELINE_ERROR_HOVER_RGB.0,
445 SHORELINE_ERROR_HOVER_RGB.1,
446 SHORELINE_ERROR_HOVER_RGB.2,
447 ),
448 error_surface: Color::Rgb(
449 SHORELINE_ERROR_SURFACE_RGB.0,
450 SHORELINE_ERROR_SURFACE_RGB.1,
451 SHORELINE_ERROR_SURFACE_RGB.2,
452 ),
453 error_border: Color::Rgb(
454 SHORELINE_ERROR_RGB.0,
455 SHORELINE_ERROR_RGB.1,
456 SHORELINE_ERROR_RGB.2,
457 ),
458 error_text: Color::Rgb(
459 SHORELINE_ERROR_TEXT_RGB.0,
460 SHORELINE_ERROR_TEXT_RGB.1,
461 SHORELINE_ERROR_TEXT_RGB.2,
462 ),
463 warning: Color::Rgb(
464 SHORELINE_WARNING_RGB.0,
465 SHORELINE_WARNING_RGB.1,
466 SHORELINE_WARNING_RGB.2,
467 ),
468 success: Color::Rgb(
469 SHORELINE_SUCCESS_RGB.0,
470 SHORELINE_SUCCESS_RGB.1,
471 SHORELINE_SUCCESS_RGB.2,
472 ),
473 info: Color::Rgb(
474 SHORELINE_ACTION_RGB.0,
475 SHORELINE_ACTION_RGB.1,
476 SHORELINE_ACTION_RGB.2,
477 ),
478 mode_agent: Color::Rgb(
479 SHORELINE_MODE_AGENT_RGB.0,
480 SHORELINE_MODE_AGENT_RGB.1,
481 SHORELINE_MODE_AGENT_RGB.2,
482 ),
483 mode_yolo: Color::Rgb(
484 SHORELINE_MODE_YOLO_RGB.0,
485 SHORELINE_MODE_YOLO_RGB.1,
486 SHORELINE_MODE_YOLO_RGB.2,
487 ),
488 mode_plan: Color::Rgb(
489 SHORELINE_MODE_PLAN_RGB.0,
490 SHORELINE_MODE_PLAN_RGB.1,
491 SHORELINE_MODE_PLAN_RGB.2,
492 ),
493 mode_operate: Color::Rgb(
494 SHORELINE_MODE_OPERATE_RGB.0,
495 SHORELINE_MODE_OPERATE_RGB.1,
496 SHORELINE_MODE_OPERATE_RGB.2,
497 ),
498 permission_ask: Color::Rgb(
499 SHORELINE_TEXT_MUTED_RGB.0,
500 SHORELINE_TEXT_MUTED_RGB.1,
501 SHORELINE_TEXT_MUTED_RGB.2,
502 ),
503 permission_auto_review: Color::Rgb(
504 SHORELINE_HUMAN_RGB.0,
505 SHORELINE_HUMAN_RGB.1,
506 SHORELINE_HUMAN_RGB.2,
507 ),
508 permission_full_access: Color::Rgb(
509 SHORELINE_WARNING_RGB.0,
510 SHORELINE_WARNING_RGB.1,
511 SHORELINE_WARNING_RGB.2,
512 ),
513 status_ready: Color::Rgb(
514 SHORELINE_SUCCESS_RGB.0,
515 SHORELINE_SUCCESS_RGB.1,
516 SHORELINE_SUCCESS_RGB.2,
517 ),
518 status_working: Color::Rgb(
519 SHORELINE_STATUS_WORKING_RGB.0,
520 SHORELINE_STATUS_WORKING_RGB.1,
521 SHORELINE_STATUS_WORKING_RGB.2,
522 ),
523 status_warning: Color::Rgb(
524 SHORELINE_WARNING_RGB.0,
525 SHORELINE_WARNING_RGB.1,
526 SHORELINE_WARNING_RGB.2,
527 ),
528 diff_added_fg: Color::Rgb(
529 SHORELINE_DIFF_ADDED_FG_RGB.0,
530 SHORELINE_DIFF_ADDED_FG_RGB.1,
531 SHORELINE_DIFF_ADDED_FG_RGB.2,
532 ),
533 diff_deleted_fg: Color::Rgb(
534 SHORELINE_DIFF_DELETED_FG_RGB.0,
535 SHORELINE_DIFF_DELETED_FG_RGB.1,
536 SHORELINE_DIFF_DELETED_FG_RGB.2,
537 ),
538 diff_added_bg: Color::Rgb(
539 SHORELINE_DIFF_ADDED_BG_RGB.0,
540 SHORELINE_DIFF_ADDED_BG_RGB.1,
541 SHORELINE_DIFF_ADDED_BG_RGB.2,
542 ),
543 diff_deleted_bg: Color::Rgb(
544 SHORELINE_DIFF_DELETED_BG_RGB.0,
545 SHORELINE_DIFF_DELETED_BG_RGB.1,
546 SHORELINE_DIFF_DELETED_BG_RGB.2,
547 ),
548 tool_running: Color::Rgb(
549 SHORELINE_LIVE_RGB.0,
550 SHORELINE_LIVE_RGB.1,
551 SHORELINE_LIVE_RGB.2,
552 ),
553 tool_success: Color::Rgb(
554 SHORELINE_SUCCESS_RGB.0,
555 SHORELINE_SUCCESS_RGB.1,
556 SHORELINE_SUCCESS_RGB.2,
557 ),
558 tool_failed: Color::Rgb(
559 SHORELINE_ERROR_RGB.0,
560 SHORELINE_ERROR_RGB.1,
561 SHORELINE_ERROR_RGB.2,
562 ),
563 };
564
565 /// Shoreline Light — Shoreline on warm paper, for light terminals.
566 pub const SHORELINE_LIGHT_UI_THEME: UiTheme = UiTheme {
567 name: "shoreline-light",
568 mode: PaletteMode::Light,
569 surface_bg: Color::Rgb(
570 SHORELINE_LIGHT_SURFACE_RGB.0,
571 SHORELINE_LIGHT_SURFACE_RGB.1,
572 SHORELINE_LIGHT_SURFACE_RGB.2,
573 ),
574 panel_bg: Color::Rgb(
575 SHORELINE_LIGHT_PANEL_RGB.0,
576 SHORELINE_LIGHT_PANEL_RGB.1,
577 SHORELINE_LIGHT_PANEL_RGB.2,
578 ),
579 elevated_bg: Color::Rgb(
580 SHORELINE_LIGHT_ELEVATED_RGB.0,
581 SHORELINE_LIGHT_ELEVATED_RGB.1,
582 SHORELINE_LIGHT_ELEVATED_RGB.2,
583 ),
584 composer_bg: Color::Rgb(
585 SHORELINE_LIGHT_COMPOSER_RGB.0,
586 SHORELINE_LIGHT_COMPOSER_RGB.1,
587 SHORELINE_LIGHT_COMPOSER_RGB.2,
588 ),
589 selection_bg: Color::Rgb(
590 SHORELINE_LIGHT_SELECTION_RGB.0,
591 SHORELINE_LIGHT_SELECTION_RGB.1,
592 SHORELINE_LIGHT_SELECTION_RGB.2,
593 ),
594 header_bg: Color::Rgb(
595 SHORELINE_LIGHT_CHROME_RGB.0,
596 SHORELINE_LIGHT_CHROME_RGB.1,
597 SHORELINE_LIGHT_CHROME_RGB.2,
598 ),
599 footer_bg: Color::Rgb(
600 SHORELINE_LIGHT_CHROME_RGB.0,
601 SHORELINE_LIGHT_CHROME_RGB.1,
602 SHORELINE_LIGHT_CHROME_RGB.2,
603 ),
604 text_dim: Color::Rgb(
605 SHORELINE_LIGHT_TEXT_DIM_RGB.0,
606 SHORELINE_LIGHT_TEXT_DIM_RGB.1,
607 SHORELINE_LIGHT_TEXT_DIM_RGB.2,
608 ),
609 text_hint: Color::Rgb(
610 SHORELINE_LIGHT_TEXT_HINT_RGB.0,
611 SHORELINE_LIGHT_TEXT_HINT_RGB.1,
612 SHORELINE_LIGHT_TEXT_HINT_RGB.2,
613 ),
614 text_muted: Color::Rgb(
615 SHORELINE_LIGHT_TEXT_MUTED_RGB.0,
616 SHORELINE_LIGHT_TEXT_MUTED_RGB.1,
617 SHORELINE_LIGHT_TEXT_MUTED_RGB.2,
618 ),
619 text_body: Color::Rgb(
620 SHORELINE_LIGHT_TEXT_BODY_RGB.0,
621 SHORELINE_LIGHT_TEXT_BODY_RGB.1,
622 SHORELINE_LIGHT_TEXT_BODY_RGB.2,
623 ),
624 text_soft: Color::Rgb(
625 SHORELINE_LIGHT_TEXT_SOFT_RGB.0,
626 SHORELINE_LIGHT_TEXT_SOFT_RGB.1,
627 SHORELINE_LIGHT_TEXT_SOFT_RGB.2,
628 ),
629 border: Color::Rgb(
630 SHORELINE_LIGHT_BORDER_RGB.0,
631 SHORELINE_LIGHT_BORDER_RGB.1,
632 SHORELINE_LIGHT_BORDER_RGB.2,
633 ),
634 accent_primary: Color::Rgb(
635 SHORELINE_LIGHT_ACTION_RGB.0,
636 SHORELINE_LIGHT_ACTION_RGB.1,
637 SHORELINE_LIGHT_ACTION_RGB.2,
638 ),
639 accent_secondary: Color::Rgb(
640 SHORELINE_LIGHT_LIVE_RGB.0,
641 SHORELINE_LIGHT_LIVE_RGB.1,
642 SHORELINE_LIGHT_LIVE_RGB.2,
643 ),
644 accent_action: Color::Rgb(
645 SHORELINE_LIGHT_HUMAN_RGB.0,
646 SHORELINE_LIGHT_HUMAN_RGB.1,
647 SHORELINE_LIGHT_HUMAN_RGB.2,
648 ),
649 error_fg: Color::Rgb(
650 SHORELINE_LIGHT_ERROR_RGB.0,
651 SHORELINE_LIGHT_ERROR_RGB.1,
652 SHORELINE_LIGHT_ERROR_RGB.2,
653 ),
654 error_hover: Color::Rgb(
655 SHORELINE_LIGHT_ERROR_RGB.0,
656 SHORELINE_LIGHT_ERROR_RGB.1,
657 SHORELINE_LIGHT_ERROR_RGB.2,
658 ),
659 error_surface: Color::Rgb(
660 SHORELINE_LIGHT_ERROR_SURFACE_RGB.0,
661 SHORELINE_LIGHT_ERROR_SURFACE_RGB.1,
662 SHORELINE_LIGHT_ERROR_SURFACE_RGB.2,
663 ),
664 error_border: Color::Rgb(
665 SHORELINE_LIGHT_ERROR_RGB.0,
666 SHORELINE_LIGHT_ERROR_RGB.1,
667 SHORELINE_LIGHT_ERROR_RGB.2,
668 ),
669 error_text: Color::Rgb(
670 SHORELINE_LIGHT_ERROR_TEXT_RGB.0,
671 SHORELINE_LIGHT_ERROR_TEXT_RGB.1,
672 SHORELINE_LIGHT_ERROR_TEXT_RGB.2,
673 ),
674 warning: Color::Rgb(
675 SHORELINE_LIGHT_WARNING_RGB.0,
676 SHORELINE_LIGHT_WARNING_RGB.1,
677 SHORELINE_LIGHT_WARNING_RGB.2,
678 ),
679 success: Color::Rgb(
680 SHORELINE_LIGHT_SUCCESS_RGB.0,
681 SHORELINE_LIGHT_SUCCESS_RGB.1,
682 SHORELINE_LIGHT_SUCCESS_RGB.2,
683 ),
684 info: Color::Rgb(
685 SHORELINE_LIGHT_ACTION_RGB.0,
686 SHORELINE_LIGHT_ACTION_RGB.1,
687 SHORELINE_LIGHT_ACTION_RGB.2,
688 ),
689 mode_agent: Color::Rgb(
690 SHORELINE_LIGHT_MODE_AGENT_RGB.0,
691 SHORELINE_LIGHT_MODE_AGENT_RGB.1,
692 SHORELINE_LIGHT_MODE_AGENT_RGB.2,
693 ),
694 mode_yolo: Color::Rgb(
695 SHORELINE_LIGHT_MODE_YOLO_RGB.0,
696 SHORELINE_LIGHT_MODE_YOLO_RGB.1,
697 SHORELINE_LIGHT_MODE_YOLO_RGB.2,
698 ),
699 mode_plan: Color::Rgb(
700 SHORELINE_LIGHT_MODE_PLAN_RGB.0,
701 SHORELINE_LIGHT_MODE_PLAN_RGB.1,
702 SHORELINE_LIGHT_MODE_PLAN_RGB.2,
703 ),
704 mode_operate: Color::Rgb(
705 SHORELINE_LIGHT_MODE_OPERATE_RGB.0,
706 SHORELINE_LIGHT_MODE_OPERATE_RGB.1,
707 SHORELINE_LIGHT_MODE_OPERATE_RGB.2,
708 ),
709 permission_ask: Color::Rgb(
710 SHORELINE_LIGHT_TEXT_MUTED_RGB.0,
711 SHORELINE_LIGHT_TEXT_MUTED_RGB.1,
712 SHORELINE_LIGHT_TEXT_MUTED_RGB.2,
713 ),
714 permission_auto_review: Color::Rgb(
715 SHORELINE_LIGHT_HUMAN_RGB.0,
716 SHORELINE_LIGHT_HUMAN_RGB.1,
717 SHORELINE_LIGHT_HUMAN_RGB.2,
718 ),
719 permission_full_access: Color::Rgb(
720 SHORELINE_LIGHT_WARNING_RGB.0,
721 SHORELINE_LIGHT_WARNING_RGB.1,
722 SHORELINE_LIGHT_WARNING_RGB.2,
723 ),
724 status_ready: Color::Rgb(
725 SHORELINE_LIGHT_SUCCESS_RGB.0,
726 SHORELINE_LIGHT_SUCCESS_RGB.1,
727 SHORELINE_LIGHT_SUCCESS_RGB.2,
728 ),
729 status_working: Color::Rgb(
730 SHORELINE_LIGHT_LIVE_RGB.0,
731 SHORELINE_LIGHT_LIVE_RGB.1,
732 SHORELINE_LIGHT_LIVE_RGB.2,
733 ),
734 status_warning: Color::Rgb(
735 SHORELINE_LIGHT_WARNING_RGB.0,
736 SHORELINE_LIGHT_WARNING_RGB.1,
737 SHORELINE_LIGHT_WARNING_RGB.2,
738 ),
739 diff_added_fg: Color::Rgb(
740 SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.0,
741 SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.1,
742 SHORELINE_LIGHT_DIFF_ADDED_FG_RGB.2,
743 ),
744 diff_deleted_fg: Color::Rgb(
745 SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.0,
746 SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.1,
747 SHORELINE_LIGHT_DIFF_DELETED_FG_RGB.2,
748 ),
749 diff_added_bg: Color::Rgb(
750 SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.0,
751 SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.1,
752 SHORELINE_LIGHT_DIFF_ADDED_BG_RGB.2,
753 ),
754 diff_deleted_bg: Color::Rgb(
755 SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.0,
756 SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.1,
757 SHORELINE_LIGHT_DIFF_DELETED_BG_RGB.2,
758 ),
759 tool_running: Color::Rgb(
760 SHORELINE_LIGHT_LIVE_RGB.0,
761 SHORELINE_LIGHT_LIVE_RGB.1,
762 SHORELINE_LIGHT_LIVE_RGB.2,
763 ),
764 tool_success: Color::Rgb(
765 SHORELINE_LIGHT_SUCCESS_RGB.0,
766 SHORELINE_LIGHT_SUCCESS_RGB.1,
767 SHORELINE_LIGHT_SUCCESS_RGB.2,
768 ),
769 tool_failed: Color::Rgb(
770 SHORELINE_LIGHT_ERROR_RGB.0,
771 SHORELINE_LIGHT_ERROR_RGB.1,
772 SHORELINE_LIGHT_ERROR_RGB.2,
773 ),
774 };
775
776 pub const SOLARIZED_LIGHT_UI_THEME: UiTheme = UiTheme {
777 name: "solarized-light",
778 mode: PaletteMode::SolarizedLight,
779 surface_bg: SOLARIZED_SURFACE,
780 panel_bg: SOLARIZED_PANEL,
781 elevated_bg: SOLARIZED_ELEVATED,
782 composer_bg: SOLARIZED_COMPOSER,
783 selection_bg: SOLARIZED_SELECT_BG,
784 header_bg: SOLARIZED_SURFACE,
785 footer_bg: SOLARIZED_SURFACE,
786 text_dim: SOLARIZED_TEXT_DIM,
787 text_hint: SOLARIZED_TEXT_HINT,
788 text_muted: SOLARIZED_TEXT_MUTED,
789 text_body: SOLARIZED_TEXT_BODY,
790 text_soft: SOLARIZED_TEXT_SOFT,
791 border: SOLARIZED_BORDER,
792 accent_primary: SOLARIZED_BLUE,
793 accent_secondary: SOLARIZED_CYAN,
794 accent_action: SOLARIZED_ORANGE,
795 error_fg: SOLARIZED_RED,
796 error_hover: SOLARIZED_ERROR_HOVER,
797 error_surface: SOLARIZED_ERROR_SURFACE,
798 error_border: SOLARIZED_RED,
799 error_text: SOLARIZED_ERROR_TEXT,
800 warning: SOLARIZED_YELLOW,
801 success: SOLARIZED_GREEN,
802 info: SOLARIZED_BLUE,
803 mode_agent: Color::Rgb(0x27, 0x8B, 0xD2),
804 mode_yolo: Color::Rgb(0xDD, 0x32, 0x2F),
805 mode_plan: Color::Rgb(0xCC, 0x4B, 0x16),
806 mode_operate: Color::Rgb(0x6C, 0x71, 0xC4), // solarized violet
807 permission_ask: SOLARIZED_YELLOW,
808 permission_auto_review: SOLARIZED_ORANGE,
809 permission_full_access: SOLARIZED_RED,
810 status_ready: SOLARIZED_CYAN,
811 status_working: SOLARIZED_CYAN,
812 status_warning: SOLARIZED_YELLOW,
813 diff_added_fg: SOLARIZED_GREEN,
814 diff_deleted_fg: SOLARIZED_RED,
815 diff_added_bg: SOLARIZED_DIFF_ADDED_BG,
816 diff_deleted_bg: SOLARIZED_DIFF_DELETED_BG,
817 tool_running: SOLARIZED_BLUE,
818 tool_success: SOLARIZED_GREEN,
819 tool_failed: SOLARIZED_RED,
820 };
821
822 pub const GRAYSCALE_UI_THEME: UiTheme = UiTheme {
823 name: "grayscale",
824 mode: PaletteMode::Grayscale,
825 surface_bg: GRAYSCALE_SURFACE,
826 panel_bg: GRAYSCALE_PANEL,
827 elevated_bg: GRAYSCALE_ELEVATED,
828 composer_bg: GRAYSCALE_PANEL,
829 selection_bg: GRAYSCALE_SELECTION_BG,
830 header_bg: GRAYSCALE_SURFACE,
831 footer_bg: GRAYSCALE_SURFACE,
832 text_dim: GRAYSCALE_TEXT_HINT,
833 text_hint: GRAYSCALE_TEXT_HINT,
834 text_muted: GRAYSCALE_TEXT_MUTED,
835 text_body: GRAYSCALE_TEXT_BODY,
836 text_soft: GRAYSCALE_TEXT_SOFT,
837 border: GRAYSCALE_BORDER,
838 accent_primary: GRAYSCALE_TEXT_SOFT,
839 accent_secondary: GRAYSCALE_TEXT_MUTED,
840 accent_action: Color::Rgb(210, 210, 210),
841 error_fg: GRAYSCALE_TEXT_BODY,
842 error_hover: GRAYSCALE_TEXT_SOFT,
843 error_surface: GRAYSCALE_ERROR,
844 error_border: GRAYSCALE_BORDER,
845 error_text: GRAYSCALE_TEXT_SOFT,
846 warning: GRAYSCALE_TEXT_MUTED,
847 success: GRAYSCALE_TEXT_SOFT,
848 info: GRAYSCALE_TEXT_MUTED,
849 mode_agent: Color::Rgb(200, 200, 200),
850 mode_yolo: Color::Rgb(237, 237, 237),
851 mode_plan: Color::Rgb(181, 181, 181),
852 // Near-white stays unmistakably distinct without claiming arbitrary pure
853 // white content as an already-resolved mode slot.
854 mode_operate: Color::Rgb(250, 250, 250),
855 permission_ask: Color::Rgb(181, 181, 181),
856 permission_auto_review: Color::Rgb(210, 210, 210),
857 permission_full_access: Color::Rgb(237, 237, 237),
858 status_ready: GRAYSCALE_TEXT_MUTED,
859 status_working: GRAYSCALE_TEXT_SOFT,
860 status_warning: GRAYSCALE_TEXT_BODY,
861 diff_added_fg: GRAYSCALE_TEXT_SOFT,
862 diff_deleted_fg: GRAYSCALE_TEXT_BODY,
863 diff_added_bg: GRAYSCALE_SUCCESS,
864 diff_deleted_bg: GRAYSCALE_ERROR,
865 tool_running: GRAYSCALE_TEXT_SOFT,
866 tool_success: GRAYSCALE_TEXT_HINT,
867 tool_failed: GRAYSCALE_TEXT_BODY,
868 };
869
870 pub const CATPPUCCIN_MOCHA_UI_THEME: UiTheme = UiTheme {
871 name: "catppuccin-mocha",
872 mode: PaletteMode::Dark,
873 surface_bg: Color::Rgb(0x1e, 0x1e, 0x2e), // base
874 panel_bg: Color::Rgb(0x18, 0x18, 0x25), // mantle
875 elevated_bg: Color::Rgb(0x31, 0x32, 0x44), // surface0
876 composer_bg: Color::Rgb(0x18, 0x18, 0x25),
877 selection_bg: Color::Rgb(0x45, 0x47, 0x5a), // surface1
878 header_bg: Color::Rgb(0x11, 0x11, 0x1b), // crust
879 footer_bg: Color::Rgb(0x11, 0x11, 0x1b),
880 text_dim: Color::Rgb(0x6c, 0x70, 0x86), // overlay0
881 text_hint: Color::Rgb(0x7f, 0x84, 0x9c), // overlay1
882 text_muted: Color::Rgb(0xa6, 0xad, 0xc8), // subtext0
883 text_body: Color::Rgb(0xcd, 0xd6, 0xf4), // text
884 text_soft: Color::Rgb(0xba, 0xc2, 0xde), // subtext1
885 border: Color::Rgb(0x45, 0x47, 0x5a), // surface1
886 accent_primary: Color::Rgb(0x89, 0xb4, 0xfa), // blue
887 accent_secondary: Color::Rgb(0x74, 0xc7, 0xec), // sapphire
888 accent_action: Color::Rgb(0xfa, 0xb3, 0x87), // peach
889 error_fg: Color::Rgb(0xf3, 0x8b, 0xa8), // red
890 error_hover: Color::Rgb(0xf5, 0xa2, 0xbc),
891 error_surface: Color::Rgb(0x3a, 0x1f, 0x2a),
892 error_border: Color::Rgb(0xf3, 0x8b, 0xa8),
893 error_text: Color::Rgb(0xf5, 0xc2, 0xd0),
894 warning: Color::Rgb(0xf9, 0xe2, 0xaf), // yellow
895 success: Color::Rgb(0xa6, 0xe3, 0xa1), // green
896 info: Color::Rgb(0x89, 0xd9, 0xeb), // sky
897 mode_agent: Color::Rgb(0x8a, 0xb4, 0xfa), // blue
898 mode_yolo: Color::Rgb(0xf3, 0x8c, 0xa8), // red
899 mode_plan: Color::Rgb(0xfa, 0xb4, 0x87), // peach
900 mode_operate: Color::Rgb(0xcb, 0xa6, 0xf7), // mauve
901 permission_ask: Color::Rgb(0xf9, 0xe2, 0xaf),
902 permission_auto_review: Color::Rgb(0xfa, 0xb3, 0x87),
903 permission_full_access: Color::Rgb(0xf3, 0x8b, 0xa8),
904 status_ready: Color::Rgb(0x7f, 0x84, 0x9c), // overlay1
905 status_working: Color::Rgb(0x74, 0xc7, 0xec), // sapphire
906 status_warning: Color::Rgb(0xf9, 0xe2, 0xaf), // yellow
907 diff_added_fg: Color::Rgb(0xa6, 0xe3, 0xa1), // green
908 diff_deleted_fg: Color::Rgb(0xf3, 0x8b, 0xa8), // red
909 diff_added_bg: Color::Rgb(0x1f, 0x33, 0x29),
910 diff_deleted_bg: Color::Rgb(0x3a, 0x1f, 0x2a),
911 tool_running: Color::Rgb(0x74, 0xc7, 0xec), // sapphire
912 tool_success: Color::Rgb(0x7f, 0x84, 0x9c), // overlay1
913 tool_failed: Color::Rgb(0xf3, 0x8b, 0xa8), // red
914 };
915
916 pub const TOKYO_NIGHT_UI_THEME: UiTheme = UiTheme {
917 name: "tokyo-night",
918 mode: PaletteMode::Dark,
919 surface_bg: Color::Rgb(0x1a, 0x1b, 0x26), // bg
920 panel_bg: Color::Rgb(0x16, 0x16, 0x1e), // bg_dark
921 elevated_bg: Color::Rgb(0x29, 0x2e, 0x42), // bg_highlight
922 composer_bg: Color::Rgb(0x16, 0x16, 0x1e),
923 selection_bg: Color::Rgb(0x28, 0x34, 0x57), // visual selection
924 header_bg: Color::Rgb(0x16, 0x16, 0x1e),
925 footer_bg: Color::Rgb(0x16, 0x16, 0x1e),
926 text_dim: Color::Rgb(0x5c, 0x65, 0x8d), // comment, lifted to 3:1 on bg
927 text_hint: Color::Rgb(0x73, 0x7a, 0xa2), // dark5
928 text_muted: Color::Rgb(0xa9, 0xb1, 0xd6), // fg_dark
929 text_body: Color::Rgb(0xc0, 0xca, 0xf5), // fg
930 text_soft: Color::Rgb(0xbb, 0xc2, 0xe0),
931 border: Color::Rgb(0x41, 0x48, 0x68), // terminal_black
932 accent_primary: Color::Rgb(0x7a, 0xa2, 0xf7), // blue
933 accent_secondary: Color::Rgb(0x7d, 0xcf, 0xff), // cyan
934 accent_action: Color::Rgb(0xff, 0x9e, 0x64), // orange
935 error_fg: Color::Rgb(0xf7, 0x76, 0x8e), // red
936 error_hover: Color::Rgb(0xf9, 0x92, 0xa4),
937 error_surface: Color::Rgb(0x33, 0x1c, 0x24),
938 error_border: Color::Rgb(0xf7, 0x76, 0x8e),
939 error_text: Color::Rgb(0xfa, 0xcc, 0xd4),
940 warning: Color::Rgb(0xe0, 0xaf, 0x68), // yellow
941 success: Color::Rgb(0x9e, 0xce, 0x6a), // green
942 info: Color::Rgb(0x7d, 0xcf, 0xff), // cyan
943 mode_agent: Color::Rgb(0x7b, 0xa2, 0xf7), // blue
944 mode_yolo: Color::Rgb(0xf7, 0x77, 0x8e), // red
945 mode_plan: Color::Rgb(0xff, 0x9f, 0x64), // orange
946 mode_operate: Color::Rgb(0xbb, 0x9a, 0xf7), // purple
947 permission_ask: Color::Rgb(0xe0, 0xaf, 0x68),
948 permission_auto_review: Color::Rgb(0xff, 0x9e, 0x64),
949 permission_full_access: Color::Rgb(0xf7, 0x76, 0x8e),
950 status_ready: Color::Rgb(0x5c, 0x65, 0x8d), // comment, lifted to 3:1 on bg
951 status_working: Color::Rgb(0x7d, 0xcf, 0xff), // cyan
952 status_warning: Color::Rgb(0xe0, 0xaf, 0x68), // yellow
953 diff_added_fg: Color::Rgb(0x9e, 0xce, 0x6a), // green
954 diff_deleted_fg: Color::Rgb(0xf7, 0x76, 0x8e), // red
955 diff_added_bg: Color::Rgb(0x1b, 0x2b, 0x1f),
956 diff_deleted_bg: Color::Rgb(0x33, 0x1c, 0x24),
957 tool_running: Color::Rgb(0x7d, 0xcf, 0xff), // cyan
958 tool_success: Color::Rgb(0x56, 0x5f, 0x89), // comment
959 tool_failed: Color::Rgb(0xf7, 0x76, 0x8e), // red
960 };
961
962 pub const DRACULA_UI_THEME: UiTheme = UiTheme {
963 name: "dracula",
964 mode: PaletteMode::Dark,
965 surface_bg: Color::Rgb(0x28, 0x2a, 0x36), // background
966 panel_bg: Color::Rgb(0x21, 0x22, 0x2c),
967 elevated_bg: Color::Rgb(0x34, 0x37, 0x46),
968 composer_bg: Color::Rgb(0x21, 0x22, 0x2c),
969 selection_bg: Color::Rgb(0x44, 0x47, 0x5a), // current line
970 header_bg: Color::Rgb(0x21, 0x22, 0x2c),
971 footer_bg: Color::Rgb(0x21, 0x22, 0x2c),
972 text_dim: Color::Rgb(0x62, 0x72, 0xa4), // comment
973 text_hint: Color::Rgb(0x8a, 0x8e, 0xaa),
974 text_muted: Color::Rgb(0xc0, 0xc4, 0xd6),
975 text_body: Color::Rgb(0xf8, 0xf8, 0xf2), // foreground
976 text_soft: Color::Rgb(0xe2, 0xe2, 0xdc),
977 border: Color::Rgb(0x44, 0x47, 0x5a),
978 accent_primary: Color::Rgb(0xbd, 0x93, 0xf9), // purple
979 accent_secondary: Color::Rgb(0x8b, 0xe9, 0xfd), // cyan
980 accent_action: Color::Rgb(0xff, 0xb8, 0x6c), // orange
981 error_fg: Color::Rgb(0xff, 0x55, 0x55), // red
982 error_hover: Color::Rgb(0xff, 0x7c, 0x7c),
983 error_surface: Color::Rgb(0x3a, 0x1f, 0x22),
984 error_border: Color::Rgb(0xff, 0x55, 0x55),
985 error_text: Color::Rgb(0xff, 0xbb, 0xbb),
986 warning: Color::Rgb(0xf1, 0xfa, 0x8c), // yellow
987 success: Color::Rgb(0x50, 0xfa, 0x7b), // green
988 info: Color::Rgb(0x8b, 0xe9, 0xfd), // cyan
989 mode_agent: Color::Rgb(0xbe, 0x93, 0xf9), // purple
990 mode_yolo: Color::Rgb(0xff, 0x56, 0x55), // red
991 mode_plan: Color::Rgb(0xff, 0xb9, 0x6c), // orange
992 mode_operate: Color::Rgb(0x8c, 0xe9, 0xfd), // cyan
993 permission_ask: Color::Rgb(0xf1, 0xfa, 0x8c),
994 permission_auto_review: Color::Rgb(0xff, 0xb8, 0x6c),
995 permission_full_access: Color::Rgb(0xff, 0x55, 0x55),
996 status_ready: Color::Rgb(0x62, 0x72, 0xa4), // comment
997 status_working: Color::Rgb(0x8b, 0xe9, 0xfd), // cyan
998 status_warning: Color::Rgb(0xf1, 0xfa, 0x8c), // yellow
999 diff_added_fg: Color::Rgb(0x50, 0xfa, 0x7b), // green
1000 diff_deleted_fg: Color::Rgb(0xff, 0x55, 0x55), // red
1001 diff_added_bg: Color::Rgb(0x21, 0x3a, 0x2a),
1002 diff_deleted_bg: Color::Rgb(0x3a, 0x1f, 0x22),
1003 tool_running: Color::Rgb(0x8b, 0xe9, 0xfd), // cyan
1004 tool_success: Color::Rgb(0x62, 0x72, 0xa4), // comment
1005 tool_failed: Color::Rgb(0xff, 0x55, 0x55), // red
1006 };
1007
1008 /// "Terminal" theme: lets the host terminal's color scheme show through
1009 /// instead of painting any RGB surface. Backgrounds use `Color::Reset`
1010 /// (the terminal's own default bg) and most text uses `Color::Reset`
1011 /// (terminal's own default fg). Accents are ANSI named colors so they
1012 /// also inherit the user's terminal palette (Solarized, Nord, custom
1013 /// schemes, etc.) rather than Codewhale brand RGB.
1014 pub const TERMINAL_UI_THEME: UiTheme = UiTheme {
1015 name: "terminal",
1016 // Mode is reported as Dark to avoid the dark→light cell remap kicking
1017 // in; the terminal-theme cell remap already normalizes everything to
1018 // `Color::Reset`, and we never want a second pass overwriting that.
1019 mode: PaletteMode::Dark,
1020 surface_bg: Color::Reset,
1021 panel_bg: Color::Reset,
1022 elevated_bg: Color::Reset,
1023 composer_bg: Color::Reset,
1024 selection_bg: Color::Reset,
1025 header_bg: Color::Reset,
1026 footer_bg: Color::Reset,
1027 text_dim: Color::Reset,
1028 text_hint: Color::Reset,
1029 text_muted: Color::Reset,
1030 text_body: Color::Reset,
1031 text_soft: Color::Reset,
1032 border: Color::Reset,
1033 accent_primary: Color::Blue,
1034 accent_secondary: Color::Cyan,
1035 accent_action: Color::Yellow,
1036 error_fg: Color::Red,
1037 error_hover: Color::Red,
1038 error_surface: Color::Reset,
1039 error_border: Color::Red,
1040 error_text: Color::Red,
1041 warning: Color::Yellow,
1042 success: Color::Green,
1043 info: Color::Cyan,
1044 mode_agent: Color::LightBlue,
1045 mode_yolo: Color::LightRed,
1046 // Magenta keeps Plan visually distinct from `status_warning` (yellow)
1047 // so the mode indicator and warning chip don't collide on themes that
1048 // render both in the status row.
1049 mode_plan: Color::Magenta,
1050 mode_operate: Color::LightCyan,
1051 permission_ask: Color::Yellow,
1052 permission_auto_review: Color::LightYellow,
1053 permission_full_access: Color::LightRed,
1054 // DarkGray gives "Ready" a low-contrast but still distinguishable hue
1055 // versus default body text (which is `Color::Reset` on this theme).
1056 status_ready: Color::DarkGray,
1057 status_working: Color::Cyan,
1058 status_warning: Color::Yellow,
1059 diff_added_fg: Color::Green,
1060 diff_deleted_fg: Color::Red,
1061 diff_added_bg: Color::Reset,
1062 diff_deleted_bg: Color::Reset,
1063 tool_running: Color::Cyan,
1064 tool_success: Color::Green,
1065 tool_failed: Color::Red,
1066 };
1067
1068 pub const GRUVBOX_DARK_UI_THEME: UiTheme = UiTheme {
1069 name: "gruvbox-dark",
1070 mode: PaletteMode::Dark,
1071 surface_bg: Color::Rgb(0x28, 0x28, 0x28), // bg0
1072 panel_bg: Color::Rgb(0x3c, 0x38, 0x36), // bg1
1073 elevated_bg: Color::Rgb(0x50, 0x49, 0x45), // bg2
1074 composer_bg: Color::Rgb(0x3c, 0x38, 0x36),
1075 selection_bg: Color::Rgb(0x66, 0x5c, 0x54), // bg3
1076 header_bg: Color::Rgb(0x1d, 0x20, 0x21), // bg0_h
1077 footer_bg: Color::Rgb(0x1d, 0x20, 0x21),
1078 text_dim: Color::Rgb(0x92, 0x83, 0x74), // gray
1079 text_hint: Color::Rgb(0xa8, 0x99, 0x84), // fg4
1080 text_muted: Color::Rgb(0xc5, 0xb8, 0xa0), // fg3, lifted to 4.5:1 on bg2
1081 text_body: Color::Rgb(0xeb, 0xdb, 0xb2), // fg1
1082 text_soft: Color::Rgb(0xd5, 0xc4, 0xa1), // fg2
1083 border: Color::Rgb(0x66, 0x5c, 0x54), // bg3
1084 accent_primary: Color::Rgb(0x83, 0xa5, 0x98), // blue
1085 accent_secondary: Color::Rgb(0x8e, 0xc0, 0x7c), // aqua/green
1086 accent_action: Color::Rgb(0xfe, 0x80, 0x19), // orange
1087 error_fg: Color::Rgb(0xfb, 0x49, 0x34), // red
1088 error_hover: Color::Rgb(0xfc, 0x7c, 0x6b),
1089 error_surface: Color::Rgb(0x35, 0x1c, 0x18),
1090 error_border: Color::Rgb(0xfb, 0x49, 0x34),
1091 error_text: Color::Rgb(0xfc, 0xc4, 0xb8),
1092 warning: Color::Rgb(0xfa, 0xbd, 0x2f), // yellow
1093 success: Color::Rgb(0x8e, 0xc0, 0x7c), // green
1094 info: Color::Rgb(0x83, 0xa5, 0x98), // blue
1095 mode_agent: Color::Rgb(0x84, 0xa5, 0x98), // blue
1096 mode_yolo: Color::Rgb(0xfb, 0x4a, 0x34), // red
1097 mode_plan: Color::Rgb(0xfe, 0x81, 0x19), // orange
1098 mode_operate: Color::Rgb(0xd3, 0x86, 0x9b), // purple
1099 permission_ask: Color::Rgb(0xfa, 0xbd, 0x2f),
1100 permission_auto_review: Color::Rgb(0xfe, 0x80, 0x19),
1101 permission_full_access: Color::Rgb(0xfb, 0x49, 0x34),
1102 status_ready: Color::Rgb(0x92, 0x83, 0x74), // gray
1103 status_working: Color::Rgb(0x8e, 0xc0, 0x7c), // aqua
1104 status_warning: Color::Rgb(0xfa, 0xbd, 0x2f), // yellow
1105 diff_added_fg: Color::Rgb(0x8e, 0xc0, 0x7c), // green
1106 diff_deleted_fg: Color::Rgb(0xfb, 0x49, 0x34), // red
1107 diff_added_bg: Color::Rgb(0x29, 0x32, 0x16),
1108 diff_deleted_bg: Color::Rgb(0x35, 0x1c, 0x18),
1109 tool_running: Color::Rgb(0x8e, 0xc0, 0x7c), // aqua
1110 tool_success: Color::Rgb(0x92, 0x83, 0x74), // gray
1111 tool_failed: Color::Rgb(0xfb, 0x49, 0x34), // red
1112 };
1113
1114 pub const CLAUDE_UI_THEME: UiTheme = UiTheme {
1115 name: "claude",
1116 mode: PaletteMode::Dark,
1117 // Claude Code product surfaces — dark navy with warm undertones
1118 surface_bg: Color::Rgb(0x18, 0x17, 0x15), // surface-dark
1119 panel_bg: Color::Rgb(0x25, 0x23, 0x20), // surface-dark-elevated
1120 elevated_bg: Color::Rgb(0x1f, 0x1e, 0x1b), // surface-dark-soft (code blocks)
1121 composer_bg: Color::Rgb(0x25, 0x23, 0x20),
1122 selection_bg: Color::Rgb(0x30, 0x2d, 0x28),
1123 header_bg: Color::Rgb(0x18, 0x17, 0x15),
1124 footer_bg: Color::Rgb(0x18, 0x17, 0x15),
1125 // Cream-tinted text hierarchy on dark
1126 text_dim: Color::Rgb(0x72, 0x70, 0x6a),
1127 text_hint: Color::Rgb(0x7d, 0x7a, 0x73),
1128 text_muted: Color::Rgb(0xa0, 0x9d, 0x96), // on-dark-soft
1129 text_body: Color::Rgb(0xfa, 0xf9, 0xf5), // on-dark (cream white)
1130 text_soft: Color::Rgb(0xd0, 0xcd, 0xc5),
1131 border: Color::Rgb(0x30, 0x2d, 0x28),
1132 // Coral primary (signature Anthropic accent), teal secondary
1133 accent_primary: Color::Rgb(0xcc, 0x78, 0x5c), // coral
1134 accent_secondary: Color::Rgb(0x5d, 0xb8, 0xa6), // accent-teal
1135 accent_action: Color::Rgb(0xe8, 0xa5, 0x5a), // amber
1136 // Error / destructive — warm red
1137 error_fg: Color::Rgb(0xe0, 0x60, 0x60),
1138 error_hover: Color::Rgb(0xd9, 0x66, 0x66),
1139 error_surface: Color::Rgb(0x2a, 0x1c, 0x1c),
1140 error_border: Color::Rgb(0xe0, 0x60, 0x60),
1141 error_text: Color::Rgb(0xe8, 0xb8, 0xb8),
1142 // Status
1143 warning: Color::Rgb(0xd4, 0xa0, 0x17), // amber
1144 success: Color::Rgb(0x5d, 0xb8, 0x72), // green
1145 info: Color::Rgb(0x5d, 0xb8, 0xa6), // teal
1146 // Mode badges
1147 mode_agent: Color::Rgb(0xcd, 0x78, 0x5c), // coral
1148 mode_yolo: Color::Rgb(0xc7, 0x45, 0x45), // red
1149 mode_plan: Color::Rgb(0xe8, 0xa6, 0x5a), // amber
1150 mode_operate: Color::Rgb(0x8a, 0x63, 0xd2), // violet
1151 permission_ask: Color::Rgb(0xd4, 0xa0, 0x17),
1152 permission_auto_review: Color::Rgb(0xe8, 0xa5, 0x5a),
1153 permission_full_access: Color::Rgb(0xe0, 0x60, 0x60),
1154 // Footer statusline
1155 status_ready: Color::Rgb(0xa0, 0x9d, 0x96),
1156 status_working: Color::Rgb(0x5d, 0xb8, 0xa6),
1157 status_warning: Color::Rgb(0xd4, 0xa0, 0x17),
1158 // Diff
1159 diff_added_fg: Color::Rgb(0x5d, 0xb8, 0x72),
1160 diff_deleted_fg: Color::Rgb(0xc6, 0x45, 0x45),
1161 diff_added_bg: Color::Rgb(0x1a, 0x24, 0x1d),
1162 diff_deleted_bg: Color::Rgb(0x24, 0x1a, 0x1a),
1163 // Tool cells
1164 tool_running: Color::Rgb(0x5d, 0xb8, 0xa6),
1165 tool_success: Color::Rgb(0xa0, 0x9d, 0x96),
1166 tool_failed: Color::Rgb(0xc6, 0x45, 0x45),
1167 };
1168
1169 pub const MATRIX_UI_THEME: UiTheme = UiTheme {
1170 name: "matrix",
1171 mode: PaletteMode::Dark,
1172 surface_bg: Color::Rgb(
1173 MATRIX_SURFACE_RGB.0,
1174 MATRIX_SURFACE_RGB.1,
1175 MATRIX_SURFACE_RGB.2,
1176 ),
1177 panel_bg: Color::Rgb(
1178 MATRIX_SURFACE_RGB.0,
1179 MATRIX_SURFACE_RGB.1,
1180 MATRIX_SURFACE_RGB.2,
1181 ),
1182 elevated_bg: Color::Rgb(
1183 MATRIX_ELEVATED_RGB.0,
1184 MATRIX_ELEVATED_RGB.1,
1185 MATRIX_ELEVATED_RGB.2,
1186 ),
1187 composer_bg: Color::Rgb(
1188 MATRIX_SURFACE_RGB.0,
1189 MATRIX_SURFACE_RGB.1,
1190 MATRIX_SURFACE_RGB.2,
1191 ),
1192 selection_bg: Color::Rgb(
1193 MATRIX_SELECTION_RGB.0,
1194 MATRIX_SELECTION_RGB.1,
1195 MATRIX_SELECTION_RGB.2,
1196 ),
1197 header_bg: Color::Rgb(
1198 MATRIX_SURFACE_RGB.0,
1199 MATRIX_SURFACE_RGB.1,
1200 MATRIX_SURFACE_RGB.2,
1201 ),
1202 footer_bg: Color::Rgb(
1203 MATRIX_SURFACE_RGB.0,
1204 MATRIX_SURFACE_RGB.1,
1205 MATRIX_SURFACE_RGB.2,
1206 ),
1207 text_dim: Color::Rgb(
1208 MATRIX_TEXT_DIM_RGB.0,
1209 MATRIX_TEXT_DIM_RGB.1,
1210 MATRIX_TEXT_DIM_RGB.2,
1211 ),
1212 text_hint: Color::Rgb(
1213 MATRIX_TEXT_HINT_RGB.0,
1214 MATRIX_TEXT_HINT_RGB.1,
1215 MATRIX_TEXT_HINT_RGB.2,
1216 ),
1217 text_muted: Color::Rgb(
1218 MATRIX_TEXT_MUTED_RGB.0,
1219 MATRIX_TEXT_MUTED_RGB.1,
1220 MATRIX_TEXT_MUTED_RGB.2,
1221 ),
1222 text_body: Color::Rgb(
1223 MATRIX_TEXT_BODY_RGB.0,
1224 MATRIX_TEXT_BODY_RGB.1,
1225 MATRIX_TEXT_BODY_RGB.2,
1226 ),
1227 text_soft: Color::Rgb(
1228 MATRIX_TEXT_SOFT_RGB.0,
1229 MATRIX_TEXT_SOFT_RGB.1,
1230 MATRIX_TEXT_SOFT_RGB.2,
1231 ),
1232 border: Color::Rgb(
1233 MATRIX_BORDER_RGB.0,
1234 MATRIX_BORDER_RGB.1,
1235 MATRIX_BORDER_RGB.2,
1236 ),
1237 accent_primary: Color::Rgb(
1238 MATRIX_BORDER_RGB.0,
1239 MATRIX_BORDER_RGB.1,
1240 MATRIX_BORDER_RGB.2,
1241 ),
1242 accent_secondary: Color::Rgb(0, 153, 0),
1243 accent_action: Color::Rgb(0x88, 0xff, 0x88),
1244 error_fg: Color::Rgb(0xb4, 0, 0),
1245 error_hover: Color::Rgb(0xe0, 0, 0),
1246 error_surface: Color::Rgb(0x1a, 0x0d, 0x0d),
1247 error_border: Color::Rgb(0xb4, 0, 0),
1248 error_text: Color::Rgb(0xff, 0x44, 0x44),
1249 warning: Color::Rgb(204, 204, 0),
1250 success: Color::Rgb(0x88, 0xff, 0x88),
1251 info: Color::Rgb(0, 204, 0),
1252 mode_agent: Color::Rgb(0, 154, 0),
1253 mode_yolo: Color::Rgb(255, 100, 100),
1254 mode_plan: Color::Rgb(255, 170, 60),
1255 mode_operate: Color::Rgb(100, 255, 220),
1256 permission_ask: Color::Rgb(204, 204, 0),
1257 permission_auto_review: Color::Rgb(255, 170, 60),
1258 permission_full_access: Color::Rgb(255, 100, 100),
1259 status_ready: Color::Rgb(0, 108, 0),
1260 status_working: Color::Rgb(
1261 MATRIX_TEXT_BODY_RGB.0,
1262 MATRIX_TEXT_BODY_RGB.1,
1263 MATRIX_TEXT_BODY_RGB.2,
1264 ),
1265 status_warning: Color::Rgb(204, 204, 0),
1266 diff_added_fg: Color::Rgb(0x88, 0xff, 0x88),
1267 diff_deleted_fg: Color::Rgb(0xbc, 0x1d, 0x1d), // lifted to 3:1 on diff_deleted_bg
1268 diff_added_bg: Color::Rgb(0x0d, 0x1a, 0x0d),
1269 diff_deleted_bg: Color::Rgb(0x1a, 0x0d, 0x0d),
1270 tool_running: Color::Rgb(0x88, 0xff, 0x88),
1271 tool_success: Color::Rgb(0, 102, 0),
1272 tool_failed: Color::Rgb(0xb4, 0, 0),
1273 };
1274
1275 pub const UWU_UI_THEME: UiTheme = UiTheme {
1276 name: "uwu",
1277 mode: PaletteMode::Dark,
1278 surface_bg: Color::Rgb(0x16, 0x12, 0x1c),
1279 panel_bg: Color::Rgb(0x1f, 0x18, 0x28),
1280 elevated_bg: Color::Rgb(0x2c, 0x22, 0x38),
1281 composer_bg: Color::Rgb(0x1f, 0x18, 0x28),
1282 selection_bg: Color::Rgb(0x3d, 0x2e, 0x4e),
1283 header_bg: Color::Rgb(0x12, 0x0e, 0x18),
1284 footer_bg: Color::Rgb(0x12, 0x0e, 0x18),
1285 text_dim: Color::Rgb(0x6e, 0x60, 0x7a),
1286 text_hint: Color::Rgb(0x8a, 0x7c, 0x98),
1287 text_muted: Color::Rgb(0xb8, 0xaa, 0xc8),
1288 text_body: Color::Rgb(0xf7, 0xf0, 0xf8),
1289 text_soft: Color::Rgb(0xe8, 0xdc, 0xee),
1290 border: Color::Rgb(0x4a, 0x3a, 0x5c),
1291 accent_primary: Color::Rgb(0xff, 0x9e, 0xcd),
1292 accent_secondary: Color::Rgb(0x9a, 0xec, 0xe0),
1293 accent_action: Color::Rgb(0xff, 0xd6, 0x9a),
1294 error_fg: Color::Rgb(0xff, 0x6b, 0x8a),
1295 error_hover: Color::Rgb(0xff, 0x8f, 0xa8),
1296 error_surface: Color::Rgb(0x3a, 0x18, 0x28),
1297 error_border: Color::Rgb(0xff, 0x6b, 0x8a),
1298 error_text: Color::Rgb(0xff, 0xc8, 0xd6),
1299 warning: Color::Rgb(0xff, 0xe0, 0x8a),
1300 success: Color::Rgb(0x9a, 0xef, 0xc0),
1301 info: Color::Rgb(0xb8, 0xc8, 0xff),
1302 mode_agent: Color::Rgb(0xc4, 0xa8, 0xff),
1303 mode_yolo: Color::Rgb(0xff, 0x55, 0x77),
1304 mode_plan: Color::Rgb(0xff, 0xb4, 0xe8),
1305 mode_operate: Color::Rgb(0x8a, 0xd4, 0xff),
1306 permission_ask: Color::Rgb(0xff, 0xe0, 0x8a),
1307 permission_auto_review: Color::Rgb(0xff, 0xd6, 0x9a),
1308 permission_full_access: Color::Rgb(0xff, 0x6b, 0x8a),
1309 status_ready: Color::Rgb(0x8a, 0x7c, 0x98),
1310 status_working: Color::Rgb(0x9a, 0xec, 0xe0),
1311 status_warning: Color::Rgb(0xff, 0xe0, 0x8a),
1312 diff_added_fg: Color::Rgb(0x9a, 0xef, 0xc0),
1313 diff_deleted_fg: Color::Rgb(0xff, 0x6b, 0x8a),
1314 diff_added_bg: Color::Rgb(0x1a, 0x2a, 0x24),
1315 diff_deleted_bg: Color::Rgb(0x3a, 0x18, 0x28),
1316 tool_running: Color::Rgb(0x9a, 0xec, 0xe0),
1317 tool_success: Color::Rgb(0x8a, 0x7c, 0x98),
1318 tool_failed: Color::Rgb(0xff, 0x6b, 0x8a),
1319 };
1320
1321 /// Stable identifiers for the named themes the user can select. `System`
1322 /// defers to `PaletteMode::detect()` (terminal-driven dark/light). Each
1323 /// dark/light id resolves to a single fixed `UiTheme`.
1324 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1325 pub enum ThemeId {
1326 System,
1327 Terminal,
1328 Shoreline,
1329 ShorelineLight,
1330 Underwater,
1331 UnderwaterRetro,
1332 Whale,
1333 WhaleLight,
1334 Grayscale,
1335 CatppuccinMocha,
1336 TokyoNight,
1337 Dracula,
1338 GruvboxDark,
1339 Claude,
1340 Matrix,
1341 SolarizedLight,
1342 Uwu,
1343 }
1344
1345 impl ThemeId {
1346 /// Parse a settings string (`"system"`, `"dark"`, `"catppuccin-mocha"`, …).
1347 /// Accepts a few aliases (`"whale"` for dark, `"light"` for whale-light)
1348 /// so existing config files keep working. Case-insensitive.
1349 #[must_use]
1350 pub fn from_name(value: &str) -> Option<Self> {
1351 match normalize_theme_name(value)? {
1352 "system" => Some(Self::System),
1353 "terminal" => Some(Self::Terminal),
1354 "underwater" | "deepsea" => Some(Self::Underwater),
1355 "underwater-retro" | "retro" => Some(Self::UnderwaterRetro),
1356 "shoreline" => Some(Self::Shoreline),
1357 "shoreline-light" => Some(Self::ShorelineLight),
1358 "dark" => Some(Self::Whale),
1359 "light" => Some(Self::WhaleLight),
1360 "grayscale" => Some(Self::Grayscale),
1361 "catppuccin-mocha" => Some(Self::CatppuccinMocha),
1362 "tokyo-night" => Some(Self::TokyoNight),
1363 "dracula" => Some(Self::Dracula),
1364 "gruvbox-dark" => Some(Self::GruvboxDark),
1365 "claude" => Some(Self::Claude),
1366 "matrix" => Some(Self::Matrix),
1367 "solarized-light" => Some(Self::SolarizedLight),
1368 "uwu" => Some(Self::Uwu),
1369 _ => None,
1370 }
1371 }
1372
1373 /// Canonical settings string (lowercase, dash-separated). Round-trips
1374 /// through `from_name`.
1375 #[must_use]
1376 pub const fn name(self) -> &'static str {
1377 match self {
1378 Self::System => "system",
1379 Self::Terminal => "terminal",
1380 Self::Shoreline => "shoreline",
1381 Self::ShorelineLight => "shoreline-light",
1382 Self::Underwater => "underwater",
1383 Self::UnderwaterRetro => "underwater-retro",
1384 Self::Whale => "dark",
1385 Self::WhaleLight => "light",
1386 Self::Grayscale => "grayscale",
1387 Self::CatppuccinMocha => "catppuccin-mocha",
1388 Self::TokyoNight => "tokyo-night",
1389 Self::Dracula => "dracula",
1390 Self::GruvboxDark => "gruvbox-dark",
1391 Self::Claude => "claude",
1392 Self::Matrix => "matrix",
1393 Self::SolarizedLight => "solarized-light",
1394 Self::Uwu => "uwu",
1395 }
1396 }
1397
1398 /// Human-readable label for picker rows.
1399 #[must_use]
1400 pub const fn display_name(self) -> &'static str {
1401 match self {
1402 Self::System => "System",
1403 Self::Terminal => "Terminal",
1404 Self::Shoreline => "Shoreline",
1405 Self::ShorelineLight => "Shoreline Light",
1406 Self::Underwater => "Underwater",
1407 Self::UnderwaterRetro => "Underwater Retro",
1408 Self::Whale => "Blue Stage",
1409 Self::WhaleLight => "Blue Stage Light",
1410 Self::Grayscale => "Grayscale",
1411 Self::CatppuccinMocha => "Catppuccin Mocha",
1412 Self::TokyoNight => "Tokyo Night",
1413 Self::Dracula => "Dracula",
1414 Self::GruvboxDark => "Gruvbox Dark",
1415 Self::Claude => "Claude",
1416 Self::Matrix => "Matrix",
1417 Self::SolarizedLight => "Solarized Light",
1418 Self::Uwu => "Uwu",
1419 }
1420 }
1421
1422 /// Short tagline for picker rows.
1423 #[must_use]
1424 pub const fn tagline(self) -> &'static str {
1425 match self {
1426 Self::System => "Follow terminal background (COLORFGBG / macOS appearance)",
1427 Self::Terminal => "Inherit terminal colors fully (transparent surfaces, ANSI accents)",
1428 Self::Shoreline => "Warm charcoal, one restrained blue — the desktop client's palette",
1429 Self::ShorelineLight => "Shoreline on warm paper — the desktop client's light mode",
1430 Self::Underwater => "The painted ocean field: ombre water, ambient life, the whale",
1431 Self::UnderwaterRetro => "Flat phosphor-teal ocean: the legacy deepsea look, no ombre",
1432 Self::Whale => "Stage black, action blue, and one Signal Gold human beacon",
1433 Self::WhaleLight => "Paper, cobalt action, and one Signal Gold human beacon",
1434 Self::Grayscale => "Color-minimal high contrast",
1435 Self::CatppuccinMocha => "Soft pastels on warm dark",
1436 Self::TokyoNight => "Deep blue/violet night palette",
1437 Self::Dracula => "Classic high-contrast purple",
1438 Self::GruvboxDark => "Vintage warm earth tones",
1439 Self::Claude => "Warm navy & coral",
1440 Self::Matrix => "The Matrix films inspired theme",
1441 Self::SolarizedLight => {
1442 "Solarized light — Light, calming palette on warm ivory — easy on the eyes"
1443 }
1444 Self::Uwu => "Soft kawaii night — sakura, mint, and peach",
1445 }
1446 }
1447
1448 /// Resolve to a concrete `UiTheme`. For `System` this consults
1449 /// `PaletteMode::detect()` exactly once and returns the corresponding
1450 /// dark/light theme — callers that want to live-track terminal background
1451 /// changes need to re-invoke this.
1452 #[must_use]
1453 pub fn ui_theme(self) -> UiTheme {
1454 match self {
1455 Self::System => UiTheme::detect(),
1456 Self::Terminal => TERMINAL_UI_THEME,
1457 Self::Shoreline => SHORELINE_UI_THEME,
1458 Self::ShorelineLight => SHORELINE_LIGHT_UI_THEME,
1459 Self::Underwater => UNDERWATER_UI_THEME,
1460 Self::UnderwaterRetro => UNDERWATER_RETRO_UI_THEME,
1461 Self::Whale => UI_THEME,
1462 Self::WhaleLight => LIGHT_UI_THEME,
1463 Self::Grayscale => GRAYSCALE_UI_THEME,
1464 Self::CatppuccinMocha => CATPPUCCIN_MOCHA_UI_THEME,
1465 Self::TokyoNight => TOKYO_NIGHT_UI_THEME,
1466 Self::Dracula => DRACULA_UI_THEME,
1467 Self::GruvboxDark => GRUVBOX_DARK_UI_THEME,
1468 Self::Claude => CLAUDE_UI_THEME,
1469 Self::Matrix => MATRIX_UI_THEME,
1470 Self::SolarizedLight => SOLARIZED_LIGHT_UI_THEME,
1471 Self::Uwu => UWU_UI_THEME,
1472 }
1473 }
1474 }
1475
1476 /// Themes shown in the `/theme` picker, in display order.
1477 pub const SELECTABLE_THEMES: &[ThemeId] = &[
1478 ThemeId::System,
1479 ThemeId::Terminal,
1480 ThemeId::Shoreline,
1481 ThemeId::ShorelineLight,
1482 ThemeId::Underwater,
1483 ThemeId::UnderwaterRetro,
1484 ThemeId::Whale,
1485 ThemeId::WhaleLight,
1486 ThemeId::Grayscale,
1487 ThemeId::CatppuccinMocha,
1488 ThemeId::TokyoNight,
1489 ThemeId::Dracula,
1490 ThemeId::GruvboxDark,
1491 ThemeId::Claude,
1492 ThemeId::Matrix,
1493 ThemeId::SolarizedLight,
1494 ThemeId::Uwu,
1495 ];
1496
1497 impl UiTheme {
1498 /// Let the terminal own the ordinary shell while retaining explicit
1499 /// semantic surfaces (selection, elevation, errors, diffs, and status).
1500 ///
1501 /// Deepsea paints these reset cells later through the shared ocean
1502 /// column; Flat leaves them untouched so the host terminal shows through.
1503 #[must_use]
1504 pub const fn with_terminal_native_shell(mut self) -> Self {
1505 self.surface_bg = Color::Reset;
1506 self.panel_bg = Color::Reset;
1507 self.composer_bg = Color::Reset;
1508 self.header_bg = Color::Reset;
1509 self.footer_bg = Color::Reset;
1510 self
1511 }
1512
1513 #[must_use]
1514 pub fn for_mode(mode: PaletteMode) -> Self {
1515 match mode {
1516 PaletteMode::Dark => UI_THEME,
1517 PaletteMode::Light => LIGHT_UI_THEME,
1518 PaletteMode::Grayscale => GRAYSCALE_UI_THEME,
1519 PaletteMode::SolarizedLight => SOLARIZED_LIGHT_UI_THEME,
1520 }
1521 }
1522
1523 #[must_use]
1524 pub fn detect() -> Self {
1525 Self::for_mode(PaletteMode::detect())
1526 }
1527
1528 #[must_use]
1529 pub fn with_background_color(mut self, color: Color) -> Self {
1530 self.surface_bg = color;
1531 self.header_bg = color;
1532 self.footer_bg = color;
1533 self
1534 }
1535 }
1536
1537 #[must_use]
1538 pub fn normalize_theme_name(value: &str) -> Option<&'static str> {
1539 match value.trim().to_ascii_lowercase().as_str() {
1540 "" | "auto" | "system" | "default" => Some("system"),
1541 "terminal" | "term" | "transparent" | "follow-terminal" | "inherit" => Some("terminal"),
1542 "underwater" | "deepsea" | "deep-sea" | "ocean" | "ombre" => Some("underwater"),
1543 "underwater-retro" | "retro" | "uw-retro" => Some("underwater-retro"),
1544 "shoreline" | "warm" | "charcoal" => Some("shoreline"),
1545 "shoreline-light" | "paper" => Some("shoreline-light"),
1546 "dark" | "whale" | "whale-dark" => Some("dark"),
1547 "light" | "whale-light" => Some("light"),
1548 "grayscale" | "greyscale" | "gray" | "grey" | "mono" | "monochrome" | "black-white"
1549 | "black_and_white" | "blackwhite" | "bw" | "b&w" => Some("grayscale"),
1550 "catppuccin-mocha" | "catppuccin" | "mocha" => Some("catppuccin-mocha"),
1551 "tokyo-night" | "tokyonight" | "tokyo" => Some("tokyo-night"),
1552 "dracula" => Some("dracula"),
1553 "gruvbox-dark" | "gruvbox" => Some("gruvbox-dark"),
1554 "claude" => Some("claude"),
1555 "matrix" | "hacker" => Some("matrix"),
1556 "solarized-light" | "solarized" => Some("solarized-light"),
1557 "uwu" | "owo" | "kawaii" => Some("uwu"),
1558 _ => None,
1559 }
1560 }
1561
1562 #[must_use]
1563 pub fn theme_label_for_mode(mode: PaletteMode) -> &'static str {
1564 match mode {
1565 PaletteMode::Dark => "dark",
1566 PaletteMode::Light => "light",
1567 PaletteMode::Grayscale => "grayscale",
1568 PaletteMode::SolarizedLight => "solarized-light",
1569 }
1570 }
1571
1572 #[must_use]
1573 #[cfg(test)]
1574 pub fn ui_theme_from_settings(theme: &str, background_color: Option<&str>) -> UiTheme {
1575 super::resolve_theme_setting(theme, background_color)
1576 .map(|(_, _, theme)| theme)
1577 .unwrap_or_else(|_| UiTheme::detect())
1578 }
1579
1580 #[must_use]
1581 pub fn parse_hex_rgb_color(value: &str) -> Option<Color> {
1582 let hex = value.trim().strip_prefix('#').unwrap_or(value.trim());
1583 if hex.len() != 6 || !hex.chars().all(|ch| ch.is_ascii_hexdigit()) {
1584 return None;
1585 }
1586
1587 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
1588 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
1589 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
1590 Some(Color::Rgb(r, g, b))
1591 }
1592
1593 #[must_use]
1594 pub fn normalize_hex_rgb_color(value: &str) -> Option<String> {
1595 hex_rgb_string(parse_hex_rgb_color(value)?)
1596 }
1597
1598 #[must_use]
1599 pub fn hex_rgb_string(color: Color) -> Option<String> {
1600 let Color::Rgb(r, g, b) = color else {
1601 return None;
1602 };
1603 Some(format!("#{r:02x}{g:02x}{b:02x}"))
1604 }
1605
1606 #[cfg(test)]
1607 mod tests {
1608 use super::*;
1609
1610 #[test]
1611 fn whale_refresh_keeps_theme_ids_and_default_compatibility() {
1612 let names = SELECTABLE_THEMES
1613 .iter()
1614 .map(|theme| theme.name())
1615 .collect::<Vec<_>>();
1616 assert_eq!(
1617 names,
1618 [
1619 "system",
1620 "terminal",
1621 "shoreline",
1622 "shoreline-light",
1623 "underwater",
1624 "underwater-retro",
1625 "dark",
1626 "light",
1627 "grayscale",
1628 "catppuccin-mocha",
1629 "tokyo-night",
1630 "dracula",
1631 "gruvbox-dark",
1632 "claude",
1633 "matrix",
1634 "solarized-light",
1635 "uwu",
1636 ]
1637 );
1638 assert_eq!(normalize_theme_name("default"), Some("system"));
1639 assert_eq!(normalize_theme_name("whale"), Some("dark"));
1640 // The retired treatment spellings are theme names now.
1641 assert_eq!(normalize_theme_name("underwater"), Some("underwater"));
1642 assert_eq!(normalize_theme_name("deepsea"), Some("underwater"));
1643 assert_eq!(normalize_theme_name("ombre"), Some("underwater"));
1644 assert_eq!(normalize_theme_name("owo"), Some("uwu"));
1645 assert_eq!(normalize_theme_name("kawaii"), Some("uwu"));
1646 assert_eq!(normalize_theme_name("retro"), Some("underwater-retro"));
1647 assert_eq!(
1648 ThemeId::from_name("underwater-retro"),
1649 Some(ThemeId::UnderwaterRetro)
1650 );
1651 assert_eq!(ThemeId::UnderwaterRetro.name(), "underwater-retro");
1652 }
1653
1654 #[test]
1655 fn whale_pair_uses_codewhale_semantic_grammar() {
1656 assert_eq!(UI_THEME.accent_primary, WHALE_ACTION);
1657 assert_eq!(UI_THEME.status_working, WHALE_LIVE);
1658 assert_eq!(UI_THEME.accent_action, WHALE_HUMAN);
1659 assert_eq!(UI_THEME.warning, STATUS_WARNING);
1660 assert_eq!(UI_THEME.error_fg, WHALE_ERROR);
1661 assert_eq!(UI_THEME.mode_operate, MODE_OPERATE);
1662 assert_eq!(LIGHT_UI_THEME.accent_primary, LIGHT_ACTION);
1663 assert_eq!(LIGHT_UI_THEME.status_working, LIGHT_LIVE);
1664 assert_eq!(LIGHT_UI_THEME.accent_action, LIGHT_HUMAN);
1665 assert_eq!(LIGHT_UI_THEME.warning, LIGHT_WARNING);
1666 assert_eq!(LIGHT_UI_THEME.error_fg, LIGHT_DANGER);
1667 assert_eq!(LIGHT_UI_THEME.mode_operate, LIGHT_OPERATE);
1668 }
1669
1670 /// Dogfood A7 (#4092): every mode must be tellable apart from the footer
1671 /// badge alone — Operate must never wear the full-access red again. Mode
1672 /// slots also stay distinct from general semantic lanes so limited-color
1673 /// adaptation can identify direct `UiTheme` call sites without guessing.
1674 #[test]
1675 fn every_selectable_theme_keeps_mode_badges_distinct() {
1676 for theme_id in SELECTABLE_THEMES {
1677 let ui = theme_id.ui_theme();
1678 let badges = [
1679 ("work", ui.mode_agent),
1680 ("plan", ui.mode_plan),
1681 ("operate", ui.mode_operate),
1682 ("full access", ui.mode_yolo),
1683 ];
1684 for (i, (name_a, color_a)) in badges.iter().enumerate() {
1685 for (name_b, color_b) in badges.iter().skip(i + 1) {
1686 assert_ne!(
1687 color_a,
1688 color_b,
1689 "theme '{}' renders modes '{name_a}' and '{name_b}' with the same badge color",
1690 theme_id.name(),
1691 );
1692 }
1693 }
1694 let semantic_lanes = [
1695 ("action", ui.accent_primary),
1696 ("live", ui.status_working),
1697 ("human", ui.accent_action),
1698 ("warning", ui.warning),
1699 ("danger", ui.error_fg),
1700 ("success", ui.success),
1701 ];
1702 for (mode_name, mode_color) in badges {
1703 for (lane_name, lane_color) in semantic_lanes {
1704 assert_ne!(
1705 mode_color,
1706 lane_color,
1707 "theme '{}' reuses the {lane_name} color for mode '{mode_name}', erasing render-stage identity",
1708 theme_id.name(),
1709 );
1710 }
1711 }
1712 }
1713 }
1714
1715 #[test]
1716 fn every_selectable_theme_keeps_permission_lanes_distinct() {
1717 for theme_id in SELECTABLE_THEMES {
1718 let ui = theme_id.ui_theme();
1719 let permissions = [
1720 ("ask", ui.permission_ask),
1721 ("auto-review", ui.permission_auto_review),
1722 ("full-access", ui.permission_full_access),
1723 ];
1724 for (index, (name_a, color_a)) in permissions.iter().enumerate() {
1725 for (name_b, color_b) in permissions.iter().skip(index + 1) {
1726 assert_ne!(
1727 color_a,
1728 color_b,
1729 "theme '{}' renders permission lanes '{name_a}' and '{name_b}' identically",
1730 theme_id.name(),
1731 );
1732 }
1733 }
1734 }
1735 }
1736
1737 #[test]
1738 fn every_selectable_theme_separates_live_workers_from_completed_work() {
1739 for theme_id in SELECTABLE_THEMES {
1740 let ui = theme_id.ui_theme();
1741 assert_ne!(
1742 ui.info,
1743 ui.success,
1744 "theme '{}' makes live workers indistinguishable from completed work",
1745 theme_id.name(),
1746 );
1747 }
1748 }
1749 }
1750
1750 lines RUST