max / makeover
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01DwpiantpUgohzML4xr6KeQ
1 file changed,
+126 insertions,
-0 deletions
| @@ -1,0 +1,126 @@ | |||
| 1 | + | //! Candidate page-to-raised ramps for one theme, in both units the ramp | |
| 2 | + | //! decisions are argued in. | |
| 3 | + | //! | |
| 4 | + | //! `cargo run --example ramp_candidates -- makenotwork` | |
| 5 | + | //! | |
| 6 | + | //! Two units, and mixing them is how a ramp gets misread: `raised_is_distinct | |
| 7 | + | //! _from_page` asserts in **oklab L on 0 to 1** (0.05), while the theme files | |
| 8 | + | //! and the rulings quote **CIE L\* on 0 to 100** (6.7). They are not a factor | |
| 9 | + | //! of a hundred apart. Both are printed for every row. | |
| 10 | + | //! | |
| 11 | + | //! Two directions, because a ramp has two ends. Lifting `raised` runs it toward | |
| 12 | + | //! white on a light theme, and `bevel-light` is derived from `raised` by | |
| 13 | + | //! lightening -- so past a point the lit edge stops gaining while the shadowed | |
| 14 | + | //! one keeps going, and the bevel gets less symmetric rather than more legible. | |
| 15 | + | //! Dropping `page` buys the same separation with the headroom left alone. | |
| 16 | + | //! | |
| 17 | + | //! Prints; changes nothing. The edit it argues for is a hand edit to | |
| 18 | + | //! `[surface]`, and the look call is not this program's. | |
| 19 | + | ||
| 20 | + | #![allow(clippy::many_single_char_names)] | |
| 21 | + | ||
| 22 | + | use makeover::{Oklab, Rgb, SemanticTokens, ThemeColors}; | |
| 23 | + | ||
| 24 | + | /// CIE L\*, the unit the theme files quote. | |
| 25 | + | fn cie_l(c: Rgb) -> f32 { | |
| 26 | + | let lin = |v: u8| { | |
| 27 | + | let v = f32::from(v) / 255.0; | |
| 28 | + | if v <= 0.04045 { | |
| 29 | + | v / 12.92 | |
| 30 | + | } else { | |
| 31 | + | ((v + 0.055) / 1.055).powf(2.4) | |
| 32 | + | } | |
| 33 | + | }; | |
| 34 | + | let (r, g, b) = c.tuple(); | |
| 35 | + | let y = 0.212_672_9 * lin(r) + 0.715_152_2 * lin(g) + 0.072_175_0 * lin(b); | |
| 36 | + | let f = if y > 216.0 / 24389.0 { | |
| 37 | + | y.cbrt() | |
| 38 | + | } else { | |
| 39 | + | ((24389.0 / 27.0) * y + 16.0) / 116.0 | |
| 40 | + | }; | |
| 41 | + | 116.0 * f - 16.0 | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | /// The same color at a new oklab L, hue and chroma untouched. | |
| 45 | + | fn at_l(c: Rgb, l: f32) -> Rgb { | |
| 46 | + | Rgb::from_oklab(Oklab { l, ..c.to_oklab() }) | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | fn with_surfaces(theme: &ThemeColors, page: Rgb, raised: Rgb) -> SemanticTokens { | |
| 50 | + | let mut colors = theme.colors.clone(); | |
| 51 | + | colors.insert("surface.page".into(), page.to_hex()); | |
| 52 | + | colors.insert("surface.raised".into(), raised.to_hex()); | |
| 53 | + | // Equal to raised in every house theme: an overlay is a raised surface that | |
| 54 | + | // happens to float. | |
| 55 | + | if colors.contains_key("surface.overlay") { | |
| 56 | + | colors.insert("surface.overlay".into(), raised.to_hex()); | |
| 57 | + | } | |
| 58 | + | makeover::resolve(&ThemeColors { | |
| 59 | + | meta: theme.meta.clone(), | |
| 60 | + | colors, | |
| 61 | + | }) | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | fn row(label: &str, page: Rgb, raised: Rgb, tokens: &SemanticTokens) { | |
| 65 | + | let delta = raised.to_oklab().l - page.to_oklab().l; | |
| 66 | + | // A well is cut into the page, so a page that has dropped past `sunken` | |
| 67 | + | // leaves the theme claiming a recess that reads as a rise. The direction | |
| 68 | + | // that drops the page runs into this before it runs out of gamut. | |
| 69 | + | let inverted = tokens | |
| 70 | + | .hex("surface-sunken") | |
| 71 | + | .and_then(Rgb::from_hex) | |
| 72 | + | .is_some_and(|sunken| sunken.to_oklab().l >= page.to_oklab().l); | |
| 73 | + | println!( | |
| 74 | + | "{label:<22} page {} raised {} delta {:.3} oklab / {:.1} L* bevel {} .. {}{}", | |
| 75 | + | page.to_hex(), | |
| 76 | + | raised.to_hex(), | |
| 77 | + | delta, | |
| 78 | + | cie_l(raised) - cie_l(page), | |
| 79 | + | tokens.hex("bevel-light").unwrap_or("-"), | |
| 80 | + | tokens.hex("bevel-dark").unwrap_or("-"), | |
| 81 | + | if inverted { | |
| 82 | + | " SUNKEN IS NO LONGER BELOW THE PAGE" | |
| 83 | + | } else { | |
| 84 | + | "" | |
| 85 | + | }, | |
| 86 | + | ); | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | fn main() { | |
| 90 | + | let id = std::env::args() | |
| 91 | + | .nth(1) | |
| 92 | + | .unwrap_or_else(|| "makenotwork".into()); | |
| 93 | + | let dirs = vec![( | |
| 94 | + | makeover::bundled_themes_dir().expect("run me from a checkout"), | |
| 95 | + | false, | |
| 96 | + | )]; | |
| 97 | + | let theme = makeover::load_theme(&dirs, &id).expect("no such theme"); | |
| 98 | + | let hex = |key: &str| Rgb::from_hex(&theme.colors[key]).expect("hex"); | |
| 99 | + | let (page, raised) = (hex("surface.page"), hex("surface.raised")); | |
| 100 | + | ||
| 101 | + | println!("{id}, as shipped:"); | |
| 102 | + | row( | |
| 103 | + | " current", | |
| 104 | + | page, | |
| 105 | + | raised, | |
| 106 | + | &with_surfaces(&theme, page, raised), | |
| 107 | + | ); | |
| 108 | + | println!( | |
| 109 | + | "\nthe assertion's floor is 0.05 oklab; goingson sits at {:.3}, audiofiles at {:.3}\n", | |
| 110 | + | 0.119, 0.065 | |
| 111 | + | ); | |
| 112 | + | ||
| 113 | + | println!("lifting raised, page fixed:"); | |
| 114 | + | for delta in [0.070_f32, 0.085, 0.100, 0.119] { | |
| 115 | + | let candidate = at_l(raised, page.to_oklab().l + delta); | |
| 116 | + | let tokens = with_surfaces(&theme, page, candidate); | |
| 117 | + | row(&format!(" delta {delta:.3}"), page, candidate, &tokens); | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | println!("\ndropping page, raised fixed:"); | |
| 121 | + | for delta in [0.070_f32, 0.085, 0.100, 0.119] { | |
| 122 | + | let candidate = at_l(page, raised.to_oklab().l - delta); | |
| 123 | + | let tokens = with_surfaces(&theme, candidate, raised); | |
| 124 | + | row(&format!(" delta {delta:.3}"), candidate, raised, &tokens); | |
| 125 | + | } | |
| 126 | + | } |