use crate::color::Color;
use crate::contrast::{contrast_ratio, AA_NON_TEXT, DARK_BG, LIGHT_BG};
#[derive(Debug, Clone, Copy)]
pub struct AdaptiveBrand {
pub light: Color,
pub dark: Color,
pub light_adjusted: bool,
pub dark_adjusted: bool,
pub light_clears_aa: bool,
pub dark_clears_aa: bool,
}
pub fn adaptive_brand(brand: &Color) -> AdaptiveBrand {
let light_bg = Color::from_hex(LIGHT_BG).expect("constant");
let dark_bg = Color::from_hex(DARK_BG).expect("constant");
let (light, light_adjusted, light_clears_aa) =
adjust_for_bg(brand, &light_bg, false);
let (dark, dark_adjusted, dark_clears_aa) =
adjust_for_bg(brand, &dark_bg, true);
AdaptiveBrand {
light,
dark,
light_adjusted,
dark_adjusted,
light_clears_aa,
dark_clears_aa,
}
}
fn adjust_for_bg(color: &Color, bg: &Color, lighten: bool) -> (Color, bool, bool) {
if contrast_ratio(bg, color) >= AA_NON_TEXT {
return (*color, false, true);
}
let mut c = *color;
let mut cleared = false;
for _ in 0..20 {
let new_l = if lighten {
(c.l + 0.05).min(1.0)
} else {
(c.l - 0.05).max(0.0)
};
if (new_l - c.l).abs() < f64::EPSILON {
break;
}
c = Color::from_oklch(new_l, c.c, c.h);
if contrast_ratio(bg, &c) >= AA_NON_TEXT {
cleared = true;
break;
}
}
(c, true, cleared)
}
#[cfg(test)]
mod tests {
use super::*;
fn c(hex: &str) -> Color {
Color::from_hex(hex).unwrap()
}
#[test]
fn very_dark_navy_lightens_for_dark_mode_only() {
let navy = c("#0a1a2e");
let a = adaptive_brand(&navy);
assert!(!a.light_adjusted, "should pass against white");
assert!(a.dark_adjusted, "should fail against dark bg");
assert!(a.dark_clears_aa, "should clear AA after lightening");
assert!(a.dark.l > navy.l, "dark variant should be lighter");
}
#[test]
fn mid_tone_passes_both_modes_unchanged() {
let mid = c("#0d9488");
let a = adaptive_brand(&mid);
assert!(!a.light_adjusted);
assert!(!a.dark_adjusted);
assert!(a.light_clears_aa);
assert!(a.dark_clears_aa);
assert_eq!(a.light.to_hex(), mid.to_hex());
assert_eq!(a.dark.to_hex(), mid.to_hex());
}
#[test]
fn adjustment_flag_separates_attempted_from_succeeded() {
let navy = c("#0a1a2e");
let a = adaptive_brand(&navy);
assert!(a.dark_adjusted && a.dark_clears_aa);
}
}