1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use ratatui::style::{Color, Modifier, Style};
use crate::config::theme::{Palette, Theme};
/// Companion light palette to [`super::dark_256`]. Same color
/// families (warm orange brand, edamame-green success, cool purple
/// chrome) re-tuned for a near-white page: brand colors are darker
/// and more saturated so they pop against the light surface.
/// Inverse-text sites (`fg = bg`) on any saturated brand color rely
/// on those colors being dark enough to contrast with near-white —
/// yellows are shifted to amber/orange-gold for that reason.
pub fn palette() -> Palette {
Palette {
text: Color::Indexed(234),
text_muted: Color::Indexed(244),
bg: Color::Indexed(254),
bg_muted: Color::Indexed(252),
surface: Color::Indexed(251),
surface_elevated: Color::Indexed(250),
// Orange — brand identity.
primary: Color::Indexed(166),
// Mid purple — structural chrome. Cool hue chosen to
// contrast with the warm `primary` orange.
secondary: Color::Indexed(97),
// Medium blue — list markers, table header, selection bg.
// Pale enough to let the near-black `text` (234) read
// through when used as selection bg, dark enough to read as
// a fg on the near-white page.
accent: Color::Indexed(75),
// Saturated blue — link foreground.
link: Color::Indexed(21),
success: Color::Indexed(28),
// Amber — shifted darker than the dark palette's yellow so
// inverse-text sites (highlight, raw-mode chip,
// task_unchecked text color) stay legible against the
// near-white page.
warning: Color::Indexed(172),
error: Color::Indexed(124),
// Inline code — saturated purple foreground on the muted
// light grey. Distinct enough from the `secondary` mid
// purple that inline-code reads as code rather than chrome.
code: Color::Indexed(91),
// Reserved for a future diff view; not consumed yet. Picked
// distinct from `success` / `error` so the palette has no
// duplicate slot values.
diff_add: Color::Indexed(22),
diff_delete: Color::Indexed(88),
light: true,
}
}
/// Built-in theme: builds a [`Theme`] from [`palette`] and pins the
/// `h1`–`h6` heading ramp to curated 256-cube shades. See the
/// `dark_256::theme` doc for the rationale.
pub fn theme() -> Theme {
let mut t = Theme::from_palette(&palette());
let bold = Modifier::BOLD;
let underline = Modifier::UNDERLINED;
// Heading ramp: alternates primary (orange) and secondary (purple),
// each shade pushed darker than its dark-palette counterpart so
// it reads on a light page.
let h1 = Color::Indexed(166); // primary, bright
let h2 = Color::Indexed(53); // secondary, bright (dark purple)
let h3 = Color::Indexed(130); // primary, medium
let h4 = Color::Indexed(97); // secondary, medium
let h5 = Color::Indexed(94); // primary, dull
let h6 = Color::Indexed(60); // secondary, dull
t.h1 = Style::default().fg(h1).add_modifier(bold);
t.h1_rule = Style::default().fg(h1);
t.h2 = Style::default().fg(h2).add_modifier(bold | underline);
t.h3 = Style::default().fg(h3).add_modifier(bold | underline);
t.h4 = Style::default().fg(h4).add_modifier(bold | underline);
t.h5 = Style::default().fg(h5).add_modifier(bold | underline);
t.h6 = Style::default().fg(h6).add_modifier(bold | underline);
// Code surface: a light shade between `bg` (254) and `bg_muted`
// (252, the striped-row bg) so a code span inside a stripe still
// reads as code. Indexed-cube stepping of `code` (91, saturated
// purple) shifts hue, so we pick the shade by hand.
let code_bg = Color::Indexed(253);
t.code_span = Style::default().fg(palette().code).bg(code_bg);
t.code_span_dim = Style::default()
.fg(palette().code)
.bg(code_bg)
.add_modifier(Modifier::DIM);
t.code_block_border = Style::default().fg(palette().text).bg(code_bg);
t.code_block_text = Style::default().fg(palette().text).bg(code_bg);
// Muted selection (non-focused search matches). See the
// `dark_256` counterpart: the derived blend is a no-op for indexed
// colors and would leave the highlight as the bare `surface` grey
// (251) against a 254 page. Pick a pale blue instead — a washed
// version of `accent` (75) that still reads as a highlight on the
// near-white page while the focused match's saturated 75 stays
// clearly stronger.
let selection_muted_bg = Color::Indexed(153);
t.selection_muted = Style::default().bg(selection_muted_bg).fg(palette().text);
// Diff washes. Same `blend` no-op as `selection_muted` — see the
// `dark_256` counterpart for why it matters most here: `diff_view`
// paints add / delete rows with no gutter, so collapsing both to the
// bare `surface` grey makes an addition and a deletion identical.
//
// Unlike the dark theme, this palette has room for the full
// hierarchy the derived styles intend: against near-black `text`
// (234) every pale tint below clears 7:1, so the four levels can be
// spent on focus and inline depth rather than on legibility. Each
// hue therefore ramps pale wash → stronger wash (focused) → muted
// patch (non-focused inline) → saturated patch (focused inline).
// The non-focused inline shades (151 / 181) are the greyer members
// of each ramp, so a changed word inside a non-focused hunk reads as
// deeper than its wash without competing with the focused hunk's
// brighter fills.
t.diff_add_line_unfocused = Style::default().bg(Color::Indexed(194)); // #d7ffd7
t.diff_add_line = Style::default().bg(Color::Indexed(157)); // #afffaf
t.diff_delete_line_unfocused = Style::default().bg(Color::Indexed(224)); // #ffd7d7
t.diff_delete_line = Style::default().bg(Color::Indexed(217)); // #ffafaf
// Inline (within-line) change highlights. Bold on the focused pair
// only, as in the derived styles. No foreground is pinned here —
// `text` clears 7:1 on all four shades, so unlike the dark theme's
// bright green there is nothing to rescue, and leaving fg unset lets
// the markdown's own colors show through the highlight.
t.diff_add_inline_unfocused = Style::default().bg(Color::Indexed(151)); // #afd7af
t.diff_delete_inline_unfocused = Style::default().bg(Color::Indexed(181)); // #d7afaf
t.diff_add_inline = Style::default()
.bg(Color::Indexed(114)) // #87d787
.add_modifier(Modifier::BOLD);
t.diff_delete_inline = Style::default()
.bg(Color::Indexed(210)) // #ff8787
.add_modifier(Modifier::BOLD);
// Bottom region in diff mode — green tint on the status line, red on
// the hint line, mirroring the adds-below / deletes-above stacking.
// Reuses the faint wash shades so the bars read as the same language
// as the document rows.
t.status_bar_diff = Style::default().bg(Color::Indexed(194)).fg(palette().text);
t.hint_bar_diff = Style::default().bg(Color::Indexed(224)).fg(palette().text);
t
}