1use crate::{Color, color};
3
4use std::sync::LazyLock;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct Palette {
9 pub background: Background,
11 pub primary: Swatch,
13 pub secondary: Swatch,
15 pub success: Swatch,
17 pub warning: Swatch,
19 pub danger: Swatch,
21 pub is_dark: bool,
23}
24
25impl Palette {
26 pub fn generate(palette: Seed) -> Self {
28 Self {
29 background: Background::new(palette.background, palette.text),
30 primary: Swatch::generate(palette.primary, palette.background, palette.text),
31 secondary: Swatch::derive(palette.background, palette.text),
32 success: Swatch::generate(palette.success, palette.background, palette.text),
33 warning: Swatch::generate(palette.warning, palette.background, palette.text),
34 danger: Swatch::generate(palette.danger, palette.background, palette.text),
35 is_dark: is_dark(palette.background),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq)]
42pub struct Pair {
43 pub color: Color,
45
46 pub text: Color,
52}
53
54impl Pair {
55 pub fn new(color: Color, text: Color) -> Self {
57 Self {
58 color,
59 text: readable(color, text),
60 }
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq)]
66pub struct Background {
67 pub base: Pair,
69 pub weakest: Pair,
71 pub weaker: Pair,
73 pub weak: Pair,
75 pub neutral: Pair,
77 pub strong: Pair,
79 pub stronger: Pair,
81 pub strongest: Pair,
83}
84
85impl Background {
86 pub fn new(base: Color, text: Color) -> Self {
88 let weakest = deviate(base, 0.03);
89 let weaker = deviate(base, 0.07);
90 let weak = deviate(base, 0.1);
91 let neutral = deviate(base, 0.125);
92 let strong = deviate(base, 0.15);
93 let stronger = deviate(base, 0.175);
94 let strongest = deviate(base, 0.20);
95
96 Self {
97 base: Pair::new(base, text),
98 weakest: Pair::new(weakest, text),
99 weaker: Pair::new(weaker, text),
100 weak: Pair::new(weak, text),
101 neutral: Pair::new(neutral, text),
102 strong: Pair::new(strong, text),
103 stronger: Pair::new(stronger, text),
104 strongest: Pair::new(strongest, text),
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq)]
111pub struct Swatch {
112 pub base: Pair,
114 pub weak: Pair,
116 pub strong: Pair,
118}
119
120impl Swatch {
121 pub fn generate(base: Color, background: Color, text: Color) -> Self {
123 let weak = base.mix(background, 0.4);
124 let strong = deviate(base, 0.1);
125
126 Self {
127 base: Pair::new(base, text),
128 weak: Pair::new(weak, text),
129 strong: Pair::new(strong, text),
130 }
131 }
132
133 pub fn derive(base: Color, text: Color) -> Self {
135 let factor = if is_dark(base) { 0.2 } else { 0.4 };
136
137 let weak = deviate(base, 0.1).mix(text, factor);
138 let strong = deviate(base, 0.3).mix(text, factor);
139 let base = deviate(base, 0.2).mix(text, factor);
140
141 Self {
142 base: Pair::new(base, text),
143 weak: Pair::new(weak, text),
144 strong: Pair::new(strong, text),
145 }
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152pub struct Seed {
153 pub background: Color,
155 pub text: Color,
157 pub primary: Color,
159 pub success: Color,
161 pub warning: Color,
163 pub danger: Color,
165}
166
167impl Seed {
168 pub const LIGHT: Self = Self {
170 background: Color::WHITE,
171 text: Color::BLACK,
172 primary: color!(0x5865F2),
173 success: color!(0x12664f),
174 warning: color!(0xb77e33),
175 danger: color!(0xc3423f),
176 };
177
178 pub const DARK: Self = Self {
180 background: color!(0x2B2D31),
181 text: Color::from_rgb(0.90, 0.90, 0.90),
182 primary: color!(0x5865F2),
183 success: color!(0x12664f),
184 warning: color!(0xffc14e),
185 danger: color!(0xc3423f),
186 };
187
188 pub const DRACULA: Self = Self {
192 background: color!(0x282A36), text: color!(0xf8f8f2), primary: color!(0xbd93f9), success: color!(0x50fa7b), warning: color!(0xf1fa8c), danger: color!(0xff5555), };
199
200 pub const NORD: Self = Self {
204 background: color!(0x2e3440), text: color!(0xeceff4), primary: color!(0x8fbcbb), success: color!(0xa3be8c), warning: color!(0xebcb8b), danger: color!(0xbf616a), };
211
212 pub const SOLARIZED_LIGHT: Self = Self {
216 background: color!(0xfdf6e3), text: color!(0x657b83), primary: color!(0x2aa198), success: color!(0x859900), warning: color!(0xb58900), danger: color!(0xdc322f), };
223
224 pub const SOLARIZED_DARK: Self = Self {
228 background: color!(0x002b36), text: color!(0x839496), primary: color!(0x2aa198), success: color!(0x859900), warning: color!(0xb58900), danger: color!(0xdc322f), };
235
236 pub const GRUVBOX_LIGHT: Self = Self {
240 background: color!(0xfbf1c7), text: color!(0x282828), primary: color!(0x458588), success: color!(0x98971a), warning: color!(0xd79921), danger: color!(0xcc241d), };
247
248 pub const GRUVBOX_DARK: Self = Self {
252 background: color!(0x282828), text: color!(0xfbf1c7), primary: color!(0x458588), success: color!(0x98971a), warning: color!(0xd79921), danger: color!(0xcc241d), };
259
260 pub const CATPPUCCIN_LATTE: Self = Self {
264 background: color!(0xeff1f5), text: color!(0x4c4f69), primary: color!(0x1e66f5), success: color!(0x40a02b), warning: color!(0xdf8e1d), danger: color!(0xd20f39), };
271
272 pub const CATPPUCCIN_FRAPPE: Self = Self {
276 background: color!(0x303446), text: color!(0xc6d0f5), primary: color!(0x8caaee), success: color!(0xa6d189), warning: color!(0xe5c890), danger: color!(0xe78284), };
283
284 pub const CATPPUCCIN_MACCHIATO: Self = Self {
288 background: color!(0x24273a), text: color!(0xcad3f5), primary: color!(0x8aadf4), success: color!(0xa6da95), warning: color!(0xeed49f), danger: color!(0xed8796), };
295
296 pub const CATPPUCCIN_MOCHA: Self = Self {
300 background: color!(0x1e1e2e), text: color!(0xcdd6f4), primary: color!(0x89b4fa), success: color!(0xa6e3a1), warning: color!(0xf9e2af), danger: color!(0xf38ba8), };
307
308 pub const TOKYO_NIGHT: Self = Self {
312 background: color!(0x1a1b26), text: color!(0x9aa5ce), primary: color!(0x2ac3de), success: color!(0x9ece6a), warning: color!(0xe0af68), danger: color!(0xf7768e), };
319
320 pub const TOKYO_NIGHT_STORM: Self = Self {
324 background: color!(0x24283b), text: color!(0x9aa5ce), primary: color!(0x2ac3de), success: color!(0x9ece6a), warning: color!(0xe0af68), danger: color!(0xf7768e), };
331
332 pub const TOKYO_NIGHT_LIGHT: Self = Self {
336 background: color!(0xd5d6db), text: color!(0x565a6e), primary: color!(0x166775), success: color!(0x485e30), warning: color!(0x8f5e15), danger: color!(0x8c4351), };
343
344 pub const KANAGAWA_WAVE: Self = Self {
348 background: color!(0x1f1f28), text: color!(0xDCD7BA), primary: color!(0x7FB4CA), success: color!(0x76946A), warning: color!(0xff9e3b), danger: color!(0xC34043), };
355
356 pub const KANAGAWA_DRAGON: Self = Self {
360 background: color!(0x181616), text: color!(0xc5c9c5), primary: color!(0x223249), success: color!(0x8a9a7b), warning: color!(0xff9e3b), danger: color!(0xc4746e), };
367
368 pub const KANAGAWA_LOTUS: Self = Self {
372 background: color!(0xf2ecbc), text: color!(0x545464), primary: color!(0x4d699b), success: color!(0x6f894e), warning: color!(0xe98a00), danger: color!(0xc84053), };
379
380 pub const MOONFLY: Self = Self {
384 background: color!(0x080808), text: color!(0xbdbdbd), primary: color!(0x80a0ff), success: color!(0x8cc85f), warning: color!(0xe3c78a), danger: color!(0xff5454), };
391
392 pub const NIGHTFLY: Self = Self {
396 background: color!(0x011627), text: color!(0xbdc1c6), primary: color!(0x82aaff), success: color!(0xa1cd5e), warning: color!(0xe3d18a), danger: color!(0xfc514e), };
403
404 pub const OXOCARBON: Self = Self {
408 background: color!(0x232323),
409 text: color!(0xd0d0d0),
410 primary: color!(0x00b4ff),
411 success: color!(0x00c15a),
412 warning: color!(0xbe95ff), danger: color!(0xf62d0f),
414 };
415
416 pub const FERRA: Self = Self {
420 background: color!(0x2b292d),
421 text: color!(0xfecdb2),
422 primary: color!(0xd1d1e0),
423 success: color!(0xb1b695),
424 warning: color!(0xf5d76e), danger: color!(0xe06b75),
426 };
427}
428
429pub static LIGHT: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::LIGHT));
431
432pub static DARK: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::DARK));
434
435pub static DRACULA: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::DRACULA));
437
438pub static NORD: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::NORD));
440
441pub static SOLARIZED_LIGHT: LazyLock<Palette> =
443 LazyLock::new(|| Palette::generate(Seed::SOLARIZED_LIGHT));
444
445pub static SOLARIZED_DARK: LazyLock<Palette> =
447 LazyLock::new(|| Palette::generate(Seed::SOLARIZED_DARK));
448
449pub static GRUVBOX_LIGHT: LazyLock<Palette> =
451 LazyLock::new(|| Palette::generate(Seed::GRUVBOX_LIGHT));
452
453pub static GRUVBOX_DARK: LazyLock<Palette> =
455 LazyLock::new(|| Palette::generate(Seed::GRUVBOX_DARK));
456
457pub static CATPPUCCIN_LATTE: LazyLock<Palette> =
459 LazyLock::new(|| Palette::generate(Seed::CATPPUCCIN_LATTE));
460
461pub static CATPPUCCIN_FRAPPE: LazyLock<Palette> =
463 LazyLock::new(|| Palette::generate(Seed::CATPPUCCIN_FRAPPE));
464
465pub static CATPPUCCIN_MACCHIATO: LazyLock<Palette> =
467 LazyLock::new(|| Palette::generate(Seed::CATPPUCCIN_MACCHIATO));
468
469pub static CATPPUCCIN_MOCHA: LazyLock<Palette> =
471 LazyLock::new(|| Palette::generate(Seed::CATPPUCCIN_MOCHA));
472
473pub static TOKYO_NIGHT: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::TOKYO_NIGHT));
475
476pub static TOKYO_NIGHT_STORM: LazyLock<Palette> =
478 LazyLock::new(|| Palette::generate(Seed::TOKYO_NIGHT_STORM));
479
480pub static TOKYO_NIGHT_LIGHT: LazyLock<Palette> =
482 LazyLock::new(|| Palette::generate(Seed::TOKYO_NIGHT_LIGHT));
483
484pub static KANAGAWA_WAVE: LazyLock<Palette> =
486 LazyLock::new(|| Palette::generate(Seed::KANAGAWA_WAVE));
487
488pub static KANAGAWA_DRAGON: LazyLock<Palette> =
490 LazyLock::new(|| Palette::generate(Seed::KANAGAWA_DRAGON));
491
492pub static KANAGAWA_LOTUS: LazyLock<Palette> =
494 LazyLock::new(|| Palette::generate(Seed::KANAGAWA_LOTUS));
495
496pub static MOONFLY: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::MOONFLY));
498
499pub static NIGHTFLY: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::NIGHTFLY));
501
502pub static OXOCARBON: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::OXOCARBON));
504
505pub static FERRA: LazyLock<Palette> = LazyLock::new(|| Palette::generate(Seed::FERRA));
507
508pub fn darken(color: Color, amount: f32) -> Color {
510 let mut oklch = color.into_oklch();
511
512 if oklch.c > 0.0 && oklch.c < (1.0 - oklch.l) / 2.0 {
514 oklch.c *= 1.0 + (0.2 / oklch.c).min(100.0) * amount;
516 }
517
518 oklch.l = if oklch.l - amount < 0.0 {
519 0.0
520 } else {
521 oklch.l - amount
522 };
523
524 Color::from_oklch(oklch)
525}
526
527pub fn lighten(color: Color, amount: f32) -> Color {
529 let mut oklch = color.into_oklch();
530
531 oklch.c *= 1.0 + 2.0 * amount / oklch.l.max(0.05);
534
535 oklch.l = if oklch.l + amount > 1.0 {
536 1.0
537 } else {
538 oklch.l + amount
539 };
540
541 Color::from_oklch(oklch)
542}
543
544pub fn deviate(color: Color, amount: f32) -> Color {
547 if is_dark(color) {
548 lighten(color, amount)
549 } else {
550 darken(color, amount)
551 }
552}
553
554pub fn readable(background: Color, text: Color) -> Color {
557 if text.is_readable_on(background) {
558 return text;
559 }
560
561 let improve = if is_dark(background) { lighten } else { darken };
562
563 let candidate = improve(text, 0.1);
565
566 if candidate.is_readable_on(background) {
567 return candidate;
568 }
569
570 let candidate = improve(text, 0.2);
571
572 if candidate.is_readable_on(background) {
573 return candidate;
574 }
575
576 let white_contrast = background.relative_contrast(Color::WHITE);
577 let black_contrast = background.relative_contrast(Color::BLACK);
578
579 if white_contrast >= black_contrast {
580 Color::WHITE.mix(background, 0.05)
581 } else {
582 Color::BLACK.mix(background, 0.05)
583 }
584}
585
586pub fn is_dark(color: Color) -> bool {
588 color.into_oklch().l < 0.6
589}