Skip to main content

max / makeover

An example that prints a theme's ramp candidates, in both units The ramp question is argued in two units and mixing them is how it 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). Both are printed per row. Two directions, because a ramp has two ends, and each runs out somewhere the other does not. Lifting raised on a light theme runs it toward white, and bevel-light is derived from raised by lightening: makenot.work asks for 0.119 and gets 0.115, with the lit edge gaining nothing while the shadowed one keeps going. Dropping the page instead leaves that headroom alone and crosses surface-sunken, so the theme claims a recess that reads as a rise. The row says so where it happens. Prints and changes nothing. The look call is not the program's, and the nineteen imported themes of c616e9b8 are the second consumer.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01DwpiantpUgohzML4xr6KeQ
Author: Max Johnson <me@maxj.phd> · 2026-08-30 14:26 UTC
Commit: 50e6756e4ba66f755b295aea2ddab4a4c2a11119
Parent: 15f1841
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 + }