//! Candidate page-to-raised ramps for one theme, in both units the ramp //! decisions are argued in. //! //! `cargo run --example ramp_candidates -- makenotwork` //! //! Two units, and mixing them is how a ramp gets misread: `raised_is_distinct //! _from_page` asserts in **oklab L on 0 to 1** (0.05), while the theme files //! and the rulings quote **CIE L\* on 0 to 100** (6.7). They are not a factor //! of a hundred apart. Both are printed for every row. //! //! Two directions, because a ramp has two ends. Lifting `raised` runs it toward //! white on a light theme, and `bevel-light` is derived from `raised` by //! lightening -- so past a point the lit edge stops gaining while the shadowed //! one keeps going, and the bevel gets less symmetric rather than more legible. //! Dropping `page` buys the same separation with the headroom left alone. //! //! Prints; changes nothing. The edit it argues for is a hand edit to //! `[surface]`, and the look call is not this program's. #![allow(clippy::many_single_char_names)] use makeover::{Oklab, Rgb, SemanticTokens, ThemeColors}; /// CIE L\*, the unit the theme files quote. fn cie_l(c: Rgb) -> f32 { let lin = |v: u8| { let v = f32::from(v) / 255.0; if v <= 0.04045 { v / 12.92 } else { ((v + 0.055) / 1.055).powf(2.4) } }; let (r, g, b) = c.tuple(); let y = 0.212_672_9 * lin(r) + 0.715_152_2 * lin(g) + 0.072_175_0 * lin(b); let f = if y > 216.0 / 24389.0 { y.cbrt() } else { ((24389.0 / 27.0) * y + 16.0) / 116.0 }; 116.0 * f - 16.0 } /// The same color at a new oklab L, hue and chroma untouched. fn at_l(c: Rgb, l: f32) -> Rgb { Rgb::from_oklab(Oklab { l, ..c.to_oklab() }) } fn with_surfaces(theme: &ThemeColors, page: Rgb, raised: Rgb) -> SemanticTokens { let mut colors = theme.colors.clone(); colors.insert("surface.page".into(), page.to_hex()); colors.insert("surface.raised".into(), raised.to_hex()); // Equal to raised in every house theme: an overlay is a raised surface that // happens to float. if colors.contains_key("surface.overlay") { colors.insert("surface.overlay".into(), raised.to_hex()); } makeover::resolve(&ThemeColors { meta: theme.meta.clone(), colors, }) } fn row(label: &str, page: Rgb, raised: Rgb, tokens: &SemanticTokens) { let delta = raised.to_oklab().l - page.to_oklab().l; // A well is cut into the page, so a page that has dropped past `sunken` // leaves the theme claiming a recess that reads as a rise. The direction // that drops the page runs into this before it runs out of gamut. let inverted = tokens .hex("surface-sunken") .and_then(Rgb::from_hex) .is_some_and(|sunken| sunken.to_oklab().l >= page.to_oklab().l); println!( "{label:<22} page {} raised {} delta {:.3} oklab / {:.1} L* bevel {} .. {}{}", page.to_hex(), raised.to_hex(), delta, cie_l(raised) - cie_l(page), tokens.hex("bevel-light").unwrap_or("-"), tokens.hex("bevel-dark").unwrap_or("-"), if inverted { " SUNKEN IS NO LONGER BELOW THE PAGE" } else { "" }, ); } fn main() { let id = std::env::args() .nth(1) .unwrap_or_else(|| "makenotwork".into()); let dirs = vec![( makeover::bundled_themes_dir().expect("run me from a checkout"), false, )]; let theme = makeover::load_theme(&dirs, &id).expect("no such theme"); let hex = |key: &str| Rgb::from_hex(&theme.colors[key]).expect("hex"); let (page, raised) = (hex("surface.page"), hex("surface.raised")); println!("{id}, as shipped:"); row( " current", page, raised, &with_surfaces(&theme, page, raised), ); println!( "\nthe assertion's floor is 0.05 oklab; goingson sits at {:.3}, audiofiles at {:.3}\n", 0.119, 0.065 ); println!("lifting raised, page fixed:"); for delta in [0.070_f32, 0.085, 0.100, 0.119] { let candidate = at_l(raised, page.to_oklab().l + delta); let tokens = with_surfaces(&theme, page, candidate); row(&format!(" delta {delta:.3}"), page, candidate, &tokens); } println!("\ndropping page, raised fixed:"); for delta in [0.070_f32, 0.085, 0.100, 0.119] { let candidate = at_l(page, raised.to_oklab().l - delta); let tokens = with_surfaces(&theme, candidate, raised); row(&format!(" delta {delta:.3}"), candidate, raised, &tokens); } }