1use gpui::Hsla;
11use herogpui_core::{mix_oklab, oklch, soft_mix, with_alpha};
12
13pub fn white() -> Hsla {
19 oklch(1.0, 0.0, 0.0)
20}
21pub fn black() -> Hsla {
23 oklch(0.0, 0.0, 0.0)
24}
25pub fn snow() -> Hsla {
27 oklch(0.9911, 0.0, 0.0)
28}
29pub fn eclipse() -> Hsla {
31 oklch(0.2103, 0.0059, 285.89)
32}
33
34#[derive(Clone, Copy, Debug)]
36pub enum SoftForeground {
37 RoleForeground,
39 Mix { color: f32, foreground: f32 },
42}
43
44#[derive(Clone, Copy, Debug)]
49pub struct RoleColor {
50 pub color: Hsla,
52 pub foreground: Hsla,
54 pub hover_mix: f32,
57 soft_mix: f32,
59 soft_hover_mix: f32,
61 soft_foreground: SoftForeground,
63 vibrant_palette: bool,
69}
70
71impl RoleColor {
72 pub fn new(color: Hsla, foreground: Hsla) -> Self {
73 Self {
74 color,
75 foreground,
76 hover_mix: 0.10,
77 soft_mix: 0.15,
78 soft_hover_mix: 0.20,
79 soft_foreground: SoftForeground::Mix {
80 color: 70.0,
81 foreground: 30.0,
82 },
83 vibrant_palette: false,
84 }
85 }
86
87 pub fn with_hover_mix(mut self, hover_mix: f32) -> Self {
89 self.hover_mix = hover_mix;
90 self
91 }
92
93 pub fn with_soft_mix(mut self, soft: f32, soft_hover: f32) -> Self {
97 self.soft_mix = soft;
98 self.soft_hover_mix = soft_hover;
99 self
100 }
101
102 pub fn with_soft_foreground_mix(mut self, color: f32, foreground: f32) -> Self {
104 self.soft_foreground = SoftForeground::Mix { color, foreground };
105 self
106 }
107
108 pub fn with_vibrant_palette(mut self, vibrant: bool) -> Self {
112 self.vibrant_palette = vibrant;
113 self
114 }
115
116 pub fn is_vibrant_palette(&self) -> bool {
118 self.vibrant_palette
119 }
120
121 pub fn with_soft_foreground_role(mut self) -> Self {
123 self.soft_foreground = SoftForeground::RoleForeground;
124 self
125 }
126
127 pub fn hover(&self) -> Hsla {
129 mix_oklab(self.color, self.foreground, self.hover_mix)
130 }
131
132 pub fn soft(&self) -> Hsla {
134 soft_mix(self.color, self.soft_mix)
135 }
136
137 pub fn soft_hover(&self) -> Hsla {
139 soft_mix(self.color, self.soft_hover_mix)
140 }
141
142 pub fn soft_foreground(&self, page_foreground: Hsla) -> Hsla {
148 match self.soft_foreground {
149 SoftForeground::RoleForeground => self.foreground,
150 SoftForeground::Mix { .. } if self.vibrant_palette => {
153 mix_oklab(self.color, page_foreground, 8.0 / 100.0)
154 }
155 SoftForeground::Mix { color, foreground } => {
156 mix_oklab(
160 self.color,
161 page_foreground,
162 foreground / (color + foreground),
163 )
164 }
165 }
166 }
167
168 pub fn with_alpha(&self, alpha: f32) -> Hsla {
169 with_alpha(self.color, alpha)
170 }
171}
172
173#[derive(Clone, Copy, Debug)]
175pub struct SurfaceColor {
176 pub background: Hsla,
177 pub foreground: Hsla,
178}
179
180impl SurfaceColor {
181 pub fn hover(&self) -> Hsla {
183 mix_oklab(self.background, self.foreground, 0.08)
184 }
185}
186
187#[derive(Clone, Copy, Debug)]
190pub struct FieldColors {
191 pub background: Hsla,
193 pub foreground: Hsla,
195 pub placeholder: Hsla,
197 pub border: Hsla,
199}
200
201impl FieldColors {
202 pub fn hover(&self) -> Hsla {
206 mix_oklab(self.background, self.foreground, 2.0 / 92.0)
207 }
208
209 pub fn focus(&self) -> Hsla {
211 self.background
212 }
213
214 pub fn border_hover(&self) -> Hsla {
220 mix_oklab(self.border, self.foreground, 10.0 / 98.0)
221 }
222
223 pub fn border_focus(&self) -> Hsla {
225 mix_oklab(self.border, self.foreground, 22.0 / 96.0)
226 }
227}
228
229#[derive(Clone, Debug)]
231pub struct ThemeColors {
232 pub background: Hsla,
235 pub foreground: Hsla,
237 pub muted: Hsla,
239 pub scrollbar: Hsla,
241
242 pub surface: SurfaceColor,
246 pub surface_secondary: Hsla,
248 pub surface_tertiary: Hsla,
250 pub overlay: SurfaceColor,
253 pub segment: SurfaceColor,
256
257 pub default: RoleColor,
260 pub accent: RoleColor,
262 pub success: RoleColor,
264 pub warning: RoleColor,
266 pub danger: RoleColor,
268
269 pub field: FieldColors,
271
272 pub border: Hsla,
275 pub separator: Hsla,
277 pub focus: Hsla,
279 pub link: Hsla,
281 pub backdrop: Hsla,
283}
284
285impl ThemeColors {
286 pub fn vibrant_palette(&self) -> bool {
291 self.accent.is_vibrant_palette()
292 }
293
294 pub fn set_vibrant_palette(&mut self, vibrant: bool) {
298 self.accent = self.accent.with_vibrant_palette(vibrant);
299 self.success = self.success.with_vibrant_palette(vibrant);
300 self.warning = self.warning.with_vibrant_palette(vibrant);
301 self.danger = self.danger.with_vibrant_palette(vibrant);
302 }
303
304 pub fn background_secondary(&self) -> Hsla {
308 mix_oklab(self.background, self.foreground, 0.04)
309 }
310
311 pub fn background_tertiary(&self) -> Hsla {
313 mix_oklab(self.background, self.foreground, 0.08)
314 }
315
316 pub fn background_inverse(&self) -> Hsla {
318 self.foreground
319 }
320
321 pub fn separator_secondary(&self) -> Hsla {
325 mix_oklab(self.surface.background, self.surface.foreground, 0.15)
326 }
327
328 pub fn separator_tertiary(&self) -> Hsla {
330 mix_oklab(self.surface.background, self.surface.foreground, 0.19)
331 }
332
333 pub fn border_secondary(&self) -> Hsla {
337 mix_oklab(self.surface.background, self.surface.foreground, 0.22)
338 }
339
340 pub fn border_tertiary(&self) -> Hsla {
342 mix_oklab(self.surface.background, self.surface.foreground, 0.34)
343 }
344
345 pub fn surface_secondary_foreground(&self) -> Hsla {
351 self.foreground
352 }
353
354 pub fn surface_tertiary_foreground(&self) -> Hsla {
356 self.foreground
357 }
358
359 pub fn role(&self, name: &str) -> &RoleColor {
361 match name {
362 "default" => &self.default,
363 "success" => &self.success,
364 "warning" => &self.warning,
365 "danger" => &self.danger,
366 _ => &self.accent,
367 }
368 }
369
370 pub fn light() -> Self {
373 let foreground = eclipse();
374 let muted = oklch(0.5517, 0.0138, 285.94);
375 let accent = RoleColor::new(oklch(0.6204, 0.195, 253.83), snow())
376 .with_soft_mix(0.15, 0.20)
377 .with_soft_foreground_mix(70.0, 30.0);
378 Self {
379 background: oklch(0.9702, 0.0, 0.0),
380 foreground,
381 muted,
382 scrollbar: with_alpha(foreground, 0.15),
383
384 surface: SurfaceColor {
385 background: white(),
386 foreground,
387 },
388 surface_secondary: oklch(0.9524, 0.0013, 286.37),
389 surface_tertiary: oklch(0.9373, 0.0013, 286.37),
390 overlay: SurfaceColor {
391 background: white(),
392 foreground,
393 },
394 segment: SurfaceColor {
395 background: white(),
396 foreground: eclipse(),
397 },
398
399 default: RoleColor::new(oklch(0.94, 0.001, 286.375), eclipse())
400 .with_hover_mix(0.04)
401 .with_soft_mix(0.50, 0.60)
402 .with_soft_foreground_role(),
403 accent,
404 success: RoleColor::new(oklch(0.7329, 0.1935, 150.81), eclipse())
405 .with_soft_foreground_mix(80.0, 60.0),
406 warning: RoleColor::new(oklch(0.7819, 0.1585, 72.33), eclipse())
407 .with_soft_foreground_mix(80.0, 70.0),
408 danger: RoleColor::new(oklch(0.6532, 0.2328, 25.74), snow())
409 .with_soft_foreground_mix(70.0, 40.0),
410
411 field: FieldColors {
412 background: white(),
413 foreground: oklch(0.2103, 0.0059, 285.89),
414 placeholder: muted,
415 border: with_alpha(black(), 0.0),
416 },
417
418 border: oklch(0.9, 0.004, 286.32),
421 separator: oklch(0.92, 0.004, 286.32),
422 focus: accent.color,
423 link: foreground,
424 backdrop: with_alpha(black(), 0.5),
425 }
426 }
427
428 pub fn dark() -> Self {
431 let foreground = snow();
432 let muted = oklch(0.705, 0.015, 286.067);
433 let accent = RoleColor::new(oklch(0.6204, 0.195, 253.83), snow())
434 .with_soft_mix(0.12, 0.16)
435 .with_soft_foreground_mix(80.0, 30.0);
436 let default = RoleColor::new(oklch(0.274, 0.006, 286.033), snow())
437 .with_hover_mix(0.04)
438 .with_soft_mix(0.50, 0.60)
439 .with_soft_foreground_role();
440 Self {
441 background: oklch(0.12, 0.005, 285.823),
442 foreground,
443 muted,
444 scrollbar: with_alpha(foreground, 0.15),
445
446 surface: SurfaceColor {
447 background: oklch(0.2103, 0.0059, 285.89),
448 foreground,
449 },
450 surface_secondary: oklch(0.257, 0.0037, 286.14),
451 surface_tertiary: oklch(0.2721, 0.0024, 247.91),
452 overlay: SurfaceColor {
457 background: oklch(0.2103, 0.0059, 285.89),
458 foreground,
459 },
460 segment: SurfaceColor {
461 background: oklch(0.3964, 0.01, 285.93),
462 foreground,
463 },
464
465 default,
466 accent,
467 success: RoleColor::new(oklch(0.7329, 0.1935, 150.81), eclipse())
470 .with_soft_mix(0.12, 0.16)
471 .with_soft_foreground_mix(80.0, 30.0),
472 warning: RoleColor::new(oklch(0.8203, 0.1388, 76.34), eclipse())
473 .with_soft_mix(0.12, 0.16)
474 .with_soft_foreground_mix(80.0, 30.0),
475 danger: RoleColor::new(oklch(0.594, 0.1967, 24.63), snow())
476 .with_soft_foreground_mix(80.0, 30.0),
477
478 field: FieldColors {
479 background: oklch(0.2103, 0.0059, 285.89),
482 foreground,
483 placeholder: muted,
484 border: with_alpha(black(), 0.0),
485 },
486
487 border: oklch(0.28, 0.006, 286.033),
490 separator: oklch(0.25, 0.006, 286.033),
491 focus: accent.color,
492 link: foreground,
493 backdrop: with_alpha(black(), 0.6),
494 }
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 fn rgb8(c: Hsla) -> (u8, u8, u8) {
503 let r = gpui::Rgba::from(c);
504 (
505 (r.r * 255.0).round() as u8,
506 (r.g * 255.0).round() as u8,
507 (r.b * 255.0).round() as u8,
508 )
509 }
510
511 #[test]
512 fn accent_soft_is_fifteen_percent_accent() {
513 let c = ThemeColors::light();
514 assert!((c.accent.soft().a - 0.15).abs() < 1e-4);
515 assert!((c.accent.soft_hover().a - 0.20).abs() < 1e-4);
516 }
517
518 #[test]
519 fn light_scrollbar_thumb_is_fifteen_percent_foreground() {
520 let c = ThemeColors::light();
521 assert_eq!(c.scrollbar, with_alpha(c.foreground, 0.15));
522 }
523
524 #[test]
525 fn dark_scrollbar_thumb_is_fifteen_percent_foreground() {
526 let c = ThemeColors::dark();
527 assert_eq!(c.scrollbar, with_alpha(c.foreground, 0.15));
528 }
529
530 #[test]
531 fn the_soft_matrix_matches_the_pinned_stylesheet() {
532 let light = ThemeColors::light();
536 let dark = ThemeColors::dark();
537 for (colors, role_name, soft, soft_hover, foreground) in [
538 (&light, "default", 0.50, 0.60, None),
539 (&light, "accent", 0.15, 0.20, Some((70.0, 30.0))),
540 (&light, "success", 0.15, 0.20, Some((80.0, 60.0))),
541 (&light, "warning", 0.15, 0.20, Some((80.0, 70.0))),
542 (&light, "danger", 0.15, 0.20, Some((70.0, 40.0))),
543 (&dark, "default", 0.50, 0.60, None),
544 (&dark, "accent", 0.12, 0.16, Some((80.0, 30.0))),
545 (&dark, "success", 0.12, 0.16, Some((80.0, 30.0))),
546 (&dark, "warning", 0.12, 0.16, Some((80.0, 30.0))),
547 (&dark, "danger", 0.15, 0.20, Some((80.0, 30.0))),
548 ] {
549 let role = colors.role(role_name);
550 assert!(
551 (role.soft().a - soft).abs() < 1e-4,
552 "{role_name} soft alpha"
553 );
554 assert!(
555 (role.soft_hover().a - soft_hover).abs() < 1e-4,
556 "{role_name} soft-hover alpha"
557 );
558 match foreground {
559 Some((color, page)) => assert_eq!(
560 role.soft_foreground(colors.foreground),
561 mix_oklab(role.color, colors.foreground, page / (color + page)),
562 "{role_name} soft-foreground mix"
563 ),
564 None => assert_eq!(
565 role.soft_foreground(colors.foreground),
566 role.foreground,
567 "{role_name} soft-foreground follows the role"
568 ),
569 }
570 }
571 }
572
573 #[test]
578 fn every_token_resolves_to_the_stylesheets_own_pixels() {
579 type Pick = fn(&ThemeColors) -> Hsla;
580 type Row = (&'static str, Pick, (u8, u8, u8, f32), (u8, u8, u8, f32));
582 #[rustfmt::skip]
583 let table: &[Row] = &[
584 ("accent", |c| c.accent.color, (4, 133, 247, 1.00), (4, 133, 247, 1.00)),
585 ("accent_foreground", |c| c.accent.foreground, (252, 252, 252, 1.00), (252, 252, 252, 1.00)),
586 ("accent_hover", |c| c.accent.hover(), (53, 146, 249, 1.00), (53, 146, 249, 1.00)),
587 ("accent_soft", |c| c.accent.soft(), (4, 133, 247, 0.15), (4, 133, 247, 0.12)),
588 ("accent_soft_foreground", |c| c.accent.soft_foreground(c.foreground), (30, 99, 174, 1.00), (97, 168, 251, 1.00)),
589 ("backdrop", |c| c.backdrop, (0, 0, 0, 0.50), (0, 0, 0, 0.60)),
590 ("background", |c| c.background, (245, 245, 245, 1.00), (6, 6, 7, 1.00)),
591 ("background_inverse", |c| c.background_inverse(), (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
592 ("background_secondary", |c| c.background_secondary(), (235, 235, 235, 1.00), (12, 12, 14, 1.00)),
593 ("background_tertiary", |c| c.background_tertiary(), (225, 225, 225, 1.00), (19, 19, 22, 1.00)),
594 ("border", |c| c.border, (221, 222, 224, 1.00), (40, 40, 44, 1.00)),
595 ("border_secondary", |c| c.border_secondary(), (198, 198, 199, 1.00), (67, 67, 69, 1.00)),
596 ("border_tertiary", |c| c.border_tertiary(), (168, 168, 169, 1.00), (92, 92, 95, 1.00)),
597 ("danger", |c| c.danger.color, (255, 56, 60, 1.00), (219, 59, 62, 1.00)),
598 ("danger_foreground", |c| c.danger.foreground, (252, 252, 252, 1.00), (252, 252, 252, 1.00)),
599 ("danger_hover", |c| c.danger.hover(), (255, 85, 81, 1.00), (225, 84, 81, 1.00)),
600 ("danger_soft", |c| c.danger.soft(), (255, 56, 60, 0.15), (219, 59, 62, 0.15)),
601 ("danger_soft_foreground", |c| c.danger.soft_foreground(c.foreground), (164, 53, 51, 1.00), (235, 120, 114, 1.00)),
602 ("default", |c| c.default.color, (235, 235, 236, 1.00), (39, 39, 42, 1.00)),
603 ("default_foreground", |c| c.default.foreground, (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
604 ("default_hover", |c| c.default.hover(), (225, 225, 226, 1.00), (46, 46, 49, 1.00)),
605 ("default_soft", |c| c.default.soft(), (235, 235, 235, 0.50), (39, 39, 42, 0.50)),
606 ("default_soft_foreground", |c| c.default.soft_foreground(c.foreground), (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
607 ("field_background", |c| c.field.background, (255, 255, 255, 1.00), (24, 24, 27, 1.00)),
608 ("field_foreground", |c| c.field.foreground, (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
609 ("field_placeholder", |c| c.field.placeholder, (113, 113, 122, 1.00), (159, 159, 169, 1.00)),
610 ("focus", |c| c.focus, (4, 133, 247, 1.00), (4, 133, 247, 1.00)),
611 ("foreground", |c| c.foreground, (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
612 ("link", |c| c.link, (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
613 ("muted", |c| c.muted, (113, 113, 122, 1.00), (159, 159, 169, 1.00)),
614 ("overlay", |c| c.overlay.background, (255, 255, 255, 1.00), (24, 24, 27, 1.00)),
615 ("overlay_foreground", |c| c.overlay.foreground, (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
616 ("scrollbar", |c| c.scrollbar, (24, 24, 27, 0.15), (255, 255, 255, 0.15)),
617 ("segment", |c| c.segment.background, (255, 255, 255, 1.00), (70, 70, 76, 1.00)),
618 ("segment_foreground", |c| c.segment.foreground, (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
619 ("separator", |c| c.separator, (228, 228, 231, 1.00), (33, 33, 36, 1.00)),
620 ("separator_secondary", |c| c.separator_secondary(), (216, 216, 216, 1.00), (52, 52, 55, 1.00)),
621 ("separator_tertiary", |c| c.separator_tertiary(), (205, 205, 206, 1.00), (60, 60, 63, 1.00)),
622 ("success", |c| c.success.color, (23, 201, 100, 1.00), (23, 201, 100, 1.00)),
623 ("success_foreground", |c| c.success.foreground, (24, 24, 27, 1.00), (24, 24, 27, 1.00)),
624 ("success_hover", |c| c.success.hover(), (33, 181, 93, 1.00), (33, 181, 93, 1.00)),
625 ("success_soft", |c| c.success.soft(), (23, 201, 100, 0.15), (23, 201, 100, 0.12)),
626 ("success_soft_foreground", |c| c.success.soft_foreground(c.foreground), (43, 119, 69, 1.00), (116, 216, 143, 1.00)),
627 ("surface", |c| c.surface.background, (255, 255, 255, 1.00), (24, 24, 27, 1.00)),
628 ("surface_foreground", |c| c.surface.foreground, (24, 24, 27, 1.00), (252, 252, 252, 1.00)),
629 ("surface_hover", |c| c.surface.hover(), (234, 234, 234, 1.00), (39, 39, 42, 1.00)),
630 ("surface_secondary", |c| c.surface_secondary, (239, 239, 240, 1.00), (35, 35, 37, 1.00)),
631 ("surface_tertiary", |c| c.surface_tertiary, (234, 234, 235, 1.00), (38, 39, 40, 1.00)),
632 ("warning", |c| c.warning.color, (245, 165, 36, 1.00), (247, 183, 80, 1.00)),
633 ("warning_foreground", |c| c.warning.foreground, (24, 24, 27, 1.00), (24, 24, 27, 1.00)),
634 ("warning_hover", |c| c.warning.hover(), (220, 150, 42, 1.00), (222, 165, 76, 1.00)),
635 ("warning_soft", |c| c.warning.soft(), (245, 165, 36, 0.15), (247, 183, 80, 0.12)),
636 ("warning_soft_foreground", |c| c.warning.soft_foreground(c.foreground), (133, 95, 46, 1.00), (249, 203, 134, 1.00)),
637 ];
638 for (name, pick, light, dark) in table {
639 for (mode, c, want) in [
640 ("light", ThemeColors::light(), light),
641 ("dark", ThemeColors::dark(), dark),
642 ] {
643 let got = pick(&c);
644 let (r, g, b) = rgb8(got);
645 let slack = (2.0 / want.3).ceil() as i32;
648 let near = |a: u8, b: u8| (a as i32 - b as i32).abs() <= slack;
649 assert!(
650 near(r, want.0) && near(g, want.1) && near(b, want.2)
651 && (got.a - want.3).abs() < 0.011,
652 "{name} ({mode}) resolved to ({r}, {g}, {b}, {:.3}), upstream resolves ({}, {}, {}, {:.3})",
653 got.a, want.0, want.1, want.2, want.3
654 );
655 }
656 }
657 }
658
659 #[test]
660 fn soft_foreground_resolves_to_the_stylesheets_own_pixels() {
661 let c = ThemeColors::light();
665 for (role_name, expected) in [
666 ("accent", (30, 99, 174)),
667 ("success", (43, 119, 69)),
668 ("warning", (133, 95, 46)),
669 ("danger", (164, 53, 51)),
670 ] {
671 let got = rgb8(c.role(role_name).soft_foreground(c.foreground));
672 let near = |a: u8, b: u8| (a as i32 - b as i32).abs() <= 2;
673 assert!(
674 near(got.0, expected.0) && near(got.1, expected.1) && near(got.2, expected.2),
675 "{role_name} soft-foreground was {got:?}, expected {expected:?}"
676 );
677 }
678 }
679
680 #[test]
681 fn a_custom_foreground_stays_live_in_the_soft_foreground_mix() {
682 let base = ThemeColors::light();
683 let custom = ThemeColors {
684 foreground: oklch(0.30, 0.05, 120.0),
685 ..base
686 };
687 assert_eq!(
690 custom.accent.soft_foreground(custom.foreground),
691 mix_oklab(custom.accent.color, custom.foreground, 0.30)
692 );
693 assert_ne!(
694 custom.accent.soft_foreground(custom.foreground),
695 base.accent.soft_foreground(base.foreground)
696 );
697 assert_eq!(
700 custom.default.soft_foreground(custom.foreground),
701 custom.default.foreground
702 );
703 }
704
705 #[test]
706 fn the_derived_borders_step_away_from_the_surface() {
707 let c = ThemeColors::light();
708 let steps = [
712 c.separator_secondary(),
713 c.border_secondary(),
714 c.border_tertiary(),
715 ];
716 for pair in steps.windows(2) {
717 assert!(
718 pair[1].l < pair[0].l,
719 "each step is darker than the last on a light surface"
720 );
721 }
722 }
723
724 #[test]
725 fn a_secondary_surface_keeps_the_page_foreground() {
726 let c = ThemeColors::light();
727 assert_eq!(c.surface_secondary_foreground(), c.foreground);
728 assert_eq!(c.surface_tertiary_foreground(), c.foreground);
729 }
730
731 #[test]
732 fn a_field_border_mixes_toward_its_own_foreground() {
733 let c = ThemeColors::light();
734 let border = c.field.border;
737 assert_ne!(c.field.border_hover(), border);
738 assert_ne!(c.field.border_focus(), c.field.border_hover());
739 }
740
741 #[test]
742 fn field_focus_matches_field_background() {
743 let c = ThemeColors::light();
744 assert_eq!(c.field.focus(), c.field.background);
745 }
746
747 #[test]
748 #[ignore = "v3 gives dark mode one colour for both; the shadow separates them"]
749 fn dark_surface_is_darker_than_overlay() {
750 let c = ThemeColors::dark();
751 assert!(c.surface.background.l < c.overlay.background.l);
752 }
753
754 #[test]
755 fn light_background_is_lighter_than_its_derived_levels() {
756 let c = ThemeColors::light();
757 assert!(c.background.l > c.background_secondary().l);
758 assert!(c.background_secondary().l > c.background_tertiary().l);
759 }
760}