1use crate::{Color, color};
3
4use palette::color_difference::Wcag21RelativeContrast;
5use palette::rgb::Rgb;
6use palette::{FromColor, Hsl, Mix};
7
8use std::sync::LazyLock;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct Palette {
13 pub background: Color,
15 pub text: Color,
17 pub primary: Color,
19 pub success: Color,
21 pub warning: Color,
23 pub danger: Color,
25}
26
27impl Palette {
28 pub const LIGHT: Self = Self {
30 background: Color::WHITE,
31 text: Color::BLACK,
32 primary: color!(0x5865F2),
33 success: color!(0x12664f),
34 warning: color!(0xffc14e),
35 danger: color!(0xc3423f),
36 };
37
38 pub const DARK: Self = Self {
40 background: color!(0x2B2D31),
41 text: Color::from_rgb(0.90, 0.90, 0.90),
42 primary: color!(0x5865F2),
43 success: color!(0x12664f),
44 warning: color!(0xffc14e),
45 danger: color!(0xc3423f),
46 };
47
48 pub const DRACULA: Self = Self {
52 background: color!(0x282A36), text: color!(0xf8f8f2), primary: color!(0xbd93f9), success: color!(0x50fa7b), warning: color!(0xf1fa8c), danger: color!(0xff5555), };
59
60 pub const NORD: Self = Self {
64 background: color!(0x2e3440), text: color!(0xeceff4), primary: color!(0x8fbcbb), success: color!(0xa3be8c), warning: color!(0xebcb8b), danger: color!(0xbf616a), };
71
72 pub const SOLARIZED_LIGHT: Self = Self {
76 background: color!(0xfdf6e3), text: color!(0x657b83), primary: color!(0x2aa198), success: color!(0x859900), warning: color!(0xb58900), danger: color!(0xdc322f), };
83
84 pub const SOLARIZED_DARK: Self = Self {
88 background: color!(0x002b36), text: color!(0x839496), primary: color!(0x2aa198), success: color!(0x859900), warning: color!(0xb58900), danger: color!(0xdc322f), };
95
96 pub const GRUVBOX_LIGHT: Self = Self {
100 background: color!(0xfbf1c7), text: color!(0x282828), primary: color!(0x458588), success: color!(0x98971a), warning: color!(0xd79921), danger: color!(0xcc241d), };
107
108 pub const GRUVBOX_DARK: Self = Self {
112 background: color!(0x282828), text: color!(0xfbf1c7), primary: color!(0x458588), success: color!(0x98971a), warning: color!(0xd79921), danger: color!(0xcc241d), };
119
120 pub const CATPPUCCIN_LATTE: Self = Self {
124 background: color!(0xeff1f5), text: color!(0x4c4f69), primary: color!(0x1e66f5), success: color!(0x40a02b), warning: color!(0xdf8e1d), danger: color!(0xd20f39), };
131
132 pub const CATPPUCCIN_FRAPPE: Self = Self {
136 background: color!(0x303446), text: color!(0xc6d0f5), primary: color!(0x8caaee), success: color!(0xa6d189), warning: color!(0xe5c890), danger: color!(0xe78284), };
143
144 pub const CATPPUCCIN_MACCHIATO: Self = Self {
148 background: color!(0x24273a), text: color!(0xcad3f5), primary: color!(0x8aadf4), success: color!(0xa6da95), warning: color!(0xeed49f), danger: color!(0xed8796), };
155
156 pub const CATPPUCCIN_MOCHA: Self = Self {
160 background: color!(0x1e1e2e), text: color!(0xcdd6f4), primary: color!(0x89b4fa), success: color!(0xa6e3a1), warning: color!(0xf9e2af), danger: color!(0xf38ba8), };
167
168 pub const TOKYO_NIGHT: Self = Self {
172 background: color!(0x1a1b26), text: color!(0x9aa5ce), primary: color!(0x2ac3de), success: color!(0x9ece6a), warning: color!(0xe0af68), danger: color!(0xf7768e), };
179
180 pub const TOKYO_NIGHT_STORM: Self = Self {
184 background: color!(0x24283b), text: color!(0x9aa5ce), primary: color!(0x2ac3de), success: color!(0x9ece6a), warning: color!(0xe0af68), danger: color!(0xf7768e), };
191
192 pub const TOKYO_NIGHT_LIGHT: Self = Self {
196 background: color!(0xd5d6db), text: color!(0x565a6e), primary: color!(0x166775), success: color!(0x485e30), warning: color!(0x8f5e15), danger: color!(0x8c4351), };
203
204 pub const KANAGAWA_WAVE: Self = Self {
208 background: color!(0x1f1f28), text: color!(0xDCD7BA), primary: color!(0x7FB4CA), success: color!(0x76946A), warning: color!(0xff9e3b), danger: color!(0xC34043), };
215
216 pub const KANAGAWA_DRAGON: Self = Self {
220 background: color!(0x181616), text: color!(0xc5c9c5), primary: color!(0x223249), success: color!(0x8a9a7b), warning: color!(0xff9e3b), danger: color!(0xc4746e), };
227
228 pub const KANAGAWA_LOTUS: Self = Self {
232 background: color!(0xf2ecbc), text: color!(0x545464), primary: color!(0x4d699b), success: color!(0x6f894e), warning: color!(0xe98a00), danger: color!(0xc84053), };
239
240 pub const MOONFLY: Self = Self {
244 background: color!(0x080808), text: color!(0xbdbdbd), primary: color!(0x80a0ff), success: color!(0x8cc85f), warning: color!(0xe3c78a), danger: color!(0xff5454), };
251
252 pub const NIGHTFLY: Self = Self {
256 background: color!(0x011627), text: color!(0xbdc1c6), primary: color!(0x82aaff), success: color!(0xa1cd5e), warning: color!(0xe3d18a), danger: color!(0xfc514e), };
263
264 pub const OXOCARBON: Self = Self {
268 background: color!(0x232323),
269 text: color!(0xd0d0d0),
270 primary: color!(0x00b4ff),
271 success: color!(0x00c15a),
272 warning: color!(0xbe95ff), danger: color!(0xf62d0f),
274 };
275
276 pub const FERRA: Self = Self {
280 background: color!(0x2b292d),
281 text: color!(0xfecdb2),
282 primary: color!(0xd1d1e0),
283 success: color!(0xb1b695),
284 warning: color!(0xf5d76e), danger: color!(0xe06b75),
286 };
287}
288
289#[derive(Debug, Clone, Copy, PartialEq)]
291pub struct Extended {
292 pub background: Background,
294 pub primary: Primary,
296 pub secondary: Secondary,
298 pub success: Success,
300 pub warning: Warning,
302 pub danger: Danger,
304 pub is_dark: bool,
306}
307
308pub static EXTENDED_LIGHT: LazyLock<Extended> =
310 LazyLock::new(|| Extended::generate(Palette::LIGHT));
311
312pub static EXTENDED_DARK: LazyLock<Extended> =
314 LazyLock::new(|| Extended::generate(Palette::DARK));
315
316pub static EXTENDED_DRACULA: LazyLock<Extended> =
318 LazyLock::new(|| Extended::generate(Palette::DRACULA));
319
320pub static EXTENDED_NORD: LazyLock<Extended> =
322 LazyLock::new(|| Extended::generate(Palette::NORD));
323
324pub static EXTENDED_SOLARIZED_LIGHT: LazyLock<Extended> =
326 LazyLock::new(|| Extended::generate(Palette::SOLARIZED_LIGHT));
327
328pub static EXTENDED_SOLARIZED_DARK: LazyLock<Extended> =
330 LazyLock::new(|| Extended::generate(Palette::SOLARIZED_DARK));
331
332pub static EXTENDED_GRUVBOX_LIGHT: LazyLock<Extended> =
334 LazyLock::new(|| Extended::generate(Palette::GRUVBOX_LIGHT));
335
336pub static EXTENDED_GRUVBOX_DARK: LazyLock<Extended> =
338 LazyLock::new(|| Extended::generate(Palette::GRUVBOX_DARK));
339
340pub static EXTENDED_CATPPUCCIN_LATTE: LazyLock<Extended> =
342 LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE));
343
344pub static EXTENDED_CATPPUCCIN_FRAPPE: LazyLock<Extended> =
346 LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE));
347
348pub static EXTENDED_CATPPUCCIN_MACCHIATO: LazyLock<Extended> =
350 LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO));
351
352pub static EXTENDED_CATPPUCCIN_MOCHA: LazyLock<Extended> =
354 LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA));
355
356pub static EXTENDED_TOKYO_NIGHT: LazyLock<Extended> =
358 LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT));
359
360pub static EXTENDED_TOKYO_NIGHT_STORM: LazyLock<Extended> =
362 LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM));
363
364pub static EXTENDED_TOKYO_NIGHT_LIGHT: LazyLock<Extended> =
366 LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT));
367
368pub static EXTENDED_KANAGAWA_WAVE: LazyLock<Extended> =
370 LazyLock::new(|| Extended::generate(Palette::KANAGAWA_WAVE));
371
372pub static EXTENDED_KANAGAWA_DRAGON: LazyLock<Extended> =
374 LazyLock::new(|| Extended::generate(Palette::KANAGAWA_DRAGON));
375
376pub static EXTENDED_KANAGAWA_LOTUS: LazyLock<Extended> =
378 LazyLock::new(|| Extended::generate(Palette::KANAGAWA_LOTUS));
379
380pub static EXTENDED_MOONFLY: LazyLock<Extended> =
382 LazyLock::new(|| Extended::generate(Palette::MOONFLY));
383
384pub static EXTENDED_NIGHTFLY: LazyLock<Extended> =
386 LazyLock::new(|| Extended::generate(Palette::NIGHTFLY));
387
388pub static EXTENDED_OXOCARBON: LazyLock<Extended> =
390 LazyLock::new(|| Extended::generate(Palette::OXOCARBON));
391
392pub static EXTENDED_FERRA: LazyLock<Extended> =
394 LazyLock::new(|| Extended::generate(Palette::FERRA));
395
396impl Extended {
397 pub fn generate(palette: Palette) -> Self {
399 Self {
400 background: Background::new(palette.background, palette.text),
401 primary: Primary::generate(
402 palette.primary,
403 palette.background,
404 palette.text,
405 ),
406 secondary: Secondary::generate(palette.background, palette.text),
407 success: Success::generate(
408 palette.success,
409 palette.background,
410 palette.text,
411 ),
412 warning: Warning::generate(
413 palette.warning,
414 palette.background,
415 palette.text,
416 ),
417 danger: Danger::generate(
418 palette.danger,
419 palette.background,
420 palette.text,
421 ),
422 is_dark: is_dark(palette.background),
423 }
424 }
425}
426
427#[derive(Debug, Clone, Copy, PartialEq)]
429pub struct Pair {
430 pub color: Color,
432
433 pub text: Color,
439}
440
441impl Pair {
442 pub fn new(color: Color, text: Color) -> Self {
444 Self {
445 color,
446 text: readable(color, text),
447 }
448 }
449}
450
451#[derive(Debug, Clone, Copy, PartialEq)]
453pub struct Background {
454 pub base: Pair,
456 pub weakest: Pair,
458 pub weak: Pair,
460 pub strong: Pair,
462 pub strongest: Pair,
464}
465
466impl Background {
467 pub fn new(base: Color, text: Color) -> Self {
469 let weakest = deviate(base, 0.03);
470 let weak = muted(deviate(base, 0.1));
471 let strong = muted(deviate(base, 0.2));
472 let strongest = muted(deviate(base, 0.3));
473
474 Self {
475 base: Pair::new(base, text),
476 weakest: Pair::new(weakest, text),
477 weak: Pair::new(weak, text),
478 strong: Pair::new(strong, text),
479 strongest: Pair::new(strongest, text),
480 }
481 }
482}
483
484#[derive(Debug, Clone, Copy, PartialEq)]
486pub struct Primary {
487 pub base: Pair,
489 pub weak: Pair,
491 pub strong: Pair,
493}
494
495impl Primary {
496 pub fn generate(base: Color, background: Color, text: Color) -> Self {
498 let weak = mix(base, background, 0.4);
499 let strong = deviate(base, 0.1);
500
501 Self {
502 base: Pair::new(base, text),
503 weak: Pair::new(weak, text),
504 strong: Pair::new(strong, text),
505 }
506 }
507}
508
509#[derive(Debug, Clone, Copy, PartialEq)]
511pub struct Secondary {
512 pub base: Pair,
514 pub weak: Pair,
516 pub strong: Pair,
518}
519
520impl Secondary {
521 pub fn generate(base: Color, text: Color) -> Self {
523 let base = mix(base, text, 0.2);
524 let weak = mix(base, text, 0.1);
525 let strong = mix(base, text, 0.3);
526
527 Self {
528 base: Pair::new(base, text),
529 weak: Pair::new(weak, text),
530 strong: Pair::new(strong, text),
531 }
532 }
533}
534
535#[derive(Debug, Clone, Copy, PartialEq)]
537pub struct Success {
538 pub base: Pair,
540 pub weak: Pair,
542 pub strong: Pair,
544}
545
546impl Success {
547 pub fn generate(base: Color, background: Color, text: Color) -> Self {
549 let weak = mix(base, background, 0.4);
550 let strong = deviate(base, 0.1);
551
552 Self {
553 base: Pair::new(base, text),
554 weak: Pair::new(weak, text),
555 strong: Pair::new(strong, text),
556 }
557 }
558}
559
560#[derive(Debug, Clone, Copy, PartialEq)]
562pub struct Warning {
563 pub base: Pair,
565 pub weak: Pair,
567 pub strong: Pair,
569}
570
571impl Warning {
572 pub fn generate(base: Color, background: Color, text: Color) -> Self {
574 let weak = mix(base, background, 0.4);
575 let strong = deviate(base, 0.1);
576
577 Self {
578 base: Pair::new(base, text),
579 weak: Pair::new(weak, text),
580 strong: Pair::new(strong, text),
581 }
582 }
583}
584
585#[derive(Debug, Clone, Copy, PartialEq)]
587pub struct Danger {
588 pub base: Pair,
590 pub weak: Pair,
592 pub strong: Pair,
594}
595
596impl Danger {
597 pub fn generate(base: Color, background: Color, text: Color) -> Self {
599 let weak = mix(base, background, 0.4);
600 let strong = deviate(base, 0.1);
601
602 Self {
603 base: Pair::new(base, text),
604 weak: Pair::new(weak, text),
605 strong: Pair::new(strong, text),
606 }
607 }
608}
609
610fn darken(color: Color, amount: f32) -> Color {
611 let mut hsl = to_hsl(color);
612
613 hsl.lightness = if hsl.lightness - amount < 0.0 {
614 0.0
615 } else {
616 hsl.lightness - amount
617 };
618
619 from_hsl(hsl)
620}
621
622fn lighten(color: Color, amount: f32) -> Color {
623 let mut hsl = to_hsl(color);
624
625 hsl.lightness = if hsl.lightness + amount > 1.0 {
626 1.0
627 } else {
628 hsl.lightness + amount
629 };
630
631 from_hsl(hsl)
632}
633
634fn deviate(color: Color, amount: f32) -> Color {
635 if is_dark(color) {
636 lighten(color, amount)
637 } else {
638 darken(color, amount * 0.8)
639 }
640}
641
642fn muted(color: Color) -> Color {
643 let mut hsl = to_hsl(color);
644
645 hsl.saturation = hsl.saturation.min(0.5);
646
647 from_hsl(hsl)
648}
649
650fn mix(a: Color, b: Color, factor: f32) -> Color {
651 let a_lin = Rgb::from(a).into_linear();
652 let b_lin = Rgb::from(b).into_linear();
653
654 let mixed = a_lin.mix(b_lin, factor);
655 Rgb::from_linear(mixed).into()
656}
657
658fn readable(background: Color, text: Color) -> Color {
659 if is_readable(background, text) {
660 return text;
661 }
662
663 let improve = if is_dark(background) { lighten } else { darken };
664
665 let candidate = improve(text, 0.1);
667
668 if is_readable(background, candidate) {
669 return candidate;
670 }
671
672 let white_contrast = relative_contrast(background, Color::WHITE);
673 let black_contrast = relative_contrast(background, Color::BLACK);
674
675 if white_contrast >= black_contrast {
676 mix(Color::WHITE, background, 0.05)
677 } else {
678 mix(Color::BLACK, background, 0.05)
679 }
680}
681
682fn is_dark(color: Color) -> bool {
683 to_hsl(color).lightness < 0.6
684}
685
686fn is_readable(a: Color, b: Color) -> bool {
687 let a_srgb = Rgb::from(a);
688 let b_srgb = Rgb::from(b);
689
690 a_srgb.has_enhanced_contrast_text(b_srgb)
691}
692
693fn relative_contrast(a: Color, b: Color) -> f32 {
694 let a_srgb = Rgb::from(a);
695 let b_srgb = Rgb::from(b);
696
697 a_srgb.relative_contrast(b_srgb)
698}
699
700fn to_hsl(color: Color) -> Hsl {
701 Hsl::from_color(Rgb::from(color))
702}
703
704fn from_hsl(hsl: Hsl) -> Color {
705 Rgb::from_color(hsl).into()
706}