use crate::color::Color;
pub const VIVID_CHROMA_THRESHOLD: f64 = 0.16;
#[derive(Debug, Clone, Copy)]
pub struct VividSplit {
pub accent: Color,
pub surface: Color,
pub was_tamed: bool,
}
pub fn split_vivid_roles(brand: &Color) -> VividSplit {
if brand.c <= VIVID_CHROMA_THRESHOLD {
return VividSplit {
accent: *brand,
surface: *brand,
was_tamed: false,
};
}
let target_c = (VIVID_CHROMA_THRESHOLD * 0.75).min(brand.c);
let target_l = brand.l.clamp(0.35, 0.65);
let surface = Color::from_oklch(target_l, target_c, brand.h);
VividSplit {
accent: *brand,
surface,
was_tamed: true,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::hue_distance;
fn c(hex: &str) -> Color {
Color::from_hex(hex).unwrap()
}
#[test]
fn neon_lime_is_tamed_and_hue_is_preserved() {
let lime = c("#39ff14");
let split = split_vivid_roles(&lime);
assert!(split.was_tamed);
assert!(split.surface.c < split.accent.c);
let surface_roundtrip = Color::from_hex(&split.surface.to_hex()).unwrap();
let accent_roundtrip = Color::from_hex(&split.accent.to_hex()).unwrap();
let drift = hue_distance(surface_roundtrip.h, accent_roundtrip.h);
assert!(
drift < 5.0,
"hue drifted {drift}° after roundtrip ({} -> {})",
split.accent.to_hex(),
split.surface.to_hex()
);
}
#[test]
fn calm_blue_passes_through_untamed() {
let blue = c("#3f6089");
let split = split_vivid_roles(&blue);
assert!(!split.was_tamed);
assert_eq!(split.accent.to_hex(), split.surface.to_hex());
}
}