Skip to main content

max / makeover-webview

0.36.0: emit a picture's frame, fit and caption makeover_layout::Image arrived at 0.21.0 and nothing here answered for it, so the first port hand-wrote the frame and the caption colour in the app's own stylesheet, where they beat the design system silently. The frame is Depth::Raised through depth_rule, the same call button and card make, rather than a border and a radius written out here. MNW's carousel draws its landing frames with a blurred elevation shadow, and that is the defect: in-flow depth is a bevel, and elevation is for what sits over the page. Emitting the bevel from the depth call is what stops the literal coming back under a new name. No size beyond width: 100%. How large a picture is depends on the box it was put in, which is the app's arrangement and makeover-geometry's scales. Fit's two non-default members get a rule each. Natural emits no attribute at all, so the bare rule is already it.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-14 16:04 UTC
Signed with PGP, not checked
Commit: 6ffadae21c5f1f6393aa969408daadf6591e58a0
Parent: 3bf102f
2 files changed, +73 insertions, -2 deletions
M Cargo.toml +2 -2
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makeover-webview"
3 - version = "0.35.1"
3 + version = "0.36.0"
4 4 edition = "2024"
5 5 # One copy of this renderer per dependency graph, enforced by cargo rather than
6 6 # by remembering. Two versions means the generated stylesheet and the emitted
@@ -17,7 +17,7 @@
17 17 # patch satisfy the requirement and still fail to compile. That happened once
18 18 # with `form::radio_html` calling `FieldKind::Radio`, and makeover-build is where
19 19 # it surfaced, one release later.
20 - makeover-layout = "0.20.0"
20 + makeover-layout = "0.21.0"
21 21 # The capability axis. `makeover-touch` decides whether a hover rule should be
22 22 # gated at all; `makeover-geometry` spells the gate as a media condition. Both
23 23 # answers are owned elsewhere and neither is re-derived here.
M src/lib.rs +71
@@ -1007,6 +1007,64 @@
1007 1007 css
1008 1008 }
1009 1009
1010 + /// A picture, its frame and its caption.
1011 + ///
1012 + /// `makeover_layout::Image` arrived at 0.21.0, found by trying to describe MNW's
1013 + /// carousel and discovering nothing named a picture.
1014 + ///
1015 + /// # The frame is a bevel
1016 + ///
1017 + /// [`Depth::Raised`] through [`depth_rule`], the same call `button` and `card`
1018 + /// make, rather than a border and a radius written out here. MNW's shipped
1019 + /// carousel draws its frames with `1px solid var(--border)` plus a blurred
1020 + /// `rgba()` shadow on the landing page, and the shadow is the defect: in-flow
1021 + /// depth is a bevel, and elevation is for what sits *over* the page. Emitting
1022 + /// the bevel from the depth call is what stops the rgba literal coming back
1023 + /// under a new name.
1024 + ///
1025 + /// # No size
1026 + ///
1027 + /// `width: 100%` and nothing else. How large a picture is depends on the box it
1028 + /// was put in, which is the app's arrangement and `makeover-geometry`'s scales,
1029 + /// and a renderer that picked one would be answering for every consumer at
1030 + /// once. This is where `figure_rules` landed for the same reason.
1031 + fn picture_rules(opts: &Emit) -> String {
1032 + let picture = class("picture", opts);
1033 + let img = class("picture-img", opts);
1034 + let caption = class("picture-caption", opts);
1035 + let mut css = String::new();
1036 +
1037 + css.push_str(&depth_rule(&img, Depth::Raised));
1038 +
1039 + // Block, or an inline image sits on the text baseline and carries a
1040 + // descender's worth of space under it that no app ever wants and every app
1041 + // deletes by hand.
1042 + let _ = writeln!(
1043 + css,
1044 + ".{img} {{\n display: block;\n width: 100%;\n height: auto;\n}}"
1045 + );
1046 +
1047 + // The two fits that need a rule. `Fit::Natural` emits no attribute at all,
1048 + // so it is the bare rule above and needs nothing here.
1049 + let _ = writeln!(
1050 + css,
1051 + ".{img}[data-fit=\"cover\"] {{\n height: 100%;\n object-fit: cover;\n}}"
1052 + );
1053 + let _ = writeln!(
1054 + css,
1055 + ".{img}[data-fit=\"contain\"] {{\n height: 100%;\n object-fit: contain;\n}}"
1056 + );
1057 +
1058 + // A caption reads back one step, which is `figure-caption`'s answer and the
1059 + // same claim: it says what the thing above it is, and it is not the thing.
1060 + let _ = writeln!(
1061 + css,
1062 + ".{picture} > .{caption} {{\n color: var(--content-muted);\n}}"
1063 + );
1064 +
1065 + css
1066 + }
1067 +
1010 1068 /// A region's stand-in, and the header of a table that can be reordered.
1011 1069 ///
1012 1070 /// Both are 0.12.0 members and both are colour and affordance only, which is
@@ -1209,6 +1267,7 @@
1209 1267 css.push_str(&row_rules(opts));
1210 1268 css.push_str(&progress_rules(opts));
1211 1269 css.push_str(&figure_rules(opts));
1270 + css.push_str(&picture_rules(opts));
1212 1271 css.push_str(&state_rules(opts));
1213 1272 css.push_str(&table_rules(opts));
1214 1273 css
@@ -2067,6 +2126,18 @@
2067 2126 | "nowrap"
2068 2127 | "100%"
2069 2128 | "1%"
2129 + // A picture's three, 0.36.0. `block` is structure
2130 + // for the reason `table` is: an inline image sits
2131 + // on the baseline and carries a descender's worth
2132 + // of space under it, which is a fact about
2133 + // replaced elements rather than a size this crate
2134 + // chose. `cover` and `contain` are `Fit`'s two
2135 + // named members reaching CSS unchanged, which is
2136 + // an intent arriving rather than a value being
2137 + // picked.
2138 + | "block"
2139 + | "cover"
2140 + | "contain"
2070 2141 ),
2071 2142 "unrecognised literal value: {line}"
2072 2143 );