pub const BG_PRIMARY: &str = "#0a0a1a";
pub const BG_SECONDARY: &str = "#1a1a2e";
pub const BG_TERTIARY: &str = "#0f0f23";
pub const BORDER: &str = "#333333";
pub const TEXT_PRIMARY: &str = "#e0e0e0";
pub const TEXT_SECONDARY: &str = "#888888";
pub const ACCENT: &str = "#4ecdc4";
pub const ACCENT_WARN: &str = "#ffd93d";
pub const ACCENT_ERROR: &str = "#ff6b6b";
pub const PROBAR: &str = "#ff6b6b";
pub const CITY_NODE: &str = "#ffd93d";
pub const TOUR_PATH: &str = "#4ecdc4";
pub const FONT_MONO: &str = "'JetBrains Mono', 'Fira Code', monospace";
pub const FONT_SIZE_BASE: f64 = 0.75;
pub const FONT_SIZE_SMALL: f64 = 0.65;
#[must_use]
pub const fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
let bytes = hex.as_bytes();
let offset = if bytes[0] == b'#' { 1 } else { 0 };
let r = hex_byte(bytes[offset], bytes[offset + 1]);
let g = hex_byte(bytes[offset + 2], bytes[offset + 3]);
let b = hex_byte(bytes[offset + 4], bytes[offset + 5]);
(r, g, b)
}
const fn hex_byte(hi: u8, lo: u8) -> u8 {
hex_digit(hi) * 16 + hex_digit(lo)
}
const fn hex_digit(c: u8) -> u8 {
match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'f' => c - b'a' + 10,
b'A'..=b'F' => c - b'A' + 10,
_ => 0,
}
}
#[must_use]
pub fn css_variables() -> String {
format!(
r":root {{
--bg-primary: {BG_PRIMARY};
--bg-secondary: {BG_SECONDARY};
--bg-tertiary: {BG_TERTIARY};
--border: {BORDER};
--text-primary: {TEXT_PRIMARY};
--text-secondary: {TEXT_SECONDARY};
--accent: {ACCENT};
--accent-warn: {ACCENT_WARN};
--accent-error: {ACCENT_ERROR};
--probar: {PROBAR};
}}"
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hex_to_rgb_with_hash() {
assert_eq!(hex_to_rgb("#4ecdc4"), (78, 205, 196));
assert_eq!(hex_to_rgb("#ffd93d"), (255, 217, 61));
assert_eq!(hex_to_rgb("#ff6b6b"), (255, 107, 107));
}
#[test]
fn test_hex_to_rgb_bg_colors() {
assert_eq!(hex_to_rgb(BG_PRIMARY), (10, 10, 26));
assert_eq!(hex_to_rgb(BG_SECONDARY), (26, 26, 46));
}
#[test]
fn test_css_variables_contains_all_colors() {
let css = css_variables();
assert!(css.contains("--bg-primary"));
assert!(css.contains("--accent"));
assert!(css.contains("#4ecdc4"));
}
#[test]
fn test_colors_are_valid_hex() {
assert_eq!(BG_PRIMARY.len(), 7);
assert_eq!(ACCENT.len(), 7);
assert_eq!(ACCENT_WARN.len(), 7);
assert!(BG_PRIMARY.starts_with('#'));
}
#[test]
fn test_contrast_accessibility() {
let (tr, tg, tb) = hex_to_rgb(TEXT_PRIMARY);
let (br, bg, bb) = hex_to_rgb(BG_PRIMARY);
let text_lum = (f64::from(tr) + f64::from(tg) + f64::from(tb)) / 3.0 / 255.0;
let bg_lum = (f64::from(br) + f64::from(bg) + f64::from(bb)) / 3.0 / 255.0;
let ratio = (text_lum + 0.05) / (bg_lum + 0.05);
assert!(
ratio > 4.5,
"Text contrast ratio should exceed WCAG AA: {ratio}"
);
}
}