git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit414cd86471b9ce9805ec725f220757bf83e1d8ac
parent958aa75001
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-07 23:15
refactor: split layout.rs and color.rs; vertical text leaves the crate root

layout.rs (7.4k lines) is layout/: mod.rs keeps the sizing constants,
the style getters/setters, reload_config and font resolution — and no
longer names WidgetHost or UiContext; registry.rs is the style registry
and its test overlay; bridge.rs the flat-host render bridge
(RenderTarget, PopoverCollector, render_widget, render_popovers); and
legacy.rs the old layout engines (Column, Section, Grid, the
LayoutStrategy family, PageLayoutBuilder, VStack) that
cce-system-interface, cce-files and cce-gallery still use.

color.rs is color/: mod.rs the constants, statics and getters; load.rs
reading the config into them and retired_surface_keys; math.rs
sRGB/linear, OKLab and the perceptual fade; materials.rs the named
materials and rung bindings; chords.rs the tree/list search keys.

Each mod.rs re-exports its submodules, so every crate::layout:: and
crate::color:: path is unchanged; the moves are git mv'd so history
follows the getters.

IS_VERTICAL / BAR_THICKNESS, two bare pub statics at the crate root that
only the status bar sets, are backend::text::set_vertical_text /
vertical_text beside the shaping that reads them. Still process-wide:
the mode is the app's (every label stacks, text shapes at 1.05), and the
bar flips it at runtime as its shape changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md                        |   32 +-
 src/backend/text.rs              |   27 +-
 src/color/chords.rs              |   31 +
 src/color/load.rs                |  602 ++++++++
 src/color/materials.rs           |   53 +
 src/color/math.rs                |  141 ++
 src/{color.rs => color/mod.rs}   |  820 +---------
 src/layout/bridge.rs             |  563 +++++++
 src/layout/legacy.rs             | 1959 ++++++++++++++++++++++++
 src/{layout.rs => layout/mod.rs} | 3058 +-------------------------------------
 src/layout/registry.rs           |  469 ++++++
 src/lib.rs                       |    2 -
 src/widget/display/label.rs      |   10 +-
 src/widget/input/text_box.rs     |    2 +-
 14 files changed, 3932 insertions(+), 3837 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index a64d330..776bf1c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -52,7 +52,7 @@ cargo run   -p cce-ui                            # run the demo/reference app (n
 ```
 
 Tests are headless unit tests colocated in `#[cfg(test)]` modules — concentrated in `src/scene/*`
-(the arena/layout/paint/anim engine) and `src/color.rs`, `src/layout.rs`, plus a
+(the arena/layout/paint/anim engine) and `src/color/`, `src/layout/`, plus a
 scattering of widgets (`text_box`, `slider`, `dropdown`, `treelist`, …). When touching the scene
 engine, that module's tests are the fast feedback loop; run `cargo test -p cce-ui scene::` before
 anything else.
@@ -856,7 +856,7 @@ the seam with the arrow at the trigger's usual right-hand place, and its left si
 narrower than its right.) A textpick row's TextBox ends where its picker begins, so its
 text stops at the well. Never grouped into a host
 plate (its profile is not a monotonic step); the overlay's host-box slot carries `split`.
-The legacy banded path and flat hosts (`layout.rs`'s bridge) draw the two-box form.
+The legacy banded path and flat hosts (`layout::bridge`) draw the two-box form.
 
 **Every flush control wears the run's edge** (since 2026-10-02): `PaintCtx::inset_plate`
 — the one flush control plate, which `ControlPlate`'s Flush stance, every widget and the
@@ -1511,7 +1511,7 @@ lock or screensaver surface, the desktop grid overlay — says so with a
 
 **Spacing is a ladder, and an app never names a number.** Three rungs, each a
 config key read through the style registry (so a nested KDL key works and
-live-reloads), each with a getter in `layout.rs`:
+live-reloads), each with a getter in `layout`:
 
 | rung | inset from the rim | gap between siblings |
 |---|---|---|
@@ -1638,9 +1638,29 @@ cce-system-interface) to confirm behavior, not just the test suite.
 
 ## Module map (where things live)
 
-- `src/layout.rs` (largest file, ~6.9k lines) — fonts + sizing; many `*_font_parsed()` getters and
-  the `read_preferred_fonts` / font-family resolution used by the cosmic-text path.
-- `color.rs` — color model and named colors (`colors` re-export module in `lib.rs`).
+- `layout/` — the style getters and the code that was filed beside them, one module whose
+  `mod.rs` re-exports every submodule, so `crate::layout::…` paths are unchanged (split
+  2026-10-07 from one 7.4k-line file):
+  - `src/layout/mod.rs` (~4.4k lines) — the sizing constants and the style getters/setters
+    (heights, radii, fonts — many `*_font_parsed()` — gaps, the relief and bevel profile
+    state), `reload_config`, and `read_preferred_fonts` / font-family resolution used by the
+    cosmic-text path. It names no widget: what does is in the two modules below.
+  - `registry.rs` — the style registry (config flattened to one map of keys), its test overlay,
+    the font-string helpers.
+  - `bridge.rs` — the flat-host render bridge: `RenderTarget`, `PopoverCollector`,
+    `render_widget`, `render_popovers`, the carve types that cross it.
+  - `src/layout/legacy.rs` (~2k lines) — the legacy layout engines (`Column`, `Section`, `Grid`,
+    the `LayoutStrategy` family, `PageLayoutBuilder`, `VStack`), still used by
+    cce-system-interface, cce-files and cce-gallery. New layout is `scene::layout`.
+- `color/` — the colour model and named colours (`colors` re-export module in `lib.rs`),
+  split the same way: `mod.rs` the constants, statics and getters; `load.rs` reading the
+  config into them (and `retired_surface_keys`); `math.rs` sRGB/linear, OKLab and the
+  perceptual fade; `materials.rs` the named materials and rung bindings; `chords.rs` the
+  tree/list search keys (input.kdl chords with a legacy colour-file fallback).
+- **Vertical text** is `backend::text::set_vertical_text(Some(bar_thickness))` /
+  `vertical_text()` — the status bar's mode when it stands on a screen edge: labels stack
+  their characters and text shapes at a 1.05 line height. Process-wide on purpose (a property
+  of the app); it was two bare `pub static`s at the crate root until 2026-10-07.
 - **`config`, `input`, `motion`, `units`, `relief_spec`, `ipc`** — re-exported from
   `cce-core` (see "The GUI-free half is cce-core"), as are `color`'s hex/sRGB helpers,
   `scene::paint::DropletSpec` and the ramp spec functions (`widget::{format,parse}_ramp_spec`,
diff --git a/src/backend/text.rs b/src/backend/text.rs
index 9d0c9cd..47b524c 100644
--- a/src/backend/text.rs
+++ b/src/backend/text.rs
@@ -75,6 +75,29 @@ fn buffer_tick() -> u64 {
     })
 }
 
+/// Vertical text, for a process whose window is a vertical bar (the status bar on a
+/// screen edge): while it is on, a [`TextLabel`](crate::widget::display::TextLabel) stacks
+/// its characters one per line, centred in a column `bar_thickness` px wide, and text is
+/// shaped at a looser 1.05 line height. Process-wide because it is a property of the app,
+/// not of one window or one label, and shaping may run on any thread. It was two bare
+/// `pub static`s at the crate root (`IS_VERTICAL`, `BAR_THICKNESS`) until 2026-10-07.
+pub fn set_vertical_text(bar_thickness: Option<u32>) {
+    use std::sync::atomic::Ordering::Relaxed;
+    if let Some(t) = bar_thickness {
+        VERTICAL_BAR_THICKNESS.store(t, Relaxed);
+    }
+    VERTICAL_TEXT.store(bar_thickness.is_some(), Relaxed);
+}
+
+/// The vertical bar's thickness while vertical text is on (see [`set_vertical_text`]).
+pub fn vertical_text() -> Option<u32> {
+    use std::sync::atomic::Ordering::Relaxed;
+    VERTICAL_TEXT.load(Relaxed).then(|| VERTICAL_BAR_THICKNESS.load(Relaxed))
+}
+
+static VERTICAL_TEXT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
+static VERTICAL_BAR_THICKNESS: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(24);
+
 /// The cached buffer for `text` under `key`, and its vertical offset.
 fn buffer_cache_get(text: &str, key: &BufferKey<'_>) -> Option<(Rc<Buffer>, f32)> {
     BUFFER_CACHE.with(|cache| {
@@ -417,7 +440,7 @@ pub(crate) fn shared_text_buffer(
     };
 
     let physical_size = font_size * scale;
-    let is_vertical = crate::IS_VERTICAL.load(std::sync::atomic::Ordering::Relaxed);
+    let is_vertical = vertical_text().is_some();
     let key = BufferKey {
         size_milli: (physical_size * 1000.0).round() as u32,
         font: family_name,
@@ -630,7 +653,7 @@ pub(crate) fn shared_laid_out_buffer(
     let key = BufferKey {
         size_milli: (physical_size * 1000.0).round() as u32,
         font: family,
-        is_vertical: crate::IS_VERTICAL.load(std::sync::atomic::Ordering::Relaxed),
+        is_vertical: vertical_text().is_some(),
         attrs: text_attrs,
         scale_bits: scale.to_bits(),
         layout: Some(layout),
diff --git a/src/color/chords.rs b/src/color/chords.rs
new file mode 100644
index 0000000..a6baead
--- /dev/null
+++ b/src/color/chords.rs
@@ -0,0 +1,31 @@
+//! The search keys the tree and list widgets open and close their search with: input.kdl's
+//! `open_search` / `close_search` chords, falling back to the legacy colour-file keys.
+
+use super::*;
+
+pub fn tree_open_search_key() -> String {
+    load_colors_once();
+    let legacy = TREE_OPEN_SEARCH_KEY.read().unwrap().clone();
+    crate::input::widget_chord("open_search", &legacy, "ctrl+f")
+}
+pub fn set_tree_open_search_key(k: String) {
+    style_write(&TREE_OPEN_SEARCH_KEY, k);
+}
+
+pub fn list_open_search_key() -> String {
+    load_colors_once();
+    let legacy = LIST_OPEN_SEARCH_KEY.read().unwrap().clone();
+    crate::input::widget_chord("open_search", &legacy, "ctrl+f")
+}
+pub fn set_list_open_search_key(k: String) {
+    style_write(&LIST_OPEN_SEARCH_KEY, k);
+}
+
+pub fn list_close_search_key() -> String {
+    load_colors_once();
+    let legacy = LIST_CLOSE_SEARCH_KEY.read().unwrap().clone();
+    crate::input::widget_chord("close_search", &legacy, "escape")
+}
+pub fn set_list_close_search_key(k: String) {
+    style_write(&LIST_CLOSE_SEARCH_KEY, k);
+}
diff --git a/src/color/load.rs b/src/color/load.rs
new file mode 100644
index 0000000..45d6567
--- /dev/null
+++ b/src/color/load.rs
@@ -0,0 +1,602 @@
+//! Loading the colour and surface config: reading `config.kdl` into the colour statics
+//! (`parse_and_set_colors`), reporting retired surface keys, and the reload entry points.
+
+use super::*;
+
+fn read_config() -> Option<String> {
+    let path = crate::config::get_config_path();
+    if let Ok(content) = std::fs::read_to_string(&path) {
+        return Some(content);
+    }
+    None
+}
+
+fn parse_and_set_colors(content: &str) {
+    let val = crate::config::parse_kdl_to_json(content);
+
+    let parse_hex = |hex_str: &str| -> Option<[f32; 4]> {
+        let hex = hex_str.trim_matches(|c| c == '"' || c == '\'' || c == ' ');
+        let hex = hex.trim_start_matches('#');
+        if hex.len() >= 8 {
+            if let (Ok(r), Ok(g), Ok(b), Ok(a)) = (
+                u8::from_str_radix(&hex[0..2], 16),
+                u8::from_str_radix(&hex[2..4], 16),
+                u8::from_str_radix(&hex[4..6], 16),
+                u8::from_str_radix(&hex[6..8], 16),
+            ) {
+                let r_f = srgb_to_linear(r as f32 / 255.0);
+                let g_f = srgb_to_linear(g as f32 / 255.0);
+                let b_f = srgb_to_linear(b as f32 / 255.0);
+                let a_f = a as f32 / 255.0;
+                return Some([r_f, g_f, b_f, a_f]);
+            }
+        } else if hex.len() >= 6 {
+            if let (Ok(r), Ok(g), Ok(b)) = (
+                u8::from_str_radix(&hex[0..2], 16),
+                u8::from_str_radix(&hex[2..4], 16),
+                u8::from_str_radix(&hex[4..6], 16),
+            ) {
+                let r_f = srgb_to_linear(r as f32 / 255.0);
+                let g_f = srgb_to_linear(g as f32 / 255.0);
+                let b_f = srgb_to_linear(b as f32 / 255.0);
+                return Some([r_f, g_f, b_f, 1.0]);
+            }
+        }
+        None
+    };
+
+    let get_color = |pointer: &str| -> Option<[f32; 4]> {
+        val.pointer(pointer).and_then(|v| v.as_str()).and_then(parse_hex)
+    };
+
+    let get_color_u8 = |pointer: &str| -> Option<[u8; 3]> {
+        val.pointer(pointer).and_then(|v| v.as_str()).and_then(|hex_str| {
+            let hex = hex_str.trim_matches(|c| c == '"' || c == '\'' || c == ' ');
+            let hex = hex.trim_start_matches('#');
+            if hex.len() >= 6 {
+                if let (Ok(r), Ok(g), Ok(b)) = (
+                    u8::from_str_radix(&hex[0..2], 16),
+                    u8::from_str_radix(&hex[2..4], 16),
+                    u8::from_str_radix(&hex[4..6], 16),
+                ) {
+                    return Some([r, g, b]);
+                }
+            }
+            None
+        })
+    };
+
+    if let Some(opacity) = val.pointer("/layout/menubar_opacity").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = OPACITY.write() {
+            *lock = Some(opacity as f32);
+        }
+    }
+
+    // `/style/surface/plate/root/...` is the one spelling of the root-plate
+    // style (RFC Phase 7a; the legacy `root plate` read-alias was removed
+    // 2026-09-06 once every live config had migrated).
+    if let Some(radius) = val.pointer("/style/surface/plate/root/corner_radius")
+        .and_then(|v| v.as_f64())
+    {
+        if let Ok(mut lock) = ROOT_PLATE_CORNER_RADIUS.write() {
+            *lock = radius as f32;
+        }
+     }
+
+    if let Some(c) = get_color("/style/surface/plate/root/color") {
+        if let Ok(mut lock) = PAGE_LOW_COLOR.write() { *lock = c; }
+        if let Ok(mut lock) = ROOT_PLATE_OPACITY.write() { *lock = Some(c[3]); }
+    }
+
+    if let Some(c) = get_color("/style/surface/plate/root/menubar/color") {
+        if let Ok(mut lock) = ROOT_PLATE_MENUBAR_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/plate/root/menubar/text_color") {
+        if let Ok(mut lock) = ROOT_PLATE_MENUBAR_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(o) = val.pointer("/style/surface/menu/opacity").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = MENU_OPACITY.write() { *lock = (o as f32).clamp(0.0, 1.0); }
+    }
+    if let Some(c) = val.pointer("/style/surface/menu/compression").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = MENU_COMPRESSION.write() { *lock = (c as f32).clamp(0.0, 1.0); }
+    }
+    if let Some(c) = get_color("/style/surface/menu/color") {
+        if let Ok(mut lock) = MENU_COLOR.write() { *lock = Some(c); }
+    }
+
+    let menubar_blur_ptr = val.pointer("/style/surface/plate/root/menubar/blur");
+    if let Some(blur) = menubar_blur_ptr.and_then(|v| v.as_bool()) {
+        if let Ok(mut lock) = ROOT_PLATE_MENUBAR_BLUR.write() { *lock = blur; }
+    } else if let Some(blur_val) = menubar_blur_ptr.and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = ROOT_PLATE_MENUBAR_BLUR.write() { *lock = blur_val > 0.001; }
+    }
+
+    if let Some(c) = get_color("/style/surface/statusbar/color") {
+        if let Ok(mut lock) = ROOT_PLATE_STATUSBAR_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/statusbar/text_color") {
+        if let Ok(mut lock) = ROOT_PLATE_STATUSBAR_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(blur) = val.pointer("/style/surface/statusbar/blur").and_then(|v| v.as_bool()) {
+        if let Ok(mut lock) = ROOT_PLATE_STATUSBAR_BLUR.write() { *lock = blur; }
+    } else if let Some(blur_val) = val.pointer("/style/surface/statusbar/blur").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = ROOT_PLATE_STATUSBAR_BLUR.write() { *lock = blur_val > 0.001; }
+    }
+    if let Some(c) = get_color("/layout/color_borders_color") {
+        if let Ok(mut lock) = COLOR_BORDERS_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/layout/paginator_sidebar_color") {
+        if let Ok(mut lock) = SIDEBAR_BG_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/highlight/primary") {
+        if let Ok(mut lock) = HIGHLIGHT_PRIMARY_COLOR.write() { *lock = [c[0], c[1], c[2], 0.12]; }
+    }
+    if let Some(c) = get_color("/style/control/button/background").or_else(|| get_color("/layout/button_background_color")) {
+        if let Ok(mut lock) = BUTTON_BACKGROUND_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/dropdown/color") {
+        if let Ok(mut lock) = DROPDOWN_BACKGROUND_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color_u8("/style/control/textbox/placeholder_text_color")
+        .or_else(|| get_color_u8("/style/textbox/placeholder_text_color"))
+        .or_else(|| get_color_u8("/style/data/textbox/placeholder_text_color")) {
+        if let Ok(mut lock) = TEXTBOX_PLACEHOLDER_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/textbox/background_color")
+        .or_else(|| get_color("/style/textbox/background_color"))
+        .or_else(|| get_color("/style/data/textbox/background_color")) {
+        if let Ok(mut lock) = TEXTBOX_BACKGROUND_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/textbox/background_edit_color")
+        .or_else(|| get_color("/style/textbox/background_edit_color"))
+        .or_else(|| get_color("/style/data/textbox/background_edit_color")) {
+        if let Ok(mut lock) = TEXTBOX_BACKGROUND_EDIT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/layout/menubar_tab_label_color").or_else(|| get_color("/layout/paginator_tab_label_color")) {
+        if let Ok(mut lock) = MENUBAR_TAB_LABEL_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/label/color") {
+        if let Ok(mut lock) = CONTROL_LABEL_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/label/color_detached") {
+        if let Ok(mut lock) = CONTROL_LABEL_COLOR_DETACHED.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/label/hover_color") {
+        if let Ok(mut lock) = CONTROL_LABEL_HOVER_COLOR.write() { *lock = Some(c); }
+    }
+    if let Some(c) = get_color("/style/control/label/focus_color") {
+        if let Ok(mut lock) = CONTROL_LABEL_FOCUS_COLOR.write() { *lock = Some(c); }
+    }
+    // The toggle state colors (enabled/disabled/gradient/background) are not
+    // read: the control inherits its plate — see the note by the retired
+    // getters below.
+    if let Some(c) = get_color("/style/control/ramp/background") {
+        if let Ok(mut lock) = RAMP_BACKGROUND_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/ramp/border_color") {
+        if let Ok(mut lock) = RAMP_BORDER_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/control_panel/color") {
+        if let Ok(mut lock) = CONTROL_PANEL_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/control_panel/border_color") {
+        if let Ok(mut lock) = CONTROL_PANEL_BORDER_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/spinbox/background_color") {
+        if let Ok(mut lock) = SPINBOX_DISPLAY_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/spinbox/button_color") {
+        if let Ok(mut lock) = SPINBOX_BUTTON_COLOR.write() { *lock = c; }
+        if let Ok(mut lock_hover) = SPINBOX_BUTTON_HOVER_COLOR.write() {
+            *lock_hover = [
+                (c[0] + 0.1).min(1.0),
+                (c[1] + 0.1).min(1.0),
+                (c[2] + 0.1).min(1.0),
+                c[3]
+            ];
+        }
+    }
+    if let Some(c) = get_color("/style/control/spinbox/button_hover_color") {
+        if let Ok(mut lock) = SPINBOX_BUTTON_HOVER_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/spinbox/text_color") {
+        if let Ok(mut lock) = SPINBOX_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/button/border_color").or_else(|| get_color("/style/button/border_color")) {
+        if let Ok(mut lock) = BUTTON_BORDER_COLOR.write() { *lock = Some(c); }
+    }
+    if let Some(c) = get_color("/style/control/button/hover_color").or_else(|| get_color("/style/button/hover_color")) {
+        if let Ok(mut lock) = BUTTON_HOVER_COLOR.write() { *lock = Some(c); }
+    }
+    if let Some(c) = get_color("/style/control/dropdown/border_color").or_else(|| get_color("/style/dropdown/border_color")) {
+        if let Ok(mut lock) = DROPDOWN_BORDER_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/dropdown/text_color").or_else(|| get_color("/style/dropdown/text_color")) {
+        if let Ok(mut lock) = DROPDOWN_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/slider/thumb_color").or_else(|| get_color("/style/slider/thumb_color")) {
+        if let Ok(mut lock) = SLIDER_THUMB_COLOR.write() { *lock = c; }
+        if let Ok(mut lock_drag) = SLIDER_THUMB_DRAG_COLOR.write() {
+            *lock_drag = [
+                (c[0] + 0.15).min(1.0),
+                (c[1] + 0.15).min(1.0),
+                (c[2] + 0.15).min(1.0),
+                c[3]
+            ];
+        }
+    }
+    if let Some(c) = get_color("/style/control/rangeslider/thumb_color").or_else(|| get_color("/style/rangeslider/thumb_color")) {
+        if let Ok(mut lock) = RANGE_SLIDER_THUMB_COLOR.write() { *lock = c; }
+        if let Ok(mut lock_drag) = RANGE_SLIDER_THUMB_DRAG_COLOR.write() {
+            *lock_drag = [
+                (c[0] + 0.15).min(1.0),
+                (c[1] + 0.15).min(1.0),
+                (c[2] + 0.15).min(1.0),
+                c[3]
+            ];
+        }
+    }
+    if let Some(c) = get_color("/style/control/progressbar/background") {
+        if let Ok(mut lock) = PROGRESS_BG_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/progressbar/fill") {
+        if let Ok(mut lock) = PROGRESS_FILL_COLOR.write() { *lock = c; }
+    }
+    let parsed_list_bg = get_color("/layout/list_bg_color");
+    let parsed_breadcrumb_bg = get_color("/layout/breadcrumb_bg_color");
+    let parsed_popover_bg = get_color("/layout/popover_bg_color");
+
+    if let Some(c) = get_color("/layout/page_color") {
+        if let Ok(mut lock) = PAGE_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/layout/layer_color") {
+        if let Ok(mut lock) = LAYER_COLOR.write() { *lock = c; }
+    }
+
+    if let Some(c) = parsed_list_bg {
+        if let Ok(mut lock) = LIST_BG_COLOR.write() {
+            *lock = [c[0], c[1], c[2], 0.3];
+        }
+    }
+    if let Some(c) = get_color("/layout/list_entry_bg_color") {
+        if let Ok(mut lock) = LIST_ENTRY_BG_COLOR.write() {
+            *lock = c;
+        }
+    }
+    if let Some(c) = get_color("/layout/list_entry_highlight_color") {
+        if let Ok(mut lock) = LIST_ENTRY_HIGHLIGHT_COLOR.write() {
+            *lock = c;
+        }
+    }
+    if let Some(c) = get_color("/style/data/list/font_color").or_else(|| get_color("/layout/list_font_color")) {
+        if let Ok(mut lock) = LIST_FONT_COLOR.write() {
+            *lock = c;
+        }
+    }
+    if let Some(c) = parsed_breadcrumb_bg {
+        if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
+            *lock = [c[0], c[1], c[2], 1.0];
+        }
+    } else if let Some(c) = parsed_list_bg {
+        if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
+            *lock = [c[0], c[1], c[2], 1.0];
+        }
+    }
+    if let Some(c) = parsed_popover_bg {
+        if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
+            *lock = [c[0], c[1], c[2], 1.0];
+        }
+    } else if let Some(c) = parsed_list_bg {
+        if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
+            *lock = [c[0], c[1], c[2], 1.0];
+        }
+    }
+
+    // The lattice's lines. The cells between them have no colour of their
+    // own: a graph shows whatever it is painted on. `cell_color` and
+    // `gap_color`, the cell model's pair, are retired (`retired_surface_keys`).
+    if let Some(c) = get_color("/style/surface/graph/grid_color") {
+        if let Ok(mut lock) = GRAPH_GRID_COLOR.write() { *lock = [c[0], c[1], c[2]]; }
+    }
+    if let Some(c) = get_color("/style/surface/graph/node/color") {
+        if let Ok(mut lock) = GRAPH_NODE_COLOR.write() { *lock = c; }
+        if let Ok(mut lock) = NODE_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/graph/node/selected_color") {
+        if let Ok(mut lock) = GRAPH_NODE_SELECTED_COLOR.write() { *lock = c; }
+        if let Ok(mut lock) = NODE_SELECTED_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/graph/node/drag_color") {
+        if let Ok(mut lock) = GRAPH_NODE_DRAG_COLOR.write() { *lock = c; }
+        if let Ok(mut lock) = NODE_DRAG_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/graph/node/wire_color") {
+        if let Ok(mut lock) = GRAPH_WIRE_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/graph/node/wire_highlight_color") {
+        if let Ok(mut lock) = GRAPH_WIRE_HIGHLIGHT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/graph/node/connector_color") {
+        if let Ok(mut lock) = GRAPH_CONNECTOR_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/graph/node/connector_highlight_color") {
+        if let Ok(mut lock) = GRAPH_CONNECTOR_HIGHLIGHT_COLOR.write() { *lock = c; }
+    }
+    if let Some(opacity) = val.pointer("/style/surface/graph/opacity").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = GRAPH_OPACITY.write() { *lock = opacity as f32; }
+    }
+    if let Some(opacity) = val.pointer("/style/surface/graph/node/opacity").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = GRAPH_NODE_OPACITY.write() { *lock = opacity as f32; }
+    }
+
+    if let Some(c) = get_color("/style/data/tree/background_color") {
+        if let Ok(mut lock) = TREE_BACKGROUND_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/border_color") {
+        if let Ok(mut lock) = TREE_BORDER_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/border_hover_color") {
+        if let Ok(mut lock) = TREE_BORDER_HOVER_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/border_focus_color") {
+        if let Ok(mut lock) = TREE_BORDER_FOCUS_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/section_bg_color") {
+        if let Ok(mut lock) = TREE_SECTION_BG_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/section_bg_hover_color") {
+        if let Ok(mut lock) = TREE_SECTION_BG_HOVER_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/leaf_bg_even_color") {
+        if let Ok(mut lock) = TREE_LEAF_BG_EVEN_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/leaf_bg_odd_color") {
+        if let Ok(mut lock) = TREE_LEAF_BG_ODD_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/leaf_bg_hover_color") {
+        if let Ok(mut lock) = TREE_LEAF_BG_HOVER_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/leaf_bg_selected_color") {
+        if let Ok(mut lock) = TREE_LEAF_BG_SELECTED_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/section_text_color") {
+        if let Ok(mut lock) = TREE_SECTION_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/leaf_text_color") {
+        if let Ok(mut lock) = TREE_LEAF_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/leaf_text_selected_color") {
+        if let Ok(mut lock) = TREE_LEAF_TEXT_SELECTED_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/type_text_color") {
+        if let Ok(mut lock) = TREE_TYPE_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/value_text_color") {
+        if let Ok(mut lock) = TREE_VALUE_TEXT_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/data/tree/separator_color") {
+        if let Ok(mut lock) = TREE_SEPARATOR_COLOR.write() { *lock = c; }
+    }
+    if let Some(k) = val.pointer("/style/data/tree/open_search").and_then(|v| v.as_str()) {
+        if let Ok(mut lock) = TREE_OPEN_SEARCH_KEY.write() { *lock = k.to_string(); }
+    }
+    if let Some(k) = val.pointer("/style/data/list/open_search").and_then(|v| v.as_str()) {
+        if let Ok(mut lock) = LIST_OPEN_SEARCH_KEY.write() { *lock = k.to_string(); }
+    }
+    if let Some(k) = val.pointer("/style/data/list/close_search").and_then(|v| v.as_str()) {
+        if let Ok(mut lock) = LIST_CLOSE_SEARCH_KEY.write() { *lock = k.to_string(); }
+    }
+    if let Some(c) = get_color("/style/control/scrollbar/track_color") {
+        if let Ok(mut lock) = SCROLLBAR_TRACK_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/control/scrollbar/thumb_color") {
+        if let Ok(mut lock) = SCROLLBAR_THUMB_COLOR.write() { *lock = c; }
+    }
+    if let Some(c) = get_color("/style/surface/plate/color") {
+        if let Ok(mut lock) = PLATE_COLOR.write() { *lock = Some(c); }
+    }
+    if let Some(c) = get_color("/style/surface/plate/border_color") {
+        if let Ok(mut lock) = PLATE_BORDER_COLOR.write() { *lock = Some(c); }
+    }
+    if let Some(t) = val.pointer("/style/surface/plate/border_thickness").and_then(|v| v.as_f64()) {
+        if let Ok(mut lock) = PLATE_BORDER_THICKNESS.write() { *lock = t as f32; }
+    }
+    if let Some(c) = get_color("/style/surface/param/color") {
+        if let Ok(mut lock) = PARAM_BG_COLOR.write() { *lock = c; }
+        if let Ok(mut lock) = PANE_COLOR_WHOLE.write() { *lock = false; }
+    }
+    // The clear spelling of the same tint: the PANE plate's colour, whole —
+    // its alpha is the tint strength and nothing multiplies it.
+    if let Some(c) = get_color("/style/surface/plate/pane/color") {
+        if let Ok(mut lock) = PARAM_BG_COLOR.write() { *lock = c; }
+        if let Ok(mut lock) = PANE_COLOR_WHOLE.write() { *lock = true; }
+    }
+    // The default plate's frost is ONE block, the same shape a named
+    // material's `frost` child has: `plate { frost radius=5.5 compression=0
+    // refraction=0 }` is frosted with those knobs, a bare `frost` is frosted
+    // at the defaults, `frost (bool)false` is not frosted. The four scattered
+    // keys it replaced on 2026-09-28 (`blur` as the switch, `radius`,
+    // `backdrop_compression`, `refraction`) were read as aliases for the rest
+    // of that day and are retired: a config still carrying one is reported
+    // (`retired_surface_keys`) and the key does nothing, because an alias that
+    // keeps working is a second spelling forever. The block is absent → not
+    // frosted, which is what `blur=true` alone now amounts to; the report
+    // is what tells the user why their plates went sharp.
+    let retired = retired_surface_keys(&val);
+    if !retired.is_empty() {
+        log::warn!(
+            "retired style.surface keys in config: {} — the frost is `plate {{ frost radius= compression= refraction= }}` (a material's `frost` child spells `compression`), every roll's width is `relief width=`, the light strength is `relief light=` (a material's `finish light=`), and the relief's geometry is `relief {{ wall height= profile= ; edge height= profile= }}` (the window_manager bevel_* spellings are the same relief keys, and its bevel_shader is `relief shader=`); a graph's lines are `graph {{ grid_color }}` and its cells take no colour",
+            retired.join(", ")
+        );
+    }
+    match val.pointer("/style/surface/plate/frost") {
+        Some(serde_json::Value::Object(fo)) => {
+            let num = |k: &str| fo.get(k).and_then(|v| v.as_f64()).map(|v| v as f32);
+            if let Ok(mut lock) = PLATE_BLUR.write() { *lock = true; }
+            if let Some(c) = num("compression") {
+                if let Ok(mut lock) = PLATE_BACKDROP_COMPRESSION.write() { *lock = c.clamp(0.0, 1.0); }
+            }
+            if let Some(r) = num("refraction") {
+                if let Ok(mut lock) = PLATE_REFRACTION.write() { *lock = r.clamp(0.0, 1.0); }
+            }
+            if let Some(r) = num("radius") {
+                if let Ok(mut lock) = PLATE_FROST_RADIUS.write() { *lock = r.max(0.0); }
+            }
+        }
+        Some(serde_json::Value::Null) => {
+            if let Ok(mut lock) = PLATE_BLUR.write() { *lock = true; }
+        }
+        Some(serde_json::Value::Bool(on)) => {
+            if let Ok(mut lock) = PLATE_BLUR.write() { *lock = *on; }
+        }
+        _ => {}
+    }
+
+    // The DE finish beyond its strength (`relief.depth` / `light`, which
+    // lives in the style registry): the three terms that were literals.
+    let f = |k: &str| val.pointer(&format!("/style/surface/relief/{k}")).and_then(|v| v.as_f64()).map(|v| v as f32);
+    if let Some(v) = f("spec") {
+        if let Ok(mut lock) = FINISH_SPEC.write() { *lock = v.max(0.0); }
+    }
+    if let Some(v) = f("shininess") {
+        if let Ok(mut lock) = FINISH_SHININESS.write() { *lock = v.max(1.0); }
+    }
+    if let Some(v) = f("curvature") {
+        if let Ok(mut lock) = FINISH_CURVATURE.write() { *lock = v.max(0.0); }
+    }
+
+    // Named materials and the rung bindings (RFC material § 5), replaced
+    // wholesale so a reload forgets what config no longer says.
+    {
+        use crate::scene::material::{FrostDef, MaterialDef};
+        let num = |v: Option<&serde_json::Value>| v.and_then(|v| v.as_f64()).map(|v| v as f32);
+        let mut map: Vec<(String, MaterialDef)> = Vec::new();
+        if let Some(obj) = val.pointer("/style/surface/material").and_then(|v| v.as_object()) {
+            for (name, node) in obj {
+                let Some(node) = node.as_object() else { continue };
+                let frost = node.get("frost").map(|fr| match fr.as_object() {
+                    Some(fo) => FrostDef {
+                        compression: num(fo.get("compression")),
+                        refraction: num(fo.get("refraction")),
+                        radius: num(fo.get("radius")),
+                    },
+                    // A bare `frost` node (no knobs) is frosted at the defaults.
+                    None => FrostDef::default(),
+                });
+                let fin = node.get("finish").and_then(|v| v.as_object());
+                let fk = |k: &str| fin.and_then(|fo| num(fo.get(k)));
+                map.push((
+                    name.clone(),
+                    MaterialDef {
+                        tint: node.get("color").and_then(|v| v.as_str()).and_then(parse_hex),
+                        frost,
+                        light: fk("light"),
+                        spec: fk("spec"),
+                        shininess: fk("shininess"),
+                        curvature: fk("curvature"),
+                    },
+                ));
+            }
+        }
+        if let Ok(mut lock) = NAMED_MATERIALS.write() { *lock = map; }
+        let bind = |p: &str| val.pointer(p).and_then(|v| v.as_str()).map(|s| s.to_string()).filter(|s| !s.is_empty());
+        let bindings = [
+            bind("/style/surface/plate/root/material"),
+            bind("/style/surface/plate/material"),
+            bind("/style/control/material"),
+        ];
+        if let Ok(mut lock) = MATERIAL_BINDINGS.write() { *lock = bindings; }
+    }
+}
+
+/// The retired `style.surface` spellings a parsed config still carries, as
+/// the dotted paths a user would grep for: the four flat frost keys under
+/// `plate` (`blur`, `radius`, `backdrop_compression`, `refraction`),
+/// `backdrop_compression` inside any `frost` child (the plate's, or a named
+/// material's), whose one name is `compression`; `plate.bevel_width`, the
+/// pane roll's former width of its own (every roll is `relief.width`); and
+/// `relief.depth` with `depth` inside any material's `finish` child, the
+/// light strength's former name (`light`, since it is not a length); and
+/// the relief's flat geometry keys `height` / `profile` (the wall's, now
+/// `wall.height` / `wall.profile`) and `edge_height` / `edge_profile` (the
+/// edge's, now `edge.height` / `edge.profile`); and `window_manager.bevel_depth`
+/// / `bevel_width` / `bevel_shader`, the block the relief keys were born in
+/// before `style.surface.relief` existed (the compositor never read them;
+/// the shader toggle is `relief.shader` now). Each was an alias for part
+/// of 2026-09-28 and is not read now. And the graph's `cell_color`,
+/// `gap_color` and `uniform_background` (2026-09-29): a graph's cells have
+/// no colour, so there is no fill to switch, and its lines are
+/// `graph.grid_color`. Empty for a clean config.
+pub fn retired_surface_keys(val: &serde_json::Value) -> Vec<String> {
+    let mut found = Vec::new();
+    for k in ["blur", "radius", "backdrop_compression", "refraction", "bevel_width"] {
+        if val.pointer(&format!("/style/surface/plate/{k}")).is_some() {
+            found.push(format!("style.surface.plate.{k}"));
+        }
+    }
+    if val.pointer("/style/surface/plate/frost/backdrop_compression").is_some() {
+        found.push("style.surface.plate.frost.backdrop_compression".to_string());
+    }
+    for k in ["depth", "height", "profile", "edge_height", "edge_profile"] {
+        if val.pointer(&format!("/style/surface/relief/{k}")).is_some() {
+            found.push(format!("style.surface.relief.{k}"));
+        }
+    }
+    for k in ["bevel_depth", "bevel_width", "bevel_shader"] {
+        if val.pointer(&format!("/window_manager/{k}")).is_some() {
+            found.push(format!("window_manager.{k}"));
+        }
+    }
+    if let Some(mats) = val.pointer("/style/surface/material").and_then(|v| v.as_object()) {
+        for (name, node) in mats {
+            if node.pointer("/frost/backdrop_compression").is_some() {
+                found.push(format!("style.surface.material.{name}.frost.backdrop_compression"));
+            }
+            if node.pointer("/finish/depth").is_some() {
+                found.push(format!("style.surface.material.{name}.finish.depth"));
+            }
+        }
+    }
+    for k in ["cell_color", "gap_color", "uniform_background"] {
+        if val.pointer(&format!("/style/surface/graph/{k}")).is_some() {
+            found.push(format!("style.surface.graph.{k}"));
+        }
+    }
+    found
+}
+
+pub(super) fn load_colors_once() {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        if let Some(content) = read_config() {
+            parse_and_set_colors(&content);
+        }
+    });
+}
+
+pub fn reload_colors(content: &str) {
+    parse_and_set_colors(content);
+}
+
+/// Test-only: the color state is process-global, so any test that calls
+/// [`reload_colors`] and then asserts getter values races every other such
+/// test on the parallel harness. Each of those tests must hold this lock
+/// across its reload + asserts, and should fire the once-per-process config
+/// loads ([`load_colors_once`] via any getter, and
+/// `layout::lazy_init_style_registry`) inside the lock BEFORE its reload, so
+/// neither can rewrite the state from the live config file mid-assert.
+#[cfg(test)]
+pub(crate) fn test_color_state_lock() -> std::sync::MutexGuard<'static, ()> {
+    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
+    LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
+}
+
+pub fn dropdown_background_color() -> [f32; 4] {
+    load_colors_once();
+    style_read(&DROPDOWN_BACKGROUND_COLOR)
+}
+
+pub fn set_dropdown_background_color(color: [f32; 4]) {
+    style_write(&DROPDOWN_BACKGROUND_COLOR, color);
+}
diff --git a/src/color/materials.rs b/src/color/materials.rs
new file mode 100644
index 0000000..e2c929c
--- /dev/null
+++ b/src/color/materials.rs
@@ -0,0 +1,53 @@
+//! The named materials config defines and the rung bindings (`docs/rfc-material.md` § 5).
+
+use super::*;
+use std::sync::RwLock;
+
+/// The named materials config defines (`style.surface.material { <name> {…} }`)
+/// and the rung bindings (`plate material=`, `plate { root material= }`,
+/// `control material=`) — see `docs/rfc-material.md` § 5. Replaced wholesale
+/// on every config load, so a node or binding removed from config is gone
+/// after a reload.
+pub(super) static NAMED_MATERIALS: RwLock<Vec<(String, crate::scene::material::MaterialDef)>> = RwLock::new(Vec::new());
+pub(super) static MATERIAL_BINDINGS: RwLock<[Option<String>; 3]> = RwLock::new([None, None, None]);
+
+fn rung_index(rung: crate::scene::material::PlateRung) -> usize {
+    match rung {
+        crate::scene::material::PlateRung::Root => 0,
+        crate::scene::material::PlateRung::Pane => 1,
+        crate::scene::material::PlateRung::Control => 2,
+    }
+}
+
+pub fn named_material(name: &str) -> Option<crate::scene::material::MaterialDef> {
+    load_colors_once();
+    style_read(&NAMED_MATERIALS).iter().find(|(n, _)| n == name).map(|(_, d)| d.clone())
+}
+
+/// Every defined material's name, sorted.
+pub fn material_names() -> Vec<String> {
+    load_colors_once();
+    let mut v: Vec<String> = style_read(&NAMED_MATERIALS).into_iter().map(|(n, _)| n).collect();
+    v.sort();
+    v
+}
+
+pub fn set_named_material(name: &str, def: Option<crate::scene::material::MaterialDef>) {
+    let mut list = style_read(&NAMED_MATERIALS);
+    list.retain(|(n, _)| n != name);
+    if let Some(d) = def {
+        list.push((name.to_string(), d));
+    }
+    style_write(&NAMED_MATERIALS, list);
+}
+
+pub fn material_binding(rung: crate::scene::material::PlateRung) -> Option<String> {
+    load_colors_once();
+    style_read(&MATERIAL_BINDINGS)[rung_index(rung)].clone()
+}
+
+pub fn set_material_binding(rung: crate::scene::material::PlateRung, name: Option<String>) {
+    let mut b = style_read(&MATERIAL_BINDINGS);
+    b[rung_index(rung)] = name;
+    style_write(&MATERIAL_BINDINGS, b);
+}
diff --git a/src/color/math.rs b/src/color/math.rs
new file mode 100644
index 0000000..f91f723
--- /dev/null
+++ b/src/color/math.rs
@@ -0,0 +1,141 @@
+//! Colour arithmetic: sRGB/linear conversion (the hex parsers come from `cce_core::color`),
+//! OKLab, and the perceptual fade.
+
+use super::*;
+
+pub use cce_core::color::{linear_to_srgb, parse_hex_bytes, parse_hex_rgb, parse_hex_rgba, parse_hex_rgba_linear, srgb_to_linear};
+
+pub fn to_linear(color: [f32; 4]) -> [f32; 4] {
+    [
+        srgb_to_linear(color[0]),
+        srgb_to_linear(color[1]),
+        srgb_to_linear(color[2]),
+        color[3],
+    ]
+}
+
+pub fn to_srgb(color: [f32; 4]) -> [f32; 4] {
+    [
+        linear_to_srgb(color[0]),
+        linear_to_srgb(color[1]),
+        linear_to_srgb(color[2]),
+        color[3],
+    ]
+}
+
+pub fn to_linear_rgb(color: [f32; 3]) -> [f32; 3] {
+    [
+        srgb_to_linear(color[0]),
+        srgb_to_linear(color[1]),
+        srgb_to_linear(color[2]),
+    ]
+}
+
+pub fn to_srgb_rgb(color: [f32; 3]) -> [f32; 3] {
+    [
+        linear_to_srgb(color[0]),
+        linear_to_srgb(color[1]),
+        linear_to_srgb(color[2]),
+    ]
+}
+
+pub fn linear_srgb_to_oklab(rgb: [f32; 3]) -> [f32; 3] {
+    let l = 0.4122214708 * rgb[0] + 0.5363325363 * rgb[1] + 0.0514459929 * rgb[2];
+    let m = 0.2119034982 * rgb[0] + 0.6806995451 * rgb[1] + 0.1073969566 * rgb[2];
+    let s = 0.0883024619 * rgb[0] + 0.2817188376 * rgb[1] + 0.6299787005 * rgb[2];
+
+    let l_ = l.max(0.0).powf(1.0 / 3.0);
+    let m_ = m.max(0.0).powf(1.0 / 3.0);
+    let s_ = s.max(0.0).powf(1.0 / 3.0);
+
+    [
+        0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
+        1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
+        0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
+    ]
+}
+
+pub fn oklab_to_linear_srgb(lab: [f32; 3]) -> [f32; 3] {
+    let l_ = lab[0] + 0.3963377774 * lab[1] + 0.2158037573 * lab[2];
+    let m_ = lab[0] - 0.1055613458 * lab[1] - 0.0638541728 * lab[2];
+    let s_ = lab[0] - 0.0894841775 * lab[1] - 1.2914855480 * lab[2];
+
+    let l = l_ * l_ * l_;
+    let m = m_ * m_ * m_;
+    let s = s_ * s_ * s_;
+
+    [
+        4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
+        -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
+        -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
+    ]
+}
+
+/// The alpha that makes an overlay's fade-out read as an EVEN fade to the eye.
+///
+/// `t` runs 0 (overlay at `max_alpha`) to 1 (fully faded); `overlay` and `backdrop` are
+/// linear-light RGB, the backdrop being whatever the fade composites onto.
+///
+/// A linear alpha ramp is not a linear fade: the surface is an sRGB attachment, so the GPU
+/// blends in LINEAR light, and perceived lightness goes as roughly the cube root of that. A
+/// straight alpha ramp therefore hangs bright through the middle and then dives near the
+/// end. This walks perceived lightness linearly instead and solves back for the alpha that
+/// lands on it — exactly, since the composite is linear in alpha:
+///
+/// ```text
+/// Y(a) = a·Y_overlay + (1 - a)·Y_backdrop        (blending, in linear light)
+/// L    ≈ cbrt(Y)                                 (perception — OKLab's L, and CIE L* to within a hair)
+/// ```
+///
+/// Solved on luminance rather than per channel because a single alpha can only satisfy one
+/// axis, and lightness is the one the eye reads a fade by. With no lightness contrast to
+/// shape (a fade between equally-bright colors) it falls back to the linear ramp.
+pub fn perceptual_fade_alpha(t: f32, overlay: [f32; 3], backdrop: [f32; 3], max_alpha: f32) -> f32 {
+    let t = t.clamp(0.0, 1.0);
+    let luminance = |c: [f32; 3]| 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
+    let y_over = luminance(overlay);
+    let y_back = luminance(backdrop);
+    let contrast = y_over - y_back;
+    if contrast.abs() < 1e-4 {
+        return max_alpha * (1.0 - t);
+    }
+    // Walk L from the fully-applied composite down to the bare backdrop.
+    let l = |y: f32| y.max(0.0).cbrt();
+    let y_full = max_alpha * y_over + (1.0 - max_alpha) * y_back;
+    let l_t = l(y_full) + (l(y_back) - l(y_full)) * t;
+    let y_t = l_t * l_t * l_t;
+    ((y_t - y_back) / contrast).clamp(0.0, max_alpha)
+}
+
+pub fn sidebar_bg_color() -> [f32; 4] {
+    load_colors_once();
+    let mut color = *SIDEBAR_BG_COLOR.read().unwrap();
+    if let Some(opacity) = read_opacity_if_configured() {
+        color[3] = opacity;
+    }
+    color
+}
+
+pub fn set_sidebar_bg_color(color: [f32; 4]) {
+    style_write(&SIDEBAR_BG_COLOR, color);
+}
+
+pub fn highlight_primary_color() -> [f32; 4] {
+    load_colors_once();
+    style_read(&HIGHLIGHT_PRIMARY_COLOR)
+}
+
+pub fn set_highlight_primary_color(color: [f32; 4]) {
+    if let Ok(mut lock) = HIGHLIGHT_PRIMARY_COLOR.write() {
+        *lock = [color[0], color[1], color[2], 0.12];
+    }
+}
+
+pub fn menubar_tab_label_color() -> [f32; 4] {
+    load_colors_once();
+    style_read(&MENUBAR_TAB_LABEL_COLOR)
+}
+
+pub fn set_menubar_tab_label_color(color: [f32; 4]) {
+    style_write(&MENUBAR_TAB_LABEL_COLOR, color);
+}
diff --git a/src/color.rs b/src/color/mod.rs
similarity index 59%
rename from src/color.rs
rename to src/color/mod.rs
index c98090f..3ebb2db 100644
--- a/src/color.rs
+++ b/src/color/mod.rs
@@ -1,3 +1,12 @@
+mod load;
+pub use load::*;
+mod math;
+pub use math::*;
+mod chords;
+pub use chords::*;
+mod materials;
+pub use materials::*;
+
 pub const HEADER_BG: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
 pub const HEADER_ACCENT: [f32; 4] = [0.60, 0.40, 0.20, 1.0];
 pub const SIDEBAR_BG: [f32; 4] = [0.10, 0.10, 0.13, 1.0];
@@ -260,604 +269,6 @@ pub fn set_node_drag_color(color: [f32; 4]) {
     style_write(&NODE_DRAG_COLOR, color);
 }
 
-fn read_config() -> Option<String> {
-    let path = crate::config::get_config_path();
-    if let Ok(content) = std::fs::read_to_string(&path) {
-        return Some(content);
-    }
-    None
-}
-
-fn parse_and_set_colors(content: &str) {
-    let val = crate::config::parse_kdl_to_json(content);
-
-    let parse_hex = |hex_str: &str| -> Option<[f32; 4]> {
-        let hex = hex_str.trim_matches(|c| c == '"' || c == '\'' || c == ' ');
-        let hex = hex.trim_start_matches('#');
-        if hex.len() >= 8 {
-            if let (Ok(r), Ok(g), Ok(b), Ok(a)) = (
-                u8::from_str_radix(&hex[0..2], 16),
-                u8::from_str_radix(&hex[2..4], 16),
-                u8::from_str_radix(&hex[4..6], 16),
-                u8::from_str_radix(&hex[6..8], 16),
-            ) {
-                let r_f = srgb_to_linear(r as f32 / 255.0);
-                let g_f = srgb_to_linear(g as f32 / 255.0);
-                let b_f = srgb_to_linear(b as f32 / 255.0);
-                let a_f = a as f32 / 255.0;
-                return Some([r_f, g_f, b_f, a_f]);
-            }
-        } else if hex.len() >= 6 {
-            if let (Ok(r), Ok(g), Ok(b)) = (
-                u8::from_str_radix(&hex[0..2], 16),
-                u8::from_str_radix(&hex[2..4], 16),
-                u8::from_str_radix(&hex[4..6], 16),
-            ) {
-                let r_f = srgb_to_linear(r as f32 / 255.0);
-                let g_f = srgb_to_linear(g as f32 / 255.0);
-                let b_f = srgb_to_linear(b as f32 / 255.0);
-                return Some([r_f, g_f, b_f, 1.0]);
-            }
-        }
-        None
-    };
-
-    let get_color = |pointer: &str| -> Option<[f32; 4]> {
-        val.pointer(pointer).and_then(|v| v.as_str()).and_then(parse_hex)
-    };
-
-    let get_color_u8 = |pointer: &str| -> Option<[u8; 3]> {
-        val.pointer(pointer).and_then(|v| v.as_str()).and_then(|hex_str| {
-            let hex = hex_str.trim_matches(|c| c == '"' || c == '\'' || c == ' ');
-            let hex = hex.trim_start_matches('#');
-            if hex.len() >= 6 {
-                if let (Ok(r), Ok(g), Ok(b)) = (
-                    u8::from_str_radix(&hex[0..2], 16),
-                    u8::from_str_radix(&hex[2..4], 16),
-                    u8::from_str_radix(&hex[4..6], 16),
-                ) {
-                    return Some([r, g, b]);
-                }
-            }
-            None
-        })
-    };
-
-    if let Some(opacity) = val.pointer("/layout/menubar_opacity").and_then(|v| v.as_f64()) {
-        if let Ok(mut lock) = OPACITY.write() {
-            *lock = Some(opacity as f32);
-        }
-    }
-
-    // `/style/surface/plate/root/...` is the one spelling of the root-plate
-    // style (RFC Phase 7a; the legacy `root plate` read-alias was removed
-    // 2026-09-06 once every live config had migrated).
-    if let Some(radius) = val.pointer("/style/surface/plate/root/corner_radius")
-        .and_then(|v| v.as_f64())
-    {
-        if let Ok(mut lock) = ROOT_PLATE_CORNER_RADIUS.write() {
-            *lock = radius as f32;
-        }
-     }
-
-    if let Some(c) = get_color("/style/surface/plate/root/color") {
-        if let Ok(mut lock) = PAGE_LOW_COLOR.write() { *lock = c; }
-        if let Ok(mut lock) = ROOT_PLATE_OPACITY.write() { *lock = Some(c[3]); }
-    }
-
-    if let Some(c) = get_color("/style/surface/plate/root/menubar/color") {
-        if let Ok(mut lock) = ROOT_PLATE_MENUBAR_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/plate/root/menubar/text_color") {
-        if let Ok(mut lock) = ROOT_PLATE_MENUBAR_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(o) = val.pointer("/style/surface/menu/opacity").and_then(|v| v.as_f64()) {
-        if let Ok(mut lock) = MENU_OPACITY.write() { *lock = (o as f32).clamp(0.0, 1.0); }
-    }
-    if let Some(c) = val.pointer("/style/surface/menu/compression").and_then(|v| v.as_f64()) {
-        if let Ok(mut lock) = MENU_COMPRESSION.write() { *lock = (c as f32).clamp(0.0, 1.0); }
-    }
-    if let Some(c) = get_color("/style/surface/menu/color") {
-        if let Ok(mut lock) = MENU_COLOR.write() { *lock = Some(c); }
-    }
-
-    let menubar_blur_ptr = val.pointer("/style/surface/plate/root/menubar/blur");
-    if let Some(blur) = menubar_blur_ptr.and_then(|v| v.as_bool()) {
-        if let Ok(mut lock) = ROOT_PLATE_MENUBAR_BLUR.write() { *lock = blur; }
-    } else if let Some(blur_val) = menubar_blur_ptr.and_then(|v| v.as_f64()) {
-        if let Ok(mut lock) = ROOT_PLATE_MENUBAR_BLUR.write() { *lock = blur_val > 0.001; }
-    }
-
-    if let Some(c) = get_color("/style/surface/statusbar/color") {
-        if let Ok(mut lock) = ROOT_PLATE_STATUSBAR_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/statusbar/text_color") {
-        if let Ok(mut lock) = ROOT_PLATE_STATUSBAR_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(blur) = val.pointer("/style/surface/statusbar/blur").and_then(|v| v.as_bool()) {
-        if let Ok(mut lock) = ROOT_PLATE_STATUSBAR_BLUR.write() { *lock = blur; }
-    } else if let Some(blur_val) = val.pointer("/style/surface/statusbar/blur").and_then(|v| v.as_f64()) {
-        if let Ok(mut lock) = ROOT_PLATE_STATUSBAR_BLUR.write() { *lock = blur_val > 0.001; }
-    }
-    if let Some(c) = get_color("/layout/color_borders_color") {
-        if let Ok(mut lock) = COLOR_BORDERS_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/layout/paginator_sidebar_color") {
-        if let Ok(mut lock) = SIDEBAR_BG_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/highlight/primary") {
-        if let Ok(mut lock) = HIGHLIGHT_PRIMARY_COLOR.write() { *lock = [c[0], c[1], c[2], 0.12]; }
-    }
-    if let Some(c) = get_color("/style/control/button/background").or_else(|| get_color("/layout/button_background_color")) {
-        if let Ok(mut lock) = BUTTON_BACKGROUND_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/dropdown/color") {
-        if let Ok(mut lock) = DROPDOWN_BACKGROUND_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color_u8("/style/control/textbox/placeholder_text_color")
-        .or_else(|| get_color_u8("/style/textbox/placeholder_text_color"))
-        .or_else(|| get_color_u8("/style/data/textbox/placeholder_text_color")) {
-        if let Ok(mut lock) = TEXTBOX_PLACEHOLDER_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/textbox/background_color")
-        .or_else(|| get_color("/style/textbox/background_color"))
-        .or_else(|| get_color("/style/data/textbox/background_color")) {
-        if let Ok(mut lock) = TEXTBOX_BACKGROUND_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/textbox/background_edit_color")
-        .or_else(|| get_color("/style/textbox/background_edit_color"))
-        .or_else(|| get_color("/style/data/textbox/background_edit_color")) {
-        if let Ok(mut lock) = TEXTBOX_BACKGROUND_EDIT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/layout/menubar_tab_label_color").or_else(|| get_color("/layout/paginator_tab_label_color")) {
-        if let Ok(mut lock) = MENUBAR_TAB_LABEL_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/label/color") {
-        if let Ok(mut lock) = CONTROL_LABEL_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/label/color_detached") {
-        if let Ok(mut lock) = CONTROL_LABEL_COLOR_DETACHED.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/label/hover_color") {
-        if let Ok(mut lock) = CONTROL_LABEL_HOVER_COLOR.write() { *lock = Some(c); }
-    }
-    if let Some(c) = get_color("/style/control/label/focus_color") {
-        if let Ok(mut lock) = CONTROL_LABEL_FOCUS_COLOR.write() { *lock = Some(c); }
-    }
-    // The toggle state colors (enabled/disabled/gradient/background) are not
-    // read: the control inherits its plate — see the note by the retired
-    // getters below.
-    if let Some(c) = get_color("/style/control/ramp/background") {
-        if let Ok(mut lock) = RAMP_BACKGROUND_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/ramp/border_color") {
-        if let Ok(mut lock) = RAMP_BORDER_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/control_panel/color") {
-        if let Ok(mut lock) = CONTROL_PANEL_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/control_panel/border_color") {
-        if let Ok(mut lock) = CONTROL_PANEL_BORDER_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/spinbox/background_color") {
-        if let Ok(mut lock) = SPINBOX_DISPLAY_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/spinbox/button_color") {
-        if let Ok(mut lock) = SPINBOX_BUTTON_COLOR.write() { *lock = c; }
-        if let Ok(mut lock_hover) = SPINBOX_BUTTON_HOVER_COLOR.write() {
-            *lock_hover = [
-                (c[0] + 0.1).min(1.0),
-                (c[1] + 0.1).min(1.0),
-                (c[2] + 0.1).min(1.0),
-                c[3]
-            ];
-        }
-    }
-    if let Some(c) = get_color("/style/control/spinbox/button_hover_color") {
-        if let Ok(mut lock) = SPINBOX_BUTTON_HOVER_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/spinbox/text_color") {
-        if let Ok(mut lock) = SPINBOX_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/button/border_color").or_else(|| get_color("/style/button/border_color")) {
-        if let Ok(mut lock) = BUTTON_BORDER_COLOR.write() { *lock = Some(c); }
-    }
-    if let Some(c) = get_color("/style/control/button/hover_color").or_else(|| get_color("/style/button/hover_color")) {
-        if let Ok(mut lock) = BUTTON_HOVER_COLOR.write() { *lock = Some(c); }
-    }
-    if let Some(c) = get_color("/style/control/dropdown/border_color").or_else(|| get_color("/style/dropdown/border_color")) {
-        if let Ok(mut lock) = DROPDOWN_BORDER_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/dropdown/text_color").or_else(|| get_color("/style/dropdown/text_color")) {
-        if let Ok(mut lock) = DROPDOWN_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/slider/thumb_color").or_else(|| get_color("/style/slider/thumb_color")) {
-        if let Ok(mut lock) = SLIDER_THUMB_COLOR.write() { *lock = c; }
-        if let Ok(mut lock_drag) = SLIDER_THUMB_DRAG_COLOR.write() {
-            *lock_drag = [
-                (c[0] + 0.15).min(1.0),
-                (c[1] + 0.15).min(1.0),
-                (c[2] + 0.15).min(1.0),
-                c[3]
-            ];
-        }
-    }
-    if let Some(c) = get_color("/style/control/rangeslider/thumb_color").or_else(|| get_color("/style/rangeslider/thumb_color")) {
-        if let Ok(mut lock) = RANGE_SLIDER_THUMB_COLOR.write() { *lock = c; }
-        if let Ok(mut lock_drag) = RANGE_SLIDER_THUMB_DRAG_COLOR.write() {
-            *lock_drag = [
-                (c[0] + 0.15).min(1.0),
-                (c[1] + 0.15).min(1.0),
-                (c[2] + 0.15).min(1.0),
-                c[3]
-            ];
-        }
-    }
-    if let Some(c) = get_color("/style/control/progressbar/background") {
-        if let Ok(mut lock) = PROGRESS_BG_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/progressbar/fill") {
-        if let Ok(mut lock) = PROGRESS_FILL_COLOR.write() { *lock = c; }
-    }
-    let parsed_list_bg = get_color("/layout/list_bg_color");
-    let parsed_breadcrumb_bg = get_color("/layout/breadcrumb_bg_color");
-    let parsed_popover_bg = get_color("/layout/popover_bg_color");
-
-    if let Some(c) = get_color("/layout/page_color") {
-        if let Ok(mut lock) = PAGE_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/layout/layer_color") {
-        if let Ok(mut lock) = LAYER_COLOR.write() { *lock = c; }
-    }
-
-    if let Some(c) = parsed_list_bg {
-        if let Ok(mut lock) = LIST_BG_COLOR.write() {
-            *lock = [c[0], c[1], c[2], 0.3];
-        }
-    }
-    if let Some(c) = get_color("/layout/list_entry_bg_color") {
-        if let Ok(mut lock) = LIST_ENTRY_BG_COLOR.write() {
-            *lock = c;
-        }
-    }
-    if let Some(c) = get_color("/layout/list_entry_highlight_color") {
-        if let Ok(mut lock) = LIST_ENTRY_HIGHLIGHT_COLOR.write() {
-            *lock = c;
-        }
-    }
-    if let Some(c) = get_color("/style/data/list/font_color").or_else(|| get_color("/layout/list_font_color")) {
-        if let Ok(mut lock) = LIST_FONT_COLOR.write() {
-            *lock = c;
-        }
-    }
-    if let Some(c) = parsed_breadcrumb_bg {
-        if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
-            *lock = [c[0], c[1], c[2], 1.0];
-        }
-    } else if let Some(c) = parsed_list_bg {
-        if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
-            *lock = [c[0], c[1], c[2], 1.0];
-        }
-    }
-    if let Some(c) = parsed_popover_bg {
-        if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
-            *lock = [c[0], c[1], c[2], 1.0];
-        }
-    } else if let Some(c) = parsed_list_bg {
-        if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
-            *lock = [c[0], c[1], c[2], 1.0];
-        }
-    }
-
-    // The lattice's lines. The cells between them have no colour of their
-    // own: a graph shows whatever it is painted on. `cell_color` and
-    // `gap_color`, the cell model's pair, are retired (`retired_surface_keys`).
-    if let Some(c) = get_color("/style/surface/graph/grid_color") {
-        if let Ok(mut lock) = GRAPH_GRID_COLOR.write() { *lock = [c[0], c[1], c[2]]; }
-    }
-    if let Some(c) = get_color("/style/surface/graph/node/color") {
-        if let Ok(mut lock) = GRAPH_NODE_COLOR.write() { *lock = c; }
-        if let Ok(mut lock) = NODE_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/graph/node/selected_color") {
-        if let Ok(mut lock) = GRAPH_NODE_SELECTED_COLOR.write() { *lock = c; }
-        if let Ok(mut lock) = NODE_SELECTED_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/graph/node/drag_color") {
-        if let Ok(mut lock) = GRAPH_NODE_DRAG_COLOR.write() { *lock = c; }
-        if let Ok(mut lock) = NODE_DRAG_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/graph/node/wire_color") {
-        if let Ok(mut lock) = GRAPH_WIRE_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/graph/node/wire_highlight_color") {
-        if let Ok(mut lock) = GRAPH_WIRE_HIGHLIGHT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/graph/node/connector_color") {
-        if let Ok(mut lock) = GRAPH_CONNECTOR_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/graph/node/connector_highlight_color") {
-        if let Ok(mut lock) = GRAPH_CONNECTOR_HIGHLIGHT_COLOR.write() { *lock = c; }
-    }
-    if let Some(opacity) = val.pointer("/style/surface/graph/opacity").and_then(|v| v.as_f64()) {
-        if let Ok(mut lock) = GRAPH_OPACITY.write() { *lock = opacity as f32; }
-    }
-    if let Some(opacity) = val.pointer("/style/surface/graph/node/opacity").and_then(|v| v.as_f64()) {
-        if let Ok(mut lock) = GRAPH_NODE_OPACITY.write() { *lock = opacity as f32; }
-    }
-
-    if let Some(c) = get_color("/style/data/tree/background_color") {
-        if let Ok(mut lock) = TREE_BACKGROUND_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/border_color") {
-        if let Ok(mut lock) = TREE_BORDER_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/border_hover_color") {
-        if let Ok(mut lock) = TREE_BORDER_HOVER_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/border_focus_color") {
-        if let Ok(mut lock) = TREE_BORDER_FOCUS_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/section_bg_color") {
-        if let Ok(mut lock) = TREE_SECTION_BG_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/section_bg_hover_color") {
-        if let Ok(mut lock) = TREE_SECTION_BG_HOVER_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/leaf_bg_even_color") {
-        if let Ok(mut lock) = TREE_LEAF_BG_EVEN_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/leaf_bg_odd_color") {
-        if let Ok(mut lock) = TREE_LEAF_BG_ODD_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/leaf_bg_hover_color") {
-        if let Ok(mut lock) = TREE_LEAF_BG_HOVER_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/leaf_bg_selected_color") {
-        if let Ok(mut lock) = TREE_LEAF_BG_SELECTED_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/section_text_color") {
-        if let Ok(mut lock) = TREE_SECTION_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/leaf_text_color") {
-        if let Ok(mut lock) = TREE_LEAF_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/leaf_text_selected_color") {
-        if let Ok(mut lock) = TREE_LEAF_TEXT_SELECTED_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/type_text_color") {
-        if let Ok(mut lock) = TREE_TYPE_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/value_text_color") {
-        if let Ok(mut lock) = TREE_VALUE_TEXT_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/data/tree/separator_color") {
-        if let Ok(mut lock) = TREE_SEPARATOR_COLOR.write() { *lock = c; }
-    }
-    if let Some(k) = val.pointer("/style/data/tree/open_search").and_then(|v| v.as_str()) {
-        if let Ok(mut lock) = TREE_OPEN_SEARCH_KEY.write() { *lock = k.to_string(); }
-    }
-    if let Some(k) = val.pointer("/style/data/list/open_search").and_then(|v| v.as_str()) {
-        if let Ok(mut lock) = LIST_OPEN_SEARCH_KEY.write() { *lock = k.to_string(); }
-    }
-    if let Some(k) = val.pointer("/style/data/list/close_search").and_then(|v| v.as_str()) {
-        if let Ok(mut lock) = LIST_CLOSE_SEARCH_KEY.write() { *lock = k.to_string(); }
-    }
-    if let Some(c) = get_color("/style/control/scrollbar/track_color") {
-        if let Ok(mut lock) = SCROLLBAR_TRACK_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/control/scrollbar/thumb_color") {
-        if let Ok(mut lock) = SCROLLBAR_THUMB_COLOR.write() { *lock = c; }
-    }
-    if let Some(c) = get_color("/style/surface/plate/color") {
-        if let Ok(mut lock) = PLATE_COLOR.write() { *lock = Some(c); }
-    }
-    if let Some(c) = get_color("/style/surface/plate/border_color") {
-        if let Ok(mut lock) = PLATE_BORDER_COLOR.write() { *lock = Some(c); }
-    }
-    if let Some(t) = val.pointer("/style/surface/plate/border_thickness").and_then(|v| v.as_f64()) {
-        if let Ok(mut lock) = PLATE_BORDER_THICKNESS.write() { *lock = t as f32; }
-    }
-    if let Some(c) = get_color("/style/surface/param/color") {
-        if let Ok(mut lock) = PARAM_BG_COLOR.write() { *lock = c; }
-        if let Ok(mut lock) = PANE_COLOR_WHOLE.write() { *lock = false; }
-    }
-    // The clear spelling of the same tint: the PANE plate's colour, whole —
-    // its alpha is the tint strength and nothing multiplies it.
-    if let Some(c) = get_color("/style/surface/plate/pane/color") {
-        if let Ok(mut lock) = PARAM_BG_COLOR.write() { *lock = c; }
-        if let Ok(mut lock) = PANE_COLOR_WHOLE.write() { *lock = true; }
-    }
-    // The default plate's frost is ONE block, the same shape a named
-    // material's `frost` child has: `plate { frost radius=5.5 compression=0
-    // refraction=0 }` is frosted with those knobs, a bare `frost` is frosted
-    // at the defaults, `frost (bool)false` is not frosted. The four scattered
-    // keys it replaced on 2026-09-28 (`blur` as the switch, `radius`,
-    // `backdrop_compression`, `refraction`) were read as aliases for the rest
-    // of that day and are retired: a config still carrying one is reported
-    // (`retired_surface_keys`) and the key does nothing, because an alias that
-    // keeps working is a second spelling forever. The block is absent → not
-    // frosted, which is what `blur=true` alone now amounts to; the report
-    // is what tells the user why their plates went sharp.
-    let retired = retired_surface_keys(&val);
-    if !retired.is_empty() {
-        log::warn!(
-            "retired style.surface keys in config: {} — the frost is `plate {{ frost radius= compression= refraction= }}` (a material's `frost` child spells `compression`), every roll's width is `relief width=`, the light strength is `relief light=` (a material's `finish light=`), and the relief's geometry is `relief {{ wall height= profile= ; edge height= profile= }}` (the window_manager bevel_* spellings are the same relief keys, and its bevel_shader is `relief shader=`); a graph's lines are `graph {{ grid_color }}` and its cells take no colour",
-            retired.join(", ")
-        );
-    }
-    match val.pointer("/style/surface/plate/frost") {
-        Some(serde_json::Value::Object(fo)) => {
-            let num = |k: &str| fo.get(k).and_then(|v| v.as_f64()).map(|v| v as f32);
-            if let Ok(mut lock) = PLATE_BLUR.write() { *lock = true; }
-            if let Some(c) = num("compression") {
-                if let Ok(mut lock) = PLATE_BACKDROP_COMPRESSION.write() { *lock = c.clamp(0.0, 1.0); }
-            }
-            if let Some(r) = num("refraction") {
-                if let Ok(mut lock) = PLATE_REFRACTION.write() { *lock = r.clamp(0.0, 1.0); }
-            }
-            if let Some(r) = num("radius") {
-                if let Ok(mut lock) = PLATE_FROST_RADIUS.write() { *lock = r.max(0.0); }
-            }
-        }
-        Some(serde_json::Value::Null) => {
-            if let Ok(mut lock) = PLATE_BLUR.write() { *lock = true; }
-        }
-        Some(serde_json::Value::Bool(on)) => {
-            if let Ok(mut lock) = PLATE_BLUR.write() { *lock = *on; }
-        }
-        _ => {}
-    }
-
-    // The DE finish beyond its strength (`relief.depth` / `light`, which
-    // lives in the style registry): the three terms that were literals.
-    let f = |k: &str| val.pointer(&format!("/style/surface/relief/{k}")).and_then(|v| v.as_f64()).map(|v| v as f32);
-    if let Some(v) = f("spec") {
-        if let Ok(mut lock) = FINISH_SPEC.write() { *lock = v.max(0.0); }
-    }
-    if let Some(v) = f("shininess") {
-        if let Ok(mut lock) = FINISH_SHININESS.write() { *lock = v.max(1.0); }
-    }
-    if let Some(v) = f("curvature") {
-        if let Ok(mut lock) = FINISH_CURVATURE.write() { *lock = v.max(0.0); }
-    }
-
-    // Named materials and the rung bindings (RFC material § 5), replaced
-    // wholesale so a reload forgets what config no longer says.
-    {
-        use crate::scene::material::{FrostDef, MaterialDef};
-        let num = |v: Option<&serde_json::Value>| v.and_then(|v| v.as_f64()).map(|v| v as f32);
-        let mut map: Vec<(String, MaterialDef)> = Vec::new();
-        if let Some(obj) = val.pointer("/style/surface/material").and_then(|v| v.as_object()) {
-            for (name, node) in obj {
-                let Some(node) = node.as_object() else { continue };
-                let frost = node.get("frost").map(|fr| match fr.as_object() {
-                    Some(fo) => FrostDef {
-                        compression: num(fo.get("compression")),
-                        refraction: num(fo.get("refraction")),
-                        radius: num(fo.get("radius")),
-                    },
-                    // A bare `frost` node (no knobs) is frosted at the defaults.
-                    None => FrostDef::default(),
-                });
-                let fin = node.get("finish").and_then(|v| v.as_object());
-                let fk = |k: &str| fin.and_then(|fo| num(fo.get(k)));
-                map.push((
-                    name.clone(),
-                    MaterialDef {
-                        tint: node.get("color").and_then(|v| v.as_str()).and_then(parse_hex),
-                        frost,
-                        light: fk("light"),
-                        spec: fk("spec"),
-                        shininess: fk("shininess"),
-                        curvature: fk("curvature"),
-                    },
-                ));
-            }
-        }
-        if let Ok(mut lock) = NAMED_MATERIALS.write() { *lock = map; }
-        let bind = |p: &str| val.pointer(p).and_then(|v| v.as_str()).map(|s| s.to_string()).filter(|s| !s.is_empty());
-        let bindings = [
-            bind("/style/surface/plate/root/material"),
-            bind("/style/surface/plate/material"),
-            bind("/style/control/material"),
-        ];
-        if let Ok(mut lock) = MATERIAL_BINDINGS.write() { *lock = bindings; }
-    }
-}
-
-/// The retired `style.surface` spellings a parsed config still carries, as
-/// the dotted paths a user would grep for: the four flat frost keys under
-/// `plate` (`blur`, `radius`, `backdrop_compression`, `refraction`),
-/// `backdrop_compression` inside any `frost` child (the plate's, or a named
-/// material's), whose one name is `compression`; `plate.bevel_width`, the
-/// pane roll's former width of its own (every roll is `relief.width`); and
-/// `relief.depth` with `depth` inside any material's `finish` child, the
-/// light strength's former name (`light`, since it is not a length); and
-/// the relief's flat geometry keys `height` / `profile` (the wall's, now
-/// `wall.height` / `wall.profile`) and `edge_height` / `edge_profile` (the
-/// edge's, now `edge.height` / `edge.profile`); and `window_manager.bevel_depth`
-/// / `bevel_width` / `bevel_shader`, the block the relief keys were born in
-/// before `style.surface.relief` existed (the compositor never read them;
-/// the shader toggle is `relief.shader` now). Each was an alias for part
-/// of 2026-09-28 and is not read now. And the graph's `cell_color`,
-/// `gap_color` and `uniform_background` (2026-09-29): a graph's cells have
-/// no colour, so there is no fill to switch, and its lines are
-/// `graph.grid_color`. Empty for a clean config.
-pub fn retired_surface_keys(val: &serde_json::Value) -> Vec<String> {
-    let mut found = Vec::new();
-    for k in ["blur", "radius", "backdrop_compression", "refraction", "bevel_width"] {
-        if val.pointer(&format!("/style/surface/plate/{k}")).is_some() {
-            found.push(format!("style.surface.plate.{k}"));
-        }
-    }
-    if val.pointer("/style/surface/plate/frost/backdrop_compression").is_some() {
-        found.push("style.surface.plate.frost.backdrop_compression".to_string());
-    }
-    for k in ["depth", "height", "profile", "edge_height", "edge_profile"] {
-        if val.pointer(&format!("/style/surface/relief/{k}")).is_some() {
-            found.push(format!("style.surface.relief.{k}"));
-        }
-    }
-    for k in ["bevel_depth", "bevel_width", "bevel_shader"] {
-        if val.pointer(&format!("/window_manager/{k}")).is_some() {
-            found.push(format!("window_manager.{k}"));
-        }
-    }
-    if let Some(mats) = val.pointer("/style/surface/material").and_then(|v| v.as_object()) {
-        for (name, node) in mats {
-            if node.pointer("/frost/backdrop_compression").is_some() {
-                found.push(format!("style.surface.material.{name}.frost.backdrop_compression"));
-            }
-            if node.pointer("/finish/depth").is_some() {
-                found.push(format!("style.surface.material.{name}.finish.depth"));
-            }
-        }
-    }
-    for k in ["cell_color", "gap_color", "uniform_background"] {
-        if val.pointer(&format!("/style/surface/graph/{k}")).is_some() {
-            found.push(format!("style.surface.graph.{k}"));
-        }
-    }
-    found
-}
-
-fn load_colors_once() {
-    use std::sync::Once;
-    static INIT: Once = Once::new();
-    INIT.call_once(|| {
-        if let Some(content) = read_config() {
-            parse_and_set_colors(&content);
-        }
-    });
-}
-
-pub fn reload_colors(content: &str) {
-    parse_and_set_colors(content);
-}
-
-/// Test-only: the color state is process-global, so any test that calls
-/// [`reload_colors`] and then asserts getter values races every other such
-/// test on the parallel harness. Each of those tests must hold this lock
-/// across its reload + asserts, and should fire the once-per-process config
-/// loads ([`load_colors_once`] via any getter, and
-/// `layout::lazy_init_style_registry`) inside the lock BEFORE its reload, so
-/// neither can rewrite the state from the live config file mid-assert.
-#[cfg(test)]
-pub(crate) fn test_color_state_lock() -> std::sync::MutexGuard<'static, ()> {
-    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
-    LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
-}
-
-pub fn dropdown_background_color() -> [f32; 4] {
-    load_colors_once();
-    style_read(&DROPDOWN_BACKGROUND_COLOR)
-}
-
-pub fn set_dropdown_background_color(color: [f32; 4]) {
-    style_write(&DROPDOWN_BACKGROUND_COLOR, color);
-}
-
 pub fn textbox_placeholder_text_color() -> [u8; 3] {
     load_colors_once();
     style_read(&TEXTBOX_PLACEHOLDER_TEXT_COLOR)
@@ -1115,143 +526,6 @@ pub fn well_frame_color(hovered: bool, active: bool) -> [f32; 4] {
 pub const TEXT_HEADER: [f32; 4] = [0.90, 0.90, 0.95, 1.0];
 pub const TEXT_ACCENT: [f32; 4] = [0.56, 0.83, 0.56, 1.0];
 
-pub use cce_core::color::{linear_to_srgb, parse_hex_bytes, parse_hex_rgb, parse_hex_rgba, parse_hex_rgba_linear, srgb_to_linear};
-
-pub fn to_linear(color: [f32; 4]) -> [f32; 4] {
-    [
-        srgb_to_linear(color[0]),
-        srgb_to_linear(color[1]),
-        srgb_to_linear(color[2]),
-        color[3],
-    ]
-}
-
-pub fn to_srgb(color: [f32; 4]) -> [f32; 4] {
-    [
-        linear_to_srgb(color[0]),
-        linear_to_srgb(color[1]),
-        linear_to_srgb(color[2]),
-        color[3],
-    ]
-}
-
-pub fn to_linear_rgb(color: [f32; 3]) -> [f32; 3] {
-    [
-        srgb_to_linear(color[0]),
-        srgb_to_linear(color[1]),
-        srgb_to_linear(color[2]),
-    ]
-}
-
-pub fn to_srgb_rgb(color: [f32; 3]) -> [f32; 3] {
-    [
-        linear_to_srgb(color[0]),
-        linear_to_srgb(color[1]),
-        linear_to_srgb(color[2]),
-    ]
-}
-
-pub fn linear_srgb_to_oklab(rgb: [f32; 3]) -> [f32; 3] {
-    let l = 0.4122214708 * rgb[0] + 0.5363325363 * rgb[1] + 0.0514459929 * rgb[2];
-    let m = 0.2119034982 * rgb[0] + 0.6806995451 * rgb[1] + 0.1073969566 * rgb[2];
-    let s = 0.0883024619 * rgb[0] + 0.2817188376 * rgb[1] + 0.6299787005 * rgb[2];
-
-    let l_ = l.max(0.0).powf(1.0 / 3.0);
-    let m_ = m.max(0.0).powf(1.0 / 3.0);
-    let s_ = s.max(0.0).powf(1.0 / 3.0);
-
-    [
-        0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
-        1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
-        0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
-    ]
-}
-
-pub fn oklab_to_linear_srgb(lab: [f32; 3]) -> [f32; 3] {
-    let l_ = lab[0] + 0.3963377774 * lab[1] + 0.2158037573 * lab[2];
-    let m_ = lab[0] - 0.1055613458 * lab[1] - 0.0638541728 * lab[2];
-    let s_ = lab[0] - 0.0894841775 * lab[1] - 1.2914855480 * lab[2];
-
-    let l = l_ * l_ * l_;
-    let m = m_ * m_ * m_;
-    let s = s_ * s_ * s_;
-
-    [
-        4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
-        -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
-        -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
-    ]
-}
-
-/// The alpha that makes an overlay's fade-out read as an EVEN fade to the eye.
-///
-/// `t` runs 0 (overlay at `max_alpha`) to 1 (fully faded); `overlay` and `backdrop` are
-/// linear-light RGB, the backdrop being whatever the fade composites onto.
-///
-/// A linear alpha ramp is not a linear fade: the surface is an sRGB attachment, so the GPU
-/// blends in LINEAR light, and perceived lightness goes as roughly the cube root of that. A
-/// straight alpha ramp therefore hangs bright through the middle and then dives near the
-/// end. This walks perceived lightness linearly instead and solves back for the alpha that
-/// lands on it — exactly, since the composite is linear in alpha:
-///
-/// ```text
-/// Y(a) = a·Y_overlay + (1 - a)·Y_backdrop        (blending, in linear light)
-/// L    ≈ cbrt(Y)                                 (perception — OKLab's L, and CIE L* to within a hair)
-/// ```
-///
-/// Solved on luminance rather than per channel because a single alpha can only satisfy one
-/// axis, and lightness is the one the eye reads a fade by. With no lightness contrast to
-/// shape (a fade between equally-bright colors) it falls back to the linear ramp.
-pub fn perceptual_fade_alpha(t: f32, overlay: [f32; 3], backdrop: [f32; 3], max_alpha: f32) -> f32 {
-    let t = t.clamp(0.0, 1.0);
-    let luminance = |c: [f32; 3]| 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
-    let y_over = luminance(overlay);
-    let y_back = luminance(backdrop);
-    let contrast = y_over - y_back;
-    if contrast.abs() < 1e-4 {
-        return max_alpha * (1.0 - t);
-    }
-    // Walk L from the fully-applied composite down to the bare backdrop.
-    let l = |y: f32| y.max(0.0).cbrt();
-    let y_full = max_alpha * y_over + (1.0 - max_alpha) * y_back;
-    let l_t = l(y_full) + (l(y_back) - l(y_full)) * t;
-    let y_t = l_t * l_t * l_t;
-    ((y_t - y_back) / contrast).clamp(0.0, max_alpha)
-}
-
-pub fn sidebar_bg_color() -> [f32; 4] {
-    load_colors_once();
-    let mut color = *SIDEBAR_BG_COLOR.read().unwrap();
-    if let Some(opacity) = read_opacity_if_configured() {
-        color[3] = opacity;
-    }
-    color
-}
-
-pub fn set_sidebar_bg_color(color: [f32; 4]) {
-    style_write(&SIDEBAR_BG_COLOR, color);
-}
-
-pub fn highlight_primary_color() -> [f32; 4] {
-    load_colors_once();
-    style_read(&HIGHLIGHT_PRIMARY_COLOR)
-}
-
-pub fn set_highlight_primary_color(color: [f32; 4]) {
-    if let Ok(mut lock) = HIGHLIGHT_PRIMARY_COLOR.write() {
-        *lock = [color[0], color[1], color[2], 0.12];
-    }
-}
-
-pub fn menubar_tab_label_color() -> [f32; 4] {
-    load_colors_once();
-    style_read(&MENUBAR_TAB_LABEL_COLOR)
-}
-
-pub fn set_menubar_tab_label_color(color: [f32; 4]) {
-    style_write(&MENUBAR_TAB_LABEL_COLOR, color);
-}
-
 pub fn read_opacity_if_configured() -> Option<f32> {
     load_colors_once();
     style_read(&OPACITY)
@@ -1706,33 +980,6 @@ pub fn set_scrollbar_thumb_color(c: [f32; 4]) {
     style_write(&SCROLLBAR_THUMB_COLOR, c);
 }
 
-pub fn tree_open_search_key() -> String {
-    load_colors_once();
-    let legacy = TREE_OPEN_SEARCH_KEY.read().unwrap().clone();
-    crate::input::widget_chord("open_search", &legacy, "ctrl+f")
-}
-pub fn set_tree_open_search_key(k: String) {
-    style_write(&TREE_OPEN_SEARCH_KEY, k);
-}
-
-pub fn list_open_search_key() -> String {
-    load_colors_once();
-    let legacy = LIST_OPEN_SEARCH_KEY.read().unwrap().clone();
-    crate::input::widget_chord("open_search", &legacy, "ctrl+f")
-}
-pub fn set_list_open_search_key(k: String) {
-    style_write(&LIST_OPEN_SEARCH_KEY, k);
-}
-
-pub fn list_close_search_key() -> String {
-    load_colors_once();
-    let legacy = LIST_CLOSE_SEARCH_KEY.read().unwrap().clone();
-    crate::input::widget_chord("close_search", &legacy, "escape")
-}
-pub fn set_list_close_search_key(k: String) {
-    style_write(&LIST_CLOSE_SEARCH_KEY, k);
-}
-
 pub fn root_plate_menubar_color() -> [f32; 4] {
     load_colors_once();
     style_read(&ROOT_PLATE_MENUBAR_COLOR)
@@ -1918,55 +1165,6 @@ pub fn set_plate_frost_radius(r: f32) {
     style_write(&PLATE_FROST_RADIUS, r.max(0.0));
 }
 
-/// The named materials config defines (`style.surface.material { <name> {…} }`)
-/// and the rung bindings (`plate material=`, `plate { root material= }`,
-/// `control material=`) — see `docs/rfc-material.md` § 5. Replaced wholesale
-/// on every config load, so a node or binding removed from config is gone
-/// after a reload.
-static NAMED_MATERIALS: RwLock<Vec<(String, crate::scene::material::MaterialDef)>> = RwLock::new(Vec::new());
-static MATERIAL_BINDINGS: RwLock<[Option<String>; 3]> = RwLock::new([None, None, None]);
-
-fn rung_index(rung: crate::scene::material::PlateRung) -> usize {
-    match rung {
-        crate::scene::material::PlateRung::Root => 0,
-        crate::scene::material::PlateRung::Pane => 1,
-        crate::scene::material::PlateRung::Control => 2,
-    }
-}
-
-pub fn named_material(name: &str) -> Option<crate::scene::material::MaterialDef> {
-    load_colors_once();
-    style_read(&NAMED_MATERIALS).iter().find(|(n, _)| n == name).map(|(_, d)| d.clone())
-}
-
-/// Every defined material's name, sorted.
-pub fn material_names() -> Vec<String> {
-    load_colors_once();
-    let mut v: Vec<String> = style_read(&NAMED_MATERIALS).into_iter().map(|(n, _)| n).collect();
-    v.sort();
-    v
-}
-
-pub fn set_named_material(name: &str, def: Option<crate::scene::material::MaterialDef>) {
-    let mut list = style_read(&NAMED_MATERIALS);
-    list.retain(|(n, _)| n != name);
-    if let Some(d) = def {
-        list.push((name.to_string(), d));
-    }
-    style_write(&NAMED_MATERIALS, list);
-}
-
-pub fn material_binding(rung: crate::scene::material::PlateRung) -> Option<String> {
-    load_colors_once();
-    style_read(&MATERIAL_BINDINGS)[rung_index(rung)].clone()
-}
-
-pub fn set_material_binding(rung: crate::scene::material::PlateRung, name: Option<String>) {
-    let mut b = style_read(&MATERIAL_BINDINGS);
-    b[rung_index(rung)] = name;
-    style_write(&MATERIAL_BINDINGS, b);
-}
-
 pub fn finish_spec() -> f32 {
     style_read(&FINISH_SPEC)
 }
diff --git a/src/layout/bridge.rs b/src/layout/bridge.rs
new file mode 100644
index 0000000..38f6e41
--- /dev/null
+++ b/src/layout/bridge.rs
@@ -0,0 +1,563 @@
+//! The flat-host render bridge: [`RenderTarget`], the target a host that does not build a
+//! display list paints into, [`render_widget`] / [`render_popovers`] which replay a widget
+//! onto one, and the carve and popover types that cross it. Legacy: new code paints
+//! through `scene::paint::PaintCtx`.
+
+use crate::widget::WidgetHost;
+use crate::context::UiContext;
+
+/// One step carve a widget's `paint` draws, handed to a flat-path host through
+/// [`RenderTarget::relief_carve`] so it can re-emit it as a real prim.
+///
+/// Geometry always comes from the WIDGET (`TextBox::well`, `Toggle::well` /
+/// `Toggle::slide_plate`), never re-derived here — a second copy of that math
+/// in the bridge is exactly how the flat host's carve and the drawn one drift
+/// apart.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct ReliefCarve {
+    pub kind: CarveKind,
+    pub x: f32,
+    pub y: f32,
+    pub w: f32,
+    pub h: f32,
+    /// Per-corner radii, clockwise from top-left.
+    pub radii: (f32, f32, f32, f32),
+    /// Full width of the step's transition band.
+    pub depth: f32,
+    /// Which walls the carve has (top, right, bottom, left). A suppressed wall
+    /// means the step runs flush to its neighbour there — a Spinbox's field
+    /// running into its button column, where the two meet in ONE step rather
+    /// than two facing walls.
+    pub edges: (bool, bool, bool, bool),
+}
+
+/// Which way a [`ReliefCarve`] steps.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum CarveKind {
+    /// Interior one step DOWN ([`crate::scene::paint::PaintCtx::recess_edges`]).
+    /// `tint` lights the rim in the focus accent (`recess_tinted`).
+    Recess { tint: Option<[f32; 3]> },
+    /// Interior one step UP ([`crate::scene::paint::PaintCtx::boss_edges`]).
+    Boss { tint: Option<[f32; 3]> },
+    /// A FLUSH inset ([`crate::scene::paint::PaintCtx::trough_edges`]): the
+    /// interior stays level with the surface and a valley seam runs the
+    /// boundary — the closed dropdown's chrome, for a control that is part
+    /// of the plate rather than a step up or down from it.
+    Trough,
+}
+
+impl ReliefCarve {
+    /// This carve shifted vertically — the page-scroll adjustment a host
+    /// applies when it re-emits collected carves.
+    pub fn shifted_y(self, dy: f32) -> Self {
+        Self { y: self.y + dy, ..self }
+    }
+}
+
+pub trait RenderTarget {
+    fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32);
+    fn rect_with_radius(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, _radius: f32) {
+        self.rect(color, x, y, w, h);
+    }
+    fn rect_with_radius_corners(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, _corners: (bool, bool, bool, bool)) {
+        self.rect_with_radius(color, x, y, w, h, radius);
+    }
+    fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
+    fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], _font: &str) {
+        self.text(content, x, y, size, color);
+    }
+    fn text_with_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], _bounds: Option<[f32; 4]>) {
+        self.text(content, x, y, size, color);
+    }
+    fn text_with_font_and_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str, _bounds: Option<[f32; 4]>) {
+        self.text_with_font(content, x, y, size, color, font);
+    }
+    fn push_clip_rect(&mut self, _x: f32, _y: f32, _w: f32, _h: f32) {}
+    fn pop_clip_rect(&mut self) {}
+    /// A bundled cce-icons glyph — see `PaintCtx::icon`. A target that
+    /// cannot draw images (the legacy `PopoverCollector`) draws nothing.
+    fn icon(&mut self, _name: &str, _rect: crate::scene::layout::Rect, _color: [f32; 4]) {}
+    /// A straight stroke — see `PaintCtx::vector`. What a graph's wires are
+    /// made of; a target that draws none (the default) shows no wires.
+    fn line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _thickness: f32, _color: [f32; 4], _cap: crate::scene::paint::Cap) {}
+    /// A stroked arc, `radius` its OUTER edge — see `PaintCtx::arc`. A
+    /// rounded wire's bends.
+    fn arc(&mut self, _cx: f32, _cy: f32, _radius: f32, _thickness: f32, _start: f32, _end: f32, _color: [f32; 4]) {}
+    /// A filled disc — see `PaintCtx::circle`. A graph's ports.
+    fn circle(&mut self, _cx: f32, _cy: f32, _radius: f32, _color: [f32; 4]) {}
+    /// A flush inset control plate ([`PaintCtx::inset_plate`]) — the raised
+    /// control surface (groove ring down, beveled lip back up). Lets a popover
+    /// draw the ACTUAL widget surface expanded (the Dropdown's grown trigger).
+    /// Hosts without relief prims degrade to a flat rounded fill.
+    fn inset_plate(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, _depth: f32) {
+        self.rect_with_radius(color, x, y, w, h, radius);
+    }
+    /// [`inset_plate`](Self::inset_plate) with the rim lit — the focused
+    /// control plate's ring (`ControlPlate::with_tint`). Hosts without relief
+    /// prims draw the plain plate.
+    fn inset_plate_tinted(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32, _tint: [f32; 3]) {
+        self.inset_plate(color, x, y, w, h, radius, depth);
+    }
+    /// One step carve from a widget's `paint` ([`ReliefCarve`]) — offered here
+    /// for the same reason as `inset_plate`: the legacy `all_quads` stream
+    /// carries no relief prims, so a flat-path host never sees them.
+    ///
+    /// The default is deliberately a NO-OP, not a fill. These controls have
+    /// transparent faces by design (the host surface IS the well floor / the
+    /// plate's face), so the carve is their entire decoration — a host that
+    /// can't carve has nothing truthful to draw, and a solid box here would
+    /// paint every text field a flat slab it never had.
+    fn relief_carve(&mut self, _carve: &ReliefCarve) {}
+    /// Whether this host renders sections as sunken wells (the designer idiom).
+    /// `SectionContext` then lays the title out left-aligned over its tab box
+    /// instead of centered on the top border.
+    fn section_relief_style(&self) -> bool {
+        false
+    }
+    /// The section frame hatch: `SectionContext::finish` offers the frame here
+    /// before falling back to the legacy 1px outline. A relief-capable host
+    /// returns true and carves the section into its plate instead (the
+    /// designer sunken-well idiom); the tuple hosts keep the default.
+    fn section_relief(&mut self, _frame: &SectionFrame) -> bool {
+        false
+    }
+}
+
+/// A section frame offered to [`RenderTarget::section_relief`]: the content
+/// body box plus, under [`RenderTarget::section_relief_style`], the title tab
+/// box the label was laid out in — the tab sits flush on the body's top edge
+/// (the designer union-carve shape).
+pub struct SectionFrame {
+    pub x: f32,
+    pub y: f32,
+    pub w: f32,
+    pub h: f32,
+    pub tab: Option<(f32, f32, f32, f32)>,
+    pub focused: bool,
+    pub is_child: bool,
+}
+
+pub struct PopoverCollector {
+    pub rects: Vec<([f32; 4], f32, f32, f32, f32)>,
+    pub texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>, Option<[f32; 4]>)>,
+}
+
+impl PopoverCollector {
+    pub fn new() -> Self {
+        Self { rects: Vec::new(), texts: Vec::new() }
+    }
+}
+
+impl RenderTarget for PopoverCollector {
+    fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
+        self.rects.push((color, x, y, w, h));
+    }
+
+    fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
+        self.texts.push((content.to_string(), size, x, y, color, None, None));
+    }
+
+    fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str) {
+        self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), None));
+    }
+
+    fn text_with_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], bounds: Option<[f32; 4]>) {
+        self.texts.push((content.to_string(), size, x, y, color, None, bounds));
+    }
+
+    fn text_with_font_and_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str, bounds: Option<[f32; 4]>) {
+        self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), bounds));
+    }
+}
+
+
+pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut T, x: f32, y: f32, ww: f32, wh: f32, ctx: &mut UiContext) {
+    ctx.register_host(w);
+    // The flat-host contract, the same block `set_rect` takes: `(x, y)` is the top of
+    // the detached label and `wh` the block height, label strip included. `layout`
+    // takes the CONTENT origin and height, so step down by the strip.
+    let strip = w.label_strip();
+    let content_h = (wh - strip).max(0.0);
+    w.layout(crate::widget::Point { x, y: y + strip }, crate::widget::LayoutConstraints::new(ww, ww, content_h, content_h), ctx);
+
+    // Shape, which on this path nobody else does. A flat host consumes
+    // `all_quads`, so `prepare_text` — where a TextBox records the per-glyph x
+    // offsets its selection highlight, caret and click->index mapping all read
+    // — was never called for the widgets it draws. Those three then fell back
+    // to `measure_text_width("M")`, an SVG-rasterized INKED extent rather than
+    // an advance, so the highlight under-ran the glyphs by a few px per
+    // character (a full glyph by the end of "example.com"). Hosts that shape
+    // for themselves (cce-files, the TreeList) just re-read the shared buffer
+    // cache here.
+    if let Ok(mut fs) = crate::geometry_font_system().lock() {
+        w.prepare_text(&mut fs);
+    }
+    let (style_r, corners) = w.corner_style();
+    let r = if corners != (false, false, false, false) { style_r } else { 0.0 };
+    let (wx, mut wy, www, mut whh) = w.rect();
+    let top_room = w.label_strip();
+    wy += top_room;
+    whh -= top_room;
+
+    // ONE ordered replay of the paint walk — the same walk the live display-
+    // list render runs (`scene::painter`), every prim in the order the widget
+    // painted it, each mapped onto the flat host's RenderTarget surface.
+    //
+    // This used to be three passes over three typed views of the same paint:
+    // the relief prims (downcast per widget type and re-derived from the
+    // widget's accessors — the Dropdown's inset plate, the TextBox's well, the
+    // Button's face, the Toggle's faces and steps), then every plain quad
+    // (`all_quads`), then every rounded quad (`all_rounded_quads`). Splitting
+    // one paint into typed streams loses the order between them, and the
+    // order is the picture: a TextBox draws its rounded background, carves
+    // its well, THEN lays the selection highlight and caret on top — the
+    // three-pass replay put the well under the highlight and the background
+    // over both. A Dropdown's hovered row is drawn after its menu plate; a
+    // host that replays plates after rects buries the highlight. The prim
+    // walk keeps the widget's order, covers every widget instead of the four
+    // that had a special case, and carries the relief prims' own per-corner
+    // radii and depth (the re-derivations rounded those off).
+    //
+    // Mapping onto the tuple surface: Quad and RoundedRect are the two
+    // native fills (the root's plain background keeps its solid-border
+    // expansion and window-corner resolution); a zero-stroke Border is an
+    // inset plate's FACE and is held until the Trough that follows it, so the
+    // pair reaches the host as ONE `inset_plate` call (a relief host carves
+    // it for real; the default degrades to the flat fill); Recess/Boss go to
+    // `relief_carve`; a Bevel degrades to its fill — what a flat host can
+    // draw of a raised plate. Ridges, circles, arcs, vectors and images have
+    // no flat-surface counterpart and are skipped, as they always were.
+    let solid_border = w.solid_border();
+    let mut text_scratch = crate::scene::paint::PaintCtx::new();
+    crate::scene::painter::paint_root_into(&*ctx, &*w, &mut text_scratch);
+
+    // A zero-stroke Border waiting for its Trough: (rect, radii, fill).
+    let mut pending_face: Option<(crate::scene::layout::Rect, crate::scene::paint::Radii, [f32; 4])> = None;
+    fn same_rect(a: crate::scene::layout::Rect, b: crate::scene::layout::Rect) -> bool {
+        (a.x - b.x).abs() < 0.1 && (a.y - b.y).abs() < 0.1 && (a.width - b.width).abs() < 0.1 && (a.height - b.height).abs() < 0.1
+    }
+    fn emit_rounded(pc: &mut dyn RenderTarget, rect: crate::scene::layout::Rect, radii: crate::scene::paint::Radii, color: [f32; 4]) {
+        let (r1, r2, r3, r4) = radii;
+        let radius = r1.max(r2).max(r3).max(r4);
+        let mask = (r1 > 0.0, r2 > 0.0, r3 > 0.0, r4 > 0.0);
+        pc.rect_with_radius_corners(color, rect.x, rect.y, rect.width, rect.height, radius, mask);
+    }
+
+    for item in text_scratch.finish().items {
+        use crate::scene::paint::Prim;
+        let trough_for_face = match (&item.prim, &pending_face) {
+            (Prim::Trough { rect, .. }, Some((face_rect, _, _))) => same_rect(*rect, *face_rect),
+            // `inset_plate`'s edge: a field that is all run.
+            (Prim::Field { rect, split, end, .. }, Some((face_rect, _, _))) => {
+                *split <= rect.x && *end >= rect.x + rect.width && same_rect(*rect, *face_rect)
+            }
+            _ => false,
+        };
+        if !trough_for_face {
+            if let Some((frect, fradii, fill)) = pending_face.take() {
+                emit_rounded(pc, frect, fradii, fill);
+            }
+        }
+        match item.prim {
+            Prim::Quad { rect, color: qc } => {
+                let (qx, qy, qw, qh) = (rect.x, rect.y, rect.width, rect.height);
+                let extra_corners = (
+                    corners.0 && qx <= wx + 1.5 && qy <= wy + 1.5,
+                    corners.1 && qx + qw >= wx + www - 1.5 && qy <= wy + 1.5,
+                    corners.2 && qx + qw >= wx + www - 1.5 && qy + qh >= wy + whh - 1.5,
+                    corners.3 && qx <= wx + 1.5 && qy + qh >= wy + whh - 1.5,
+                );
+
+                let (resolved_r, resolved_corners) = if r <= 0.1 || corners == (false, false, false, false) || extra_corners == (false, false, false, false) {
+                    (0.0, (false, false, false, false))
+                } else {
+                    (r, extra_corners)
+                };
+
+                // The widget's own background quad with a solid border: full-size
+                // border quad, then the inset background over it.
+                let mut border_drawn = false;
+                let is_bg_quad = (qx - wx).abs() < 0.1 && (qy - wy).abs() < 0.1 && (qw - www).abs() < 0.1 && (qh - whh).abs() < 0.1;
+                if is_bg_quad {
+                    if let Some((border_color, thickness)) = solid_border {
+                        if thickness > 0.0 {
+                            pc.rect_with_radius_corners(border_color, qx, qy, qw, qh, resolved_r, resolved_corners);
+                            pc.rect_with_radius_corners(
+                                qc,
+                                qx + thickness,
+                                qy + thickness,
+                                (qw - 2.0 * thickness).max(0.0),
+                                (qh - 2.0 * thickness).max(0.0),
+                                (resolved_r - thickness).max(0.0),
+                                resolved_corners,
+                            );
+                            border_drawn = true;
+                        }
+                    }
+                }
+
+                if !border_drawn {
+                    pc.rect_with_radius_corners(qc, qx, qy, qw, qh, resolved_r, resolved_corners);
+                }
+            }
+            Prim::RoundedRect { rect, radius, corners: qcorners, color: qc } => {
+                pc.rect_with_radius_corners(qc, rect.x, rect.y, rect.width, rect.height, radius, qcorners);
+            }
+            Prim::Border { rect, radii, fill, border, thickness } => {
+                if thickness > 0.0 && border[3].abs() > 0.001 {
+                    emit_rounded(pc, rect, radii, border);
+                    if fill[3].abs() > 0.001 {
+                        let inner = crate::scene::layout::Rect {
+                            x: rect.x + thickness,
+                            y: rect.y + thickness,
+                            width: (rect.width - 2.0 * thickness).max(0.0),
+                            height: (rect.height - 2.0 * thickness).max(0.0),
+                        };
+                        let (r1, r2, r3, r4) = radii;
+                        let shrink = |v: f32| if v > 0.0 { (v - thickness).max(0.0) } else { 0.0 };
+                        emit_rounded(pc, inner, (shrink(r1), shrink(r2), shrink(r3), shrink(r4)), fill);
+                    }
+                } else if fill[3].abs() > 0.001 {
+                    // abs(): a negative alpha is the frost sentinel, a real face.
+                    pending_face = Some((rect, radii, fill));
+                }
+            }
+            // A flat host has no blended outline: the two-box form
+            // `Prim::Field` replaced — the well to the seam, the run's flush
+            // plate past it.
+            Prim::Field { rect, radii, depth, split, end, tint } => {
+                let (fl, fr) = (rect.x, rect.x + rect.width);
+                let (well_l, well_r) = (split > fl, end < fr);
+                // All run and no well (`PaintCtx::inset_plate`'s edge): the
+                // plate alone, with the face that came before it.
+                let face = if !well_l && !well_r { pending_face.take().map(|(_, _, fill)| fill) } else { None }.unwrap_or([0.0; 4]);
+                let rx = split.max(fl);
+                let rw = (end.min(fr) - rx).max(0.0);
+                let mut well = |x: f32, w: f32, radii: (f32, f32, f32, f32)| {
+                    pc.relief_carve(&ReliefCarve {
+                        kind: CarveKind::Recess { tint },
+                        x,
+                        y: rect.y,
+                        w,
+                        h: rect.height,
+                        radii,
+                        depth,
+                        edges: (true, true, true, true),
+                    });
+                };
+                if well_l {
+                    well(fl, rx - fl, (radii.0, 0.0, 0.0, radii.3));
+                }
+                if well_r {
+                    well(rx + rw, fr - rx - rw, (0.0, radii.1, radii.2, 0.0));
+                }
+                let r = if well_r { radii.0.max(radii.3) } else { radii.1.max(radii.2) };
+                match tint {
+                    Some(t) => pc.inset_plate_tinted(face, rx, rect.y, rw, rect.height, r, depth, t),
+                    None => pc.inset_plate(face, rx, rect.y, rw, rect.height, r, depth),
+                }
+            }
+            Prim::Trough { rect, radii, depth, tint, .. } => {
+                let face = pending_face.take().map(|(_, _, fill)| fill).unwrap_or([0.0; 4]);
+                let (r1, r2, r3, r4) = radii;
+                let r = r1.max(r2).max(r3).max(r4);
+                match tint {
+                    Some(t) => pc.inset_plate_tinted(face, rect.x, rect.y, rect.width, rect.height, r, depth, t),
+                    None => pc.inset_plate(face, rect.x, rect.y, rect.width, rect.height, r, depth),
+                }
+            }
+            Prim::Bevel { rect, radii, material, .. } => {
+                let color = material.fill(crate::scene::material::PlateRole::Nested);
+                if color[3].abs() > 0.001 {
+                    emit_rounded(pc, rect, radii, color);
+                }
+            }
+            Prim::Recess { rect, radii, depth, edges, tint } => {
+                pc.relief_carve(&ReliefCarve {
+                    kind: CarveKind::Recess { tint },
+                    x: rect.x,
+                    y: rect.y,
+                    w: rect.width,
+                    h: rect.height,
+                    radii,
+                    depth,
+                    edges,
+                });
+            }
+            Prim::Boss { rect, radii, depth, edges, tint } => {
+                pc.relief_carve(&ReliefCarve {
+                    kind: CarveKind::Boss { tint },
+                    x: rect.x,
+                    y: rect.y,
+                    w: rect.width,
+                    h: rect.height,
+                    radii,
+                    depth,
+                    edges,
+                });
+            }
+            // Text via the paint walk: each widget's Text prims (content font + scroll-ancestor
+            // clip) exactly as the live display-list render does; the prim already carries the
+            // per-widget font + bounds.
+            Prim::Text { text, x, y, font_size, color, font, bounds, .. } => {
+                let color_f32 = [
+                    color[0] as f32 / 255.0,
+                    color[1] as f32 / 255.0,
+                    color[2] as f32 / 255.0,
+                    1.0,
+                ];
+                // Compose the walk's container clip with the prim's own bounds (the engine's dl-text
+                // merge), so a clipping ancestor still bounds the text.
+                let clip = item.clip.map(|c| [c.x, c.y, c.x + c.width, c.y + c.height]);
+                let merged = match (clip, bounds) {
+                    (Some(a), Some(b)) => Some([a[0].max(b[0]), a[1].max(b[1]), a[2].min(b[2]), a[3].min(b[3])]),
+                    (Some(a), None) => Some(a),
+                    (None, b) => b,
+                };
+                match font {
+                    Some(ref f) => pc.text_with_font_and_bounds(&text, x, y, font_size, color_f32, f, merged),
+                    None => pc.text_with_bounds(&text, x, y, font_size, color_f32, merged),
+                }
+            }
+            // Strokes, arcs and discs — a graph's wires and ports, chiefly.
+            // Until 2026-10-06 these were dropped too, so a graph in a flat
+            // host (cce-files' Graph page) drew its nodes and no wires.
+            Prim::Vector { x1, y1, x2, y2, thickness, color, cap } => {
+                if let Some(c) = item.clip {
+                    pc.push_clip_rect(c.x, c.y, c.width, c.height);
+                }
+                pc.line(x1, y1, x2, y2, thickness, color, cap);
+                if item.clip.is_some() {
+                    pc.pop_clip_rect();
+                }
+            }
+            Prim::Arc { cx, cy, radius, thickness, start, end, color } => {
+                if let Some(c) = item.clip {
+                    pc.push_clip_rect(c.x, c.y, c.width, c.height);
+                }
+                pc.arc(cx, cy, radius, thickness, start, end, color);
+                if item.clip.is_some() {
+                    pc.pop_clip_rect();
+                }
+            }
+            Prim::Circle { cx, cy, radius, color } => {
+                if let Some(c) = item.clip {
+                    pc.push_clip_rect(c.x, c.y, c.width, c.height);
+                }
+                pc.circle(cx, cy, radius, color);
+                if item.clip.is_some() {
+                    pc.pop_clip_rect();
+                }
+            }
+            // A widget's GLYPH (a dropdown's arrow, a spinbox's −/+, a menu
+            // mark): drawn by name through the host's `RenderTarget::icon`,
+            // since an image id means nothing to a flat host. Until this arm
+            // every widget glyph vanished from a flat host — the toolkit drew
+            // its symbols as text before 2026-10-05, and this replay dropped
+            // images. An image that is not a bundled glyph is still dropped.
+            Prim::Image { image, rect, alpha } => {
+                if let Some((name, _, tint)) = crate::icon_source(image) {
+                    let [r, g, b] = tint.unwrap_or([255, 255, 255]);
+                    let color = [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, alpha];
+                    if let Some(c) = item.clip {
+                        pc.push_clip_rect(c.x, c.y, c.width, c.height);
+                    }
+                    pc.icon(&name, rect, color);
+                    if item.clip.is_some() {
+                        pc.pop_clip_rect();
+                    }
+                }
+            }
+            _ => {}
+        }
+    }
+    if let Some((frect, fradii, fill)) = pending_face.take() {
+        emit_rounded(pc, frect, fradii, fill);
+    }
+    if w.popover_rect().is_some() {
+        ctx.register_popover(w);
+    }
+}
+
+pub fn render_popovers(pc: &mut dyn RenderTarget, ctx: &UiContext) {
+    for &pop_id in &ctx.active_popovers {
+        if let Some(ptr) = ctx.tree.get_ptr(pop_id) {
+            unsafe {
+                (*ptr).render_popover(pc);
+            }
+        }
+    }
+    // Registry sweep for open popovers the app never registered (popover
+    // registration is optional and spotty) — the same fallback the coverage
+    // check and the engine's outside-press close use.
+    for (id, ptr) in ctx.tree.iter_registered() {
+        if ctx.active_popovers.contains(&id) {
+            continue;
+        }
+        unsafe {
+            if let Some(w) = ptr.as_ref() {
+                if w.visible() && w.popover_rect().is_some() {
+                    w.render_popover(pc);
+                }
+            }
+        }
+    }
+}
+
+pub fn partition_concentric_corners(
+    x: f32, y: f32, w: f32, h: f32,
+    _r_std: f32,
+    r_adjust: [f32; 4],
+    color: [f32; 4],
+) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
+    let mut quads = Vec::new();
+
+    let r0 = r_adjust[0];
+    let r1 = r_adjust[1];
+    let r2 = r_adjust[2];
+    let r3 = r_adjust[3];
+
+    let max_left = r0.max(r3);
+    let max_right = r1.max(r2);
+
+    let mut push_valid_quad = |qx: f32, qy: f32, qw: f32, qh: f32, qr: f32, qcorners: (bool, bool, bool, bool)| {
+        if qw > 0.001 && qh > 0.001 {
+            quads.push((qx, qy, qw, qh, qr, color, qcorners));
+        }
+    };
+
+    // 1. Center vertical block
+    push_valid_quad(x + max_left, y, w - max_left - max_right, h, 0.0, (false, false, false, false));
+
+    // 2. Left block
+    push_valid_quad(x, y + r0, max_left, h - r0 - r3, 0.0, (false, false, false, false));
+
+    // 3. Top-left transition
+    push_valid_quad(x + r0, y, max_left - r0, r0, 0.0, (false, false, false, false));
+
+    // 4. Bottom-left transition
+    push_valid_quad(x + r3, y + h - r3, max_left - r3, r3, 0.0, (false, false, false, false));
+
+    // 5. Right block
+    push_valid_quad(x + w - max_right, y + r1, max_right, h - r1 - r2, 0.0, (false, false, false, false));
+
+    // 6. Top-right transition
+    push_valid_quad(x + w - max_right, y, max_right - r1, r1, 0.0, (false, false, false, false));
+
+    // 7. Bottom-right transition
+    push_valid_quad(x + w - max_right, y + h - r2, max_right - r2, r2, 0.0, (false, false, false, false));
+
+    // 8. Corner 0 (top-left)
+    push_valid_quad(x, y, r0, r0, r0, (true, false, false, false));
+
+    // 9. Corner 1 (top-right)
+    push_valid_quad(x + w - r1, y, r1, r1, r1, (false, true, false, false));
+
+    // 10. Corner 2 (bottom-right)
+    push_valid_quad(x + w - r2, y + h - r2, r2, r2, r2, (false, false, true, false));
+
+    // 11. Corner 3 (bottom-left)
+    push_valid_quad(x, y + h - r3, r3, r3, r3, (false, false, false, true));
+
+    quads
+}
diff --git a/src/layout/legacy.rs b/src/layout/legacy.rs
new file mode 100644
index 0000000..3107722
--- /dev/null
+++ b/src/layout/legacy.rs
@@ -0,0 +1,1959 @@
+//! The legacy layout engines — `Column`, `Row`, `Section`, `Grid`, the `LayoutStrategy`
+//! family (`FlexLayout`, `ColumnLayout`, `AdaptiveGrid`, `RadialLayout`), `PageLayoutBuilder`
+//! and `VStack` — from before `scene::layout`'s box model. Still used by
+//! cce-system-interface, cce-files and cce-gallery; new layout goes through
+//! `scene::layout`.
+
+use super::*;
+use crate::widget::WidgetHost;
+use crate::context::UiContext;
+
+pub struct UiFrame;
+
+impl UiFrame {
+    pub fn start(scroll_offset: f32) -> Self {
+        crate::widget::hover_animation::reset_frame_registration();
+        crate::widget::hover_animation::set_scroll_offset(scroll_offset);
+        Self
+    }
+
+    pub fn finish(self, pc: &mut dyn RenderTarget) {
+        crate::widget::hover_animation::post_render_check();
+        if let Some((qx, qy, qw, qh, qc)) = crate::widget::hover_animation::get_quad() {
+            pc.rect(qc, qx, qy, qw, qh);
+        }
+        // render_popovers(pc);
+    }
+}
+
+pub struct Column {
+    ox: f32,
+    oy: f32,
+    cx: f32,
+    pub y: f32,
+    pub cw: f32,
+}
+
+impl Column {
+    pub fn new(ox: f32, oy: f32, cx: f32, cy: f32, cw: f32) -> Self {
+        Self { ox, oy, cx, y: cy, cw }
+    }
+
+    pub fn ax(&self, x_off: f32) -> f32 {
+        self.ox + self.cx + x_off
+    }
+
+    pub fn ay(&self) -> f32 {
+        self.oy + self.y
+    }
+
+    pub fn rect(&mut self, pc: &mut dyn RenderTarget, color: [f32; 4], x_off: f32, w: f32, h: f32) {
+        pc.rect(color, self.ax(x_off), self.ay(), w, h);
+        self.y += h;
+    }
+
+    pub fn advance(&mut self, dy: f32) {
+        self.y += dy;
+    }
+
+    pub fn spacing(&mut self, dy: f32) {
+        self.y += dy;
+    }
+
+    pub fn separator(&mut self, pc: &mut dyn RenderTarget) {
+        let x = self.ax(8.0);
+        let y = self.ay();
+        pc.rect([0.18, 0.18, 0.27, 1.0], x, y, self.cw - 16.0, 1.0);
+        self.y += 8.0;
+    }
+
+    pub fn header(&mut self, pc: &mut dyn RenderTarget, text: &str, x_off: f32) {
+        let x = self.ax(x_off);
+        let y = self.ay();
+        pc.text(text, x, y, 14.0, [0.83, 0.83, 0.83, 1.0]);
+        self.y += 22.0;
+    }
+
+    pub fn text(&mut self, pc: &mut dyn RenderTarget, text: &str, x_off: f32, y_off: f32, font_size: f32, color: [f32; 4]) {
+        let x = self.ax(x_off);
+        let y = self.ay() + y_off;
+        pc.text(text, x, y, font_size, color);
+    }
+
+    pub fn widget<T: WidgetHost + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, x_off: f32, ww: f32, mut wh: f32, ctx: &mut UiContext) {
+        if let Some(pref) = w.preferred_height() {
+            wh = pref;
+        }
+        let top_room = w.label_strip();
+        let total_h = wh + top_room;
+        let x = self.ax(x_off);
+        let y = self.ay();
+        w.set_row_rect(self.ox + self.cx + 8.0, self.cw - 16.0);
+        let clamped_w = ww.min((self.cw - x_off).max(0.0));
+        render_widget(pc, w, x, y, clamped_w, total_h, ctx);
+        self.y += total_h;
+    }
+
+    pub fn row<F: FnOnce(&mut Row)>(&mut self, pc: &mut dyn RenderTarget, h: f32, f: F) {
+        let row_y = self.ay();
+        let mut row = Row {
+            pc: &mut *pc,
+            base_x: self.ox + self.cx,
+            y: row_y,
+            cursor_x: 0.0,
+            spacing: CONTROL_GAP,
+        };
+        f(&mut row);
+        self.y = self.y + h;
+    }
+}
+
+pub struct Row<'a> {
+    pc: &'a mut dyn RenderTarget,
+    base_x: f32,
+    y: f32,
+    pub cursor_x: f32,
+    pub spacing: f32,
+}
+
+impl<'a> Row<'a> {
+    pub fn set_spacing(&mut self, spacing: f32) {
+        self.spacing = spacing;
+    }
+
+    pub fn gap(&mut self, width: f32) {
+        self.cursor_x += width;
+    }
+
+    pub fn text(&mut self, text: &str, y_off: f32, font_size: f32, color: [f32; 4], width: f32) {
+        self.pc
+            .text(text, self.base_x + self.cursor_x, self.y + y_off, font_size, color);
+        self.cursor_x += width + self.spacing;
+    }
+
+    pub fn widget<T: WidgetHost + 'static>(&mut self, w: &mut T, ww: f32, mut wh: f32, ctx: &mut UiContext) {
+        if let Some(pref) = w.preferred_height() {
+            wh = pref;
+        }
+        render_widget(self.pc, w, self.base_x + self.cursor_x, self.y, ww, wh, ctx);
+        self.cursor_x += ww + self.spacing;
+    }
+}
+
+fn estimate_label_width_helper(label: &str, font_size: f32, font_fam: &str) -> f32 {
+    let fam_lower = font_fam.to_lowercase();
+    let is_mono = fam_lower.contains("mono") || fam_lower.contains("courier") || fam_lower == "monospace";
+    if is_mono {
+        label.chars().count() as f32 * font_size * 0.60
+    } else {
+        let mut width = 0.0;
+        for c in label.chars() {
+            let factor = match c {
+                'i' | 'l' | 'I' | ' ' | '.' | ',' | '!' | ';' | ':' | '\'' | '"' | '(' | ')' | '[' | ']' | '-' => 0.30,
+                'f' | 'j' | 't' => 0.35,
+                'r' | 's' | 'c' | 'z' => 0.50,
+                'a' | 'b' | 'd' | 'e' | 'g' | 'h' | 'k' | 'n' | 'o' | 'p' | 'q' | 'u' | 'v' | 'x' | 'y' => 0.60,
+                'm' | 'w' | 'M' | 'W' | '&' | '@' | 'O' | 'Q' | 'G' => 0.85,
+                'A' | 'B' | 'C' | 'D' | 'H' | 'N' | 'U' | 'V' | 'X' | 'Y' => 0.75,
+                'E' | 'F' | 'K' | 'L' | 'P' | 'R' | 'S' | 'T' | 'Z' | 'J' => 0.68,
+                '0'..='9' => 0.60,
+                _ => 0.60,
+            };
+            width += factor * font_size;
+        }
+        width
+    }
+}
+
+pub struct Section {
+    pub left: f32,
+    pub top: f32,
+    pub content_y: f32,
+    pub cw: f32,
+    pub label_width: f32,
+    pub is_child: bool,
+    pub grid: Grid,
+    pub last_col: usize,
+}
+
+impl Section {
+    pub const DEFAULT_MARGIN_X: f32 = 12.0;
+    pub const DEFAULT_ROW_GAP: f32 = 8.0;
+
+    fn estimate_label_width(label: &str, font_size: f32, font_fam: &str) -> f32 {
+        estimate_label_width_helper(label, font_size, font_fam)
+    }
+
+    pub fn padding(&self) -> f32 {
+        section_padding()
+    }
+
+    pub fn new(pc: &mut dyn RenderTarget, left: f32, top: f32, cw: f32, label: &str) -> Self {
+        Self::new_opt(pc, left, top, cw, label, false)
+    }
+
+    pub fn new_opt(pc: &mut dyn RenderTarget, left: f32, top: f32, cw: f32, label: &str, is_child: bool) -> Self {
+        let font_setting = if is_child {
+            nested_section_label_font()
+        } else {
+            section_label_font()
+        };
+        let (font_fam, font_size_opt) = parse_font_string(&font_setting);
+        let font_size = font_size_opt.unwrap_or(if is_child { 12.0 } else { 14.0 });
+        let font_color = if is_child { [0.53, 0.53, 0.60, 1.0] } else { [0.83, 0.83, 0.83, 1.0] };
+        let label_width = Self::estimate_label_width(label, font_size, &font_fam);
+        let label_x = if is_child {
+            let base_x = match nested_section_label_alignment() {
+                0 => left + 12.0,
+                1 => left + (cw - label_width) / 2.0,
+                2 => left + cw - 12.0 - label_width,
+                _ => left + 12.0,
+            };
+            base_x + nested_section_label_offset()
+        } else {
+            left + (cw - label_width) / 2.0
+        };
+        pc.text_with_font(label, label_x, top, font_size, font_color, &font_fam);
+
+        let pad = section_padding();
+        let margin_x = 2.0 * pad + 12.0;
+        let usable_w = (cw - 2.0 * margin_x).max(1.0);
+        let min_col_width = 130.0;
+        let gap = 8.0;
+        let max_cols = if is_child {
+            1
+        } else {
+            ((usable_w + gap) / (min_col_width + gap)).floor().max(1.0).min(2.0) as usize
+        };
+        let content_start_y = top + pad + 19.0;
+        let grid = Grid::new(left + margin_x, content_start_y, usable_w, min_col_width, gap, max_cols);
+
+        Self { left, top, content_y: content_start_y, cw, label_width, is_child, grid, last_col: usize::MAX }
+    }
+
+    /// Horizontal inset of content from this section's left edge — the same
+    /// one `row_layout`, the column `Grid` and `widget` use, so everything in
+    /// a section lines up. See `SectionContext::content_margin`.
+    pub fn content_margin(&self) -> f32 {
+        2.0 * self.padding() + 12.0
+    }
+
+    pub fn content_left(&self) -> f32 {
+        self.left + self.content_margin()
+    }
+
+    pub fn content_width(&self) -> f32 {
+        (self.cw - 2.0 * self.content_margin()).max(0.0)
+    }
+
+    /// See `SectionContext::ax` — same mapping, same reason it is no longer
+    /// stepped at `x_off == 12.0`.
+    pub fn ax(&self, x_off: f32) -> f32 {
+        self.left + 2.0 * self.padding() + x_off
+    }
+
+    pub fn ay(&self) -> f32 { self.content_y }
+
+    pub fn spacing(&mut self, dy: f32) {
+        if self.grid.col_heights.len() >= 2 {
+            if self.last_col == usize::MAX {
+                for h in &mut self.grid.col_heights {
+                    *h += dy;
+                }
+            } else if self.last_col < self.grid.col_heights.len() {
+                self.grid.col_heights[self.last_col] += dy;
+            }
+            self.content_y = self.grid.max_height();
+        } else {
+            self.content_y += dy;
+            for h in &mut self.grid.col_heights {
+                *h += dy;
+            }
+        }
+    }
+
+    pub fn text(&mut self, pc: &mut dyn RenderTarget, text: &str, x_off: f32, y_off: f32, font_size: f32, color: [f32; 4]) {
+        let x = self.ax(x_off);
+        let y = self.ay() + y_off;
+        // Bounded to the content box, like `SectionContext::text` — text is
+        // the only thing a section draws that is not already sized to fit it.
+        let right = self.content_left() + self.content_width();
+        let bounds = if right > x {
+            Some([x, y - font_size, right, y + 2.0 * font_size])
+        } else {
+            None
+        };
+        pc.text_with_bounds(text, x, y, font_size, color, bounds);
+    }
+
+    pub fn widget<T: WidgetHost + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, _x_off: f32, _ww: f32, mut wh: f32, ctx: &mut UiContext) {
+        if let Some(pref) = w.preferred_height() {
+            wh = pref;
+        }
+        let pad = self.padding();
+        let top_room = w.label_strip();
+        let total_h = wh + top_room;
+
+        let name = w.type_name();
+        let span_full = name == "Trackpad"
+            || name == "Canvas"
+            || name == "UsageBar"
+            || name == "ProgressBar"
+            || name == "ButtonStrip"
+            || name == "Spreadsheet"
+            || name == "Graph";
+
+        if span_full {
+            let margin_x = 2.0 * pad + 12.0;
+            let x = self.left + margin_x;
+            let clamped_w = (self.cw - 2.0 * margin_x).max(0.0);
+            let max_h = self.grid.max_height().max(self.content_y);
+            let y = max_h;
+
+            w.set_row_rect(self.left + pad, self.cw - 2.0 * pad);
+            render_widget(pc, w, x, y, clamped_w, total_h, ctx);
+
+            let new_bottom = y + total_h;
+            self.content_y = new_bottom;
+            for h in &mut self.grid.col_heights {
+                *h = new_bottom;
+            }
+        } else {
+            let max_h = self.grid.max_height();
+            if self.content_y > max_h {
+                for h in &mut self.grid.col_heights {
+                    *h = self.content_y;
+                }
+            }
+
+            let col = self.grid.next_column();
+            self.last_col = col;
+            let x = self.grid.col_lefts[col];
+            let y = self.grid.col_heights[col];
+
+            w.set_row_rect(x, self.grid.col_width);
+            render_widget(pc, w, x, y, self.grid.col_width, total_h, ctx);
+            self.grid.col_heights[col] += total_h;
+            self.content_y = self.grid.max_height();
+        }
+    }
+
+    pub fn widget_full<T: WidgetHost + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, wh: f32, ctx: &mut UiContext) {
+        let x_off = 12.0;
+        let ww = self.cw - 2.0 * (self.padding() + x_off);
+        self.widget(pc, w, x_off, ww, wh, ctx);
+    }
+
+    pub fn separator(&mut self, pc: &mut dyn RenderTarget) {
+        let pad = self.padding();
+        let x = self.ax(pad);
+        let max_h = self.grid.max_height().max(self.content_y);
+        let y = max_h;
+        pc.rect([0.18, 0.18, 0.27, 1.0], x, y, self.cw - 2.0 * pad, 1.0);
+        self.content_y = max_h + 8.0;
+        for h in &mut self.grid.col_heights {
+            *h = self.content_y;
+        }
+    }
+
+    pub fn rect(&mut self, pc: &mut dyn RenderTarget, color: [f32; 4], x_off: f32, w: f32, h: f32) {
+        let max_h = self.grid.max_height().max(self.content_y);
+        pc.rect(color, self.ax(x_off), max_h, w, h);
+        self.content_y = max_h + h;
+        for col_h in &mut self.grid.col_heights {
+            *col_h = self.content_y;
+        }
+    }
+
+    pub fn row_layout(&self, count: usize, gap: f32) -> Vec<(f32, f32)> {
+        let margin_x = self.content_margin();
+        let usable_w = self.content_width();
+        if count == 0 {
+            return Vec::new();
+        }
+        let total_gap = gap * (count - 1) as f32;
+        let col_w = (usable_w - total_gap).max(0.0) / count as f32;
+
+        let mut cols = Vec::with_capacity(count);
+        for i in 0..count {
+            let x = self.left + margin_x + i as f32 * (col_w + gap);
+            cols.push((x, col_w));
+        }
+        cols
+    }
+
+    pub fn row<F>(&mut self, count: usize, gap: f32, h: f32, mut f: F)
+    where
+        F: FnMut(usize, f32, f32),
+    {
+        let max_h = self.grid.max_height().max(self.content_y);
+        for col_h in &mut self.grid.col_heights {
+            *col_h = max_h;
+        }
+        self.content_y = max_h;
+
+        let cols = self.row_layout(count, gap);
+        for (i, &(x, w)) in cols.iter().enumerate() {
+            f(i, x, w);
+        }
+        self.content_y += h;
+
+        for col_h in &mut self.grid.col_heights {
+            *col_h = self.content_y;
+        }
+    }
+
+    pub fn finish(&mut self, pc: &mut dyn RenderTarget) -> f32 {
+        self.finish_focused(pc, false)
+    }
+
+    pub fn finish_focused(&mut self, pc: &mut dyn RenderTarget, focused: bool) -> f32 {
+        let border: [f32; 4] = if self.is_child {
+            if focused {
+                [0.22, 0.38, 0.24, 1.0]
+            } else {
+                [0.18, 0.18, 0.25, 1.0]
+            }
+        } else {
+            if focused {
+                [0.30, 0.50, 0.32, 1.0]
+            } else {
+                [0.25, 0.25, 0.35, 1.0]
+            }
+        };
+        let pad = self.padding();
+        let x = self.left + pad;
+        let y = self.top + 7.0;
+        let w = self.cw - 2.0 * pad;
+        let h = self.content_y - y;
+        
+        let left_edge = x;
+        let right_edge = x + w;
+        if self.label_width > 0.0 {
+            let label_x = if self.is_child {
+                let base_x = match nested_section_label_alignment() {
+                    0 => self.left + 12.0,
+                    1 => self.left + (self.cw - self.label_width) / 2.0,
+                    2 => self.left + self.cw - 12.0 - self.label_width,
+                    _ => self.left + 12.0,
+                };
+                base_x + nested_section_label_offset()
+            } else {
+                self.left + (self.cw - self.label_width) / 2.0
+            };
+            let gap_margin = 6.0;
+            let gap_start = label_x - gap_margin;
+            let gap_end = label_x + self.label_width + gap_margin;
+            if gap_start > left_edge {
+                pc.rect(border, left_edge, y, gap_start - left_edge, 1.0);
+            }
+            if right_edge > gap_end {
+                pc.rect(border, gap_end, y, right_edge - gap_end, 1.0);
+            }
+        } else {
+            pc.rect(border, left_edge, y, w, 1.0);
+        }
+
+        let extra_bottom = pad + 12.0;
+        pc.rect(border, x, y + h + extra_bottom, w, 1.0);
+        pc.rect(border, x, y, 1.0, h + extra_bottom);
+        pc.rect(border, x + w - 1.0, y, 1.0, h + extra_bottom);
+        self.content_y + extra_bottom + 8.0
+    }
+
+    pub fn vstack<'a>(&'a mut self, pc: &'a mut dyn RenderTarget, spacing: f32) -> SectionVStack<'a> {
+        SectionVStack {
+            section: self,
+            pc,
+            spacing,
+        }
+    }
+}
+
+pub struct SectionVStack<'a> {
+    section: &'a mut Section,
+    pc: &'a mut dyn RenderTarget,
+    spacing: f32,
+}
+
+impl<'a> SectionVStack<'a> {
+    pub fn add_widget<T: WidgetHost + 'static>(&mut self, w: &mut T, ww: f32, wh: f32, ctx: &mut UiContext) {
+        self.section.widget(self.pc, w, Section::DEFAULT_MARGIN_X, ww, wh, ctx);
+        self.section.spacing(self.spacing);
+    }
+
+    pub fn add_row<F>(&mut self, count: usize, gap: f32, h: f32, f: F)
+    where
+        F: FnMut(usize, f32, f32),
+    {
+        self.section.row(count, gap, h, f);
+        self.section.spacing(self.spacing);
+    }
+}
+
+
+pub struct SplitterLayout {
+    pub splitter1_x: f32,
+    pub splitter2_x: f32,
+    pub splitter_width: f32,
+    pub min_column_width: f32,
+}
+
+impl SplitterLayout {
+    pub fn new(width: f32, splitter_width: f32, min_column_width: f32) -> Self {
+        let s1 = (width - 2.0 * splitter_width) / 3.0;
+        let s2 = s1 + splitter_width + (width - 2.0 * splitter_width) / 3.0;
+        Self {
+            splitter1_x: s1,
+            splitter2_x: s2,
+            splitter_width,
+            min_column_width,
+        }
+    }
+
+    pub fn clamp(&mut self, total_width: f32, detached_circular_network: bool) {
+        if detached_circular_network {
+            let min_s2 = self.min_column_width;
+            let max_s2 = (total_width - self.min_column_width).max(min_s2);
+            self.splitter2_x = self.splitter2_x.clamp(min_s2, max_s2);
+        } else {
+            let min_s1 = self.min_column_width;
+            let max_s1 = (self.splitter2_x - self.splitter_width - self.min_column_width).max(min_s1);
+            self.splitter1_x = self.splitter1_x.clamp(min_s1, max_s1);
+            let min_s2 = self.splitter1_x + self.splitter_width + self.min_column_width;
+            let max_s2 = (total_width - self.min_column_width).max(min_s2);
+            self.splitter2_x = self.splitter2_x.clamp(min_s2, max_s2);
+        }
+    }
+
+    pub fn scale(&mut self, factor: f32) {
+        self.splitter1_x *= factor;
+        self.splitter2_x *= factor;
+    }
+
+    pub fn left_col(&self) -> (f32, f32) { // (x, width)
+        (0.0, self.splitter1_x)
+    }
+
+    pub fn center_col(&self) -> (f32, f32) { // (x, width)
+        let x = self.splitter1_x + self.splitter_width;
+        (x, self.splitter2_x - x)
+    }
+
+    pub fn right_col(&self, total_width: f32) -> (f32, f32) { // (x, width)
+        let x = self.splitter2_x + self.splitter_width;
+        (x, (total_width - x).max(0.0))
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Grid {
+    pub left: f32,
+    pub top: f32,
+    pub width: f32,
+    pub col_width: f32,
+    pub gap: f32,
+    pub col_heights: Vec<f32>,
+    pub col_lefts: Vec<f32>,
+}
+
+impl Grid {
+    pub fn new(left: f32, top: f32, width: f32, min_col_width: f32, gap: f32, count: usize) -> Self {
+        let total_gap = gap * (count - 1) as f32;
+        let col_width = if count > 0 {
+            (width - total_gap).max(0.0) / count as f32
+        } else {
+            min_col_width
+        };
+        let left_offset = 0.0;
+
+        let mut col_lefts = Vec::with_capacity(count);
+        let col_heights = vec![top; count];
+        for i in 0..count {
+            col_lefts.push(left + left_offset + i as f32 * (col_width + gap));
+        }
+
+        Self {
+            left,
+            top,
+            width,
+            col_width,
+            gap,
+            col_heights,
+            col_lefts,
+        }
+    }
+
+    pub fn next_column(&self) -> usize {
+        let mut min_idx = 0;
+        let mut min_h = self.col_heights[0];
+        for i in 1..self.col_heights.len() {
+            if self.col_heights[i] < min_h {
+                min_h = self.col_heights[i];
+                min_idx = i;
+            }
+        }
+        min_idx
+    }
+
+    pub fn max_height(&self) -> f32 {
+        let mut max_h = self.col_heights[0];
+        for i in 1..self.col_heights.len() {
+            if self.col_heights[i] > max_h {
+                max_h = self.col_heights[i];
+            }
+        }
+        max_h
+    }
+}
+
+pub struct CircularPaneLayout {
+    pub x: f32,
+    pub y: f32,
+    pub r: f32,
+}
+
+impl CircularPaneLayout {
+    pub fn new(x: f32, y: f32, r: f32) -> Self {
+        Self { x, y, r }
+    }
+
+    pub fn hit_test_content(&self, cx: f32, cy: f32, menubar_h: f32, breadcrumb_h: f32) -> bool {
+        let dx = cx - self.x;
+        let dy = cy - self.y;
+        let dist_sq = dx * dx + dy * dy;
+        dist_sq <= self.r * self.r && cy >= self.y - self.r + 45.0 + menubar_h + breadcrumb_h
+    }
+
+    pub fn hit_test_menubar(&self, cx: f32, cy: f32, menubar_h: f32) -> bool {
+        let dx = cx - self.x;
+        let dy = cy - self.y;
+        let dist = (dx * dx + dy * dy).sqrt();
+        dist >= self.r - menubar_h && dist <= self.r && cy < self.y
+    }
+
+    pub fn hit_test_breadcrumb(&self, cx: f32, cy: f32, menubar_h: f32, breadcrumb_h: f32) -> bool {
+        let dx = cx - self.x;
+        let dy = cy - self.y;
+        let dist_sq = dx * dx + dy * dy;
+        dist_sq <= self.r * self.r && cy >= self.y - self.r + menubar_h && cy < self.y - self.r + 45.0 + menubar_h + breadcrumb_h
+    }
+
+    pub fn hit_test_border(&self, cx: f32, cy: f32, border_thickness: f32) -> bool {
+        let dx = cx - self.x;
+        let dy = cy - self.y;
+        let dist = (dx * dx + dy * dy).sqrt();
+        dist >= self.r - border_thickness && dist <= self.r
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Radial {
+    pub center_x: f32,
+    pub center_y: f32,
+    pub aspect_ratio: f32,
+    pub base_spacing: f32,
+}
+
+impl Radial {
+    pub fn new(center_x: f32, center_y: f32, aspect_ratio: f32, base_spacing: f32) -> Self {
+        Self { center_x, center_y, aspect_ratio, base_spacing }
+    }
+
+    pub fn widget_rect(&self, idx: usize, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
+        if idx == 0 {
+            (self.center_x - ww / 2.0, self.center_y - wh / 2.0, ww, wh)
+        } else {
+            let mut ring = 1;
+            let mut ring_start = 1;
+            loop {
+                let ring_capacity = ring * 6;
+                if idx < ring_start + ring_capacity {
+                    let pos_in_ring = idx - ring_start;
+                    let angle = (pos_in_ring as f32) * (2.0 * std::f32::consts::PI / ring_capacity as f32);
+                    let radius = (ring as f32) * self.base_spacing;
+
+                    let x_offset = radius * angle.cos() * self.aspect_ratio;
+                    let y_offset = radius * angle.sin();
+
+                    return (
+                        self.center_x + x_offset - ww / 2.0,
+                        self.center_y + y_offset - wh / 2.0,
+                        ww,
+                        wh,
+                    );
+                }
+                ring_start += ring_capacity;
+                ring += 1;
+            }
+        }
+    }
+
+}
+
+
+
+pub trait LayoutStrategy: std::fmt::Debug {
+    fn init(&mut self, left: f32, top: f32, width: f32, height: f32);
+    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32);
+    fn set_section_count(&mut self, _count: usize) {}
+    fn get_column_width(&self) -> Option<f32> { None }
+    fn get_gap(&self) -> f32 { 20.0 }
+
+    fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &mut crate::context::UiContext) -> f32;
+    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size;
+    fn box_clone(&self) -> Box<dyn LayoutStrategy>;
+}
+
+impl Clone for Box<dyn LayoutStrategy> {
+    fn clone(&self) -> Self {
+        self.box_clone()
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FlexDirection {
+    Row,
+    Column,
+}
+
+#[derive(Debug, Clone)]
+pub struct FlexLayout {
+    left: f32,
+    top: f32,
+    width: f32,
+    height: f32,
+    direction: FlexDirection,
+    spacing: f32,
+    current_x: f32,
+    current_y: f32,
+}
+
+impl FlexLayout {
+    pub fn new(direction: FlexDirection, spacing: f32) -> Self {
+        Self {
+            left: 0.0,
+            top: 0.0,
+            width: 0.0,
+            height: 0.0,
+            direction,
+            spacing,
+            current_x: 0.0,
+            current_y: 0.0,
+        }
+    }
+}
+
+impl LayoutStrategy for FlexLayout {
+    fn init(&mut self, left: f32, top: f32, width: f32, height: f32) {
+        self.left = left;
+        self.top = top;
+        self.width = width;
+        self.height = height;
+        self.current_x = left;
+        self.current_y = top;
+    }
+
+    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
+        match self.direction {
+            FlexDirection::Row => {
+                let rx = self.current_x;
+                let ry = self.current_y;
+                self.current_x += ww + self.spacing;
+                (rx, ry, ww, wh)
+            }
+            FlexDirection::Column => {
+                let rx = self.current_x;
+                let ry = self.current_y;
+                self.current_y += wh + self.spacing;
+                (rx, ry, ww, wh)
+            }
+        }
+    }
+
+    fn get_gap(&self) -> f32 {
+        self.spacing
+    }
+
+    fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &mut crate::context::UiContext) -> f32 {
+        let (cur_x, cur_y) = (x, y);
+        // Blocks: the label row (`label_lead`) above every child's content, the
+        // gap between blocks, so a row's controls are level and a carve-out tab
+        // sits a full gap from its neighbour.
+        let lead = crate::widget::container::container_layout::label_lead(children);
+        match self.direction {
+            FlexDirection::Row => {
+                let mut cur_x = cur_x;
+                for &child_ptr in children {
+                    unsafe {
+                        let child = &mut *child_ptr;
+                        let child_w = child.rect().2;
+                        let child_h = crate::widget::container::container_layout::content_height(child);
+                        let use_h = if child_h > 0.0 { child_h } else { h };
+                        child.layout(
+                            crate::widget::Point { x: cur_x, y: cur_y + lead },
+                            crate::widget::LayoutConstraints::new(child_w, child_w, use_h, use_h),
+                            ctx,
+                        );
+                        cur_x += child_w + self.spacing;
+                    }
+                }
+                (cur_x - x).max(0.0)
+            }
+            FlexDirection::Column => {
+                let mut cur_y = cur_y;
+                for &child_ptr in children {
+                    unsafe {
+                        let child = &mut *child_ptr;
+                        let child_h = crate::widget::container::container_layout::content_height(child);
+                        let use_h = if child_h > 0.0 { child_h } else { 44.0 };
+                        child.layout(
+                            crate::widget::Point { x, y: cur_y + lead },
+                            crate::widget::LayoutConstraints::new(w, w, use_h, use_h),
+                            ctx,
+                        );
+                        cur_y += lead + use_h + self.spacing;
+                    }
+                }
+                (cur_y - y).max(0.0)
+            }
+        }
+    }
+
+    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size {
+        match self.direction {
+            FlexDirection::Row => {
+                let mut total_w = 0.0f32;
+                let mut max_h = 0.0f32;
+                for (i, &child_ptr) in children.iter().enumerate() {
+                    unsafe {
+                        let size = (*child_ptr).measure(constraints, ctx);
+                        total_w += size.width;
+                        max_h = max_h.max(size.height);
+                        if i > 0 {
+                            total_w += self.spacing;
+                        }
+                    }
+                }
+                crate::widget::Size {
+                    width: total_w.clamp(constraints.min_width, constraints.max_width),
+                    height: max_h.clamp(constraints.min_height, constraints.max_height),
+                }
+            }
+            FlexDirection::Column => {
+                let mut total_h = 0.0f32;
+                let mut max_w = 0.0f32;
+                for (i, &child_ptr) in children.iter().enumerate() {
+                    unsafe {
+                        let size = (*child_ptr).measure(constraints, ctx);
+                        total_h += size.height;
+                        max_w = max_w.max(size.width);
+                        if i > 0 {
+                            total_h += self.spacing;
+                        }
+                    }
+                }
+                crate::widget::Size {
+                    width: max_w.clamp(constraints.min_width, constraints.max_width),
+                    height: total_h.clamp(constraints.min_height, constraints.max_height),
+                }
+            }
+        }
+    }
+
+    fn box_clone(&self) -> Box<dyn LayoutStrategy> {
+        Box::new(self.clone())
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct ColumnLayout {
+    left: f32,
+    top: f32,
+    width: f32,
+    current_y: f32,
+    gap: f32,
+}
+
+impl ColumnLayout {
+    pub fn new(gap: f32) -> Self {
+        Self {
+            left: 0.0,
+            top: 0.0,
+            width: 0.0,
+            current_y: 0.0,
+            gap,
+        }
+    }
+}
+
+impl LayoutStrategy for ColumnLayout {
+    fn init(&mut self, left: f32, top: f32, width: f32, _height: f32) {
+        self.left = left;
+        self.top = top;
+        self.width = width;
+        self.current_y = top;
+    }
+
+    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
+        let x = self.left;
+        let y = self.current_y;
+        self.current_y += wh + self.gap;
+        (x, y, ww, wh)
+    }
+
+    fn get_column_width(&self) -> Option<f32> {
+        Some(self.width)
+    }
+
+    fn get_gap(&self) -> f32 {
+        self.gap
+    }
+
+    fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], _ctx: &mut crate::context::UiContext) -> f32 {
+        let mut cur_y = y;
+        for &child_ptr in children {
+            unsafe {
+                let child = &mut *child_ptr;
+                let child_h = child.preferred_height().unwrap_or(child.rect().3);
+                let use_h = if child_h > 0.0 { child_h } else { 44.0 };
+                child.set_rect(x, cur_y, w, use_h);
+                cur_y += use_h + self.gap;
+            }
+        }
+        (cur_y - y).max(0.0)
+    }
+
+    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size {
+        let mut total_h = 0.0f32;
+        let mut max_w = 0.0f32;
+        for (i, &child_ptr) in children.iter().enumerate() {
+            unsafe {
+                let size = (*child_ptr).measure(constraints, ctx);
+                total_h += size.height;
+                max_w = max_w.max(size.width);
+                if i > 0 {
+                    total_h += self.gap;
+                }
+            }
+        }
+        crate::widget::Size {
+            width: max_w.clamp(constraints.min_width, constraints.max_width),
+            height: total_h.clamp(constraints.min_height, constraints.max_height),
+        }
+    }
+
+    fn box_clone(&self) -> Box<dyn LayoutStrategy> {
+        Box::new(self.clone())
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct AdaptiveGrid {
+    grid: Option<Grid>,
+    #[allow(dead_code)]
+    min_col_width: f32,
+    #[allow(dead_code)]
+    gap: f32,
+    num_sections: Option<usize>,
+}
+
+impl AdaptiveGrid {
+    pub fn new(min_col_width: f32, gap: f32) -> Self {
+        Self {
+            grid: None,
+            min_col_width,
+            gap,
+            num_sections: None,
+        }
+    }
+}
+
+impl LayoutStrategy for AdaptiveGrid {
+    fn init(&mut self, left: f32, top: f32, width: f32, _height: f32) {
+        let min_col_width = crate::layout::grid_min_col_width();
+        let gap = crate::layout::grid_gap();
+        let max_cols = ((width + gap) / (min_col_width + gap)).floor().max(1.0) as usize;
+        let count = if let Some(n) = self.num_sections {
+            n.min(max_cols).max(1)
+        } else {
+            max_cols
+        };
+        self.grid = Some(Grid::new(left, top, width, min_col_width, gap, count));
+    }
+
+    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
+        if let Some(ref mut grid) = self.grid {
+            let num_cols = grid.col_heights.len();
+            let num_cols_spanned = (((ww + grid.gap) / (grid.col_width + grid.gap)).round() as usize)
+                .min(num_cols)
+                .max(1);
+
+            if num_cols_spanned >= num_cols {
+                let y = grid.max_height();
+                let x = grid.left;
+                let allocated_w = grid.width;
+                for col_h in &mut grid.col_heights {
+                    *col_h = y + wh + grid.gap;
+                }
+                (x, y, allocated_w, wh)
+            } else if num_cols_spanned == 1 {
+                let col = grid.next_column();
+                let x = grid.col_lefts[col];
+                let y = grid.col_heights[col];
+                grid.col_heights[col] += wh + grid.gap;
+                (x, y, grid.col_width, wh)
+            } else {
+                let n = num_cols_spanned;
+                let mut best_start_col = 0;
+                let mut min_max_h = f32::MAX;
+                for c in 0..=(num_cols - n) {
+                    let mut max_h = 0.0f32;
+                    for i in 0..n {
+                        if grid.col_heights[c + i] > max_h {
+                            max_h = grid.col_heights[c + i];
+                        }
+                    }
+                    if max_h < min_max_h {
+                        min_max_h = max_h;
+                        best_start_col = c;
+                    }
+                }
+                let x = grid.col_lefts[best_start_col];
+                let y = min_max_h;
+                let allocated_w = n as f32 * grid.col_width + (n - 1) as f32 * grid.gap;
+                for i in 0..n {
+                    grid.col_heights[best_start_col + i] = y + wh + grid.gap;
+                }
+                (x, y, allocated_w, wh)
+            }
+        } else {
+            (0.0, 0.0, 0.0, wh)
+        }
+    }
+
+    fn set_section_count(&mut self, count: usize) {
+        self.num_sections = Some(count);
+    }
+
+    fn get_column_width(&self) -> Option<f32> {
+        self.grid.as_ref().map(|g| g.col_width)
+    }
+
+    fn get_gap(&self) -> f32 {
+        crate::layout::grid_gap()
+    }
+
+    fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], _ctx: &mut crate::context::UiContext) -> f32 {
+        let usable_w = w.max(1.0);
+        let min_col_width = crate::layout::grid_min_col_width();
+        let gap = crate::layout::grid_gap();
+        let cols = (((usable_w + gap) / (min_col_width + gap)).floor().max(1.0)) as usize;
+        let count = if let Some(n) = self.num_sections {
+            n.min(cols).max(1)
+        } else {
+            cols
+        };
+
+        let total_gap = gap * (count - 1) as f32;
+        let available_w = (w - total_gap).max(1.0);
+        let col_w = available_w / count as f32;
+        
+        let mut col_heights = vec![y; count];
+
+        for &child_ptr in children {
+            unsafe {
+                let child = &mut *child_ptr;
+                let ch = child.preferred_height().unwrap_or(child.rect().3);
+                let use_h = if ch > 0.0 { ch } else { 44.0 };
+                
+                let mut min_col = 0;
+                let mut min_h = col_heights[0];
+                for i in 1..count {
+                    if col_heights[i] < min_h {
+                        min_h = col_heights[i];
+                        min_col = i;
+                    }
+                }
+                
+                let cx = x + min_col as f32 * (col_w + gap);
+                let cy = col_heights[min_col];
+                child.set_rect(cx, cy, col_w, use_h);
+                col_heights[min_col] += use_h + gap;
+            }
+        }
+        
+        let max_h = col_heights.iter().cloned().fold(0.0f32, |a, b| a.max(b));
+        (max_h - y).max(0.0)
+    }
+
+    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size {
+        let usable_w = constraints.max_width.max(1.0);
+        let min_col_width = crate::layout::grid_min_col_width();
+        let gap = crate::layout::grid_gap();
+        let cols = (((usable_w + gap) / (min_col_width + gap)).floor().max(1.0)) as usize;
+        let count = if let Some(n) = self.num_sections {
+            n.min(cols).max(1)
+        } else {
+            cols
+        };
+
+        let mut col_heights = vec![0.0f32; count];
+        let total_gap = gap * (count - 1) as f32;
+        let available_w = (constraints.max_width - total_gap).max(1.0);
+        let col_w = available_w / count as f32;
+        
+        let child_constraints = crate::widget::LayoutConstraints::new(col_w, col_w, constraints.min_height, constraints.max_height);
+
+        for &child_ptr in children {
+            unsafe {
+                let size = (*child_ptr).measure(child_constraints, ctx);
+                let mut min_col = 0;
+                let mut min_h = col_heights[0];
+                for i in 1..count {
+                    if col_heights[i] < min_h {
+                        min_h = col_heights[i];
+                        min_col = i;
+                    }
+                }
+                col_heights[min_col] += size.height + gap;
+            }
+        }
+        
+        let max_h = col_heights.iter().cloned().fold(0.0f32, |a, b| a.max(b));
+        crate::widget::Size {
+            width: constraints.max_width,
+            height: max_h.clamp(constraints.min_height, constraints.max_height),
+        }
+    }
+
+    fn box_clone(&self) -> Box<dyn LayoutStrategy> {
+        Box::new(self.clone())
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct RadialLayout {
+    radial: Option<Radial>,
+    aspect_ratio: f32,
+    base_spacing: f32,
+    idx: usize,
+}
+
+impl RadialLayout {
+    pub fn new(aspect_ratio: f32, base_spacing: f32) -> Self {
+        Self {
+            radial: None,
+            aspect_ratio,
+            base_spacing,
+            idx: 0,
+        }
+    }
+}
+
+impl LayoutStrategy for RadialLayout {
+    fn init(&mut self, left: f32, top: f32, width: f32, height: f32) {
+        let cx = left + width / 2.0;
+        let cy = top + height / 2.0;
+        let aspect = if self.aspect_ratio > 0.0 {
+            self.aspect_ratio
+        } else {
+            let screen_aspect = (width / height.max(1.0)).max(0.1);
+            1.0 + (screen_aspect - 1.0) * 0.4
+        };
+        self.radial = Some(Radial::new(cx, cy, aspect, self.base_spacing));
+        self.idx = 0;
+    }
+
+    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
+        if let Some(ref radial) = self.radial {
+            let rect = radial.widget_rect(self.idx, ww, wh);
+            self.idx += 1;
+            rect
+        } else {
+            (0.0, 0.0, ww, wh)
+        }
+    }
+
+    fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], _ctx: &mut crate::context::UiContext) -> f32 {
+        let cx = x + w / 2.0;
+        let cy = y + h / 2.0;
+        let aspect = if self.aspect_ratio > 0.0 {
+            self.aspect_ratio
+        } else {
+            let screen_aspect = (w / h.max(1.0)).max(0.1);
+            1.0 + (screen_aspect - 1.0) * 0.4
+        };
+        let radial = Radial::new(cx, cy, aspect, self.base_spacing);
+        for (idx, &child_ptr) in children.iter().enumerate() {
+            unsafe {
+                let child = &mut *child_ptr;
+                let cw = child.rect().2;
+                let ch = child.preferred_height().unwrap_or(child.rect().3);
+                let use_h = if ch > 0.0 { ch } else { 44.0 };
+                let (rx, ry, rw, rh) = radial.widget_rect(idx, cw, use_h);
+                child.set_rect(rx, ry, rw, rh);
+            }
+        }
+        h
+    }
+
+    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size {
+        let cx = constraints.max_width / 2.0;
+        let cy = constraints.max_height / 2.0;
+        let aspect = if self.aspect_ratio > 0.0 {
+            self.aspect_ratio
+        } else {
+            let screen_aspect = (constraints.max_width / constraints.max_height.max(1.0)).max(0.1);
+            1.0 + (screen_aspect - 1.0) * 0.4
+        };
+        let radial = Radial::new(cx, cy, aspect, self.base_spacing);
+        let mut max_w = 0.0f32;
+        let mut max_h = 0.0f32;
+        for (idx, &child_ptr) in children.iter().enumerate() {
+            unsafe {
+                let size = (*child_ptr).measure(constraints, ctx);
+                let (rx, ry, rw, rh) = radial.widget_rect(idx, size.width, size.height);
+                max_w = max_w.max(rx + rw);
+                max_h = max_h.max(ry + rh);
+            }
+        }
+        crate::widget::Size {
+            width: max_w.clamp(constraints.min_width, constraints.max_width),
+            height: max_h.clamp(constraints.min_height, constraints.max_height),
+        }
+    }
+
+    fn box_clone(&self) -> Box<dyn LayoutStrategy> {
+        Box::new(self.clone())
+    }
+}
+
+/// Vertical slack for a section's content clip. The clip exists to stop
+/// content escaping its section SIDEWAYS, which is the axis a section's width
+/// actually fixes; a section's height is only known once its content has been
+/// placed, so bounding that axis too would risk cutting content off rather
+/// than keeping it in. Deliberately far larger than any section.
+const SECTION_CLIP_SLACK: f32 = 100_000.0;
+
+pub struct PageLayoutBuilder<'a, P> {
+    pub strategy: &'a mut dyn LayoutStrategy,
+    pub cx: f32,
+    pub cy: f32,
+    pub cw: f32,
+    pub ch: f32,
+    pub section_width: f32,
+    pub idx: usize,
+    _phantom: std::marker::PhantomData<P>,
+}
+
+impl<'a, P: RenderTarget + Default> PageLayoutBuilder<'a, P> {
+    pub fn new(
+        strategy: &'a mut dyn LayoutStrategy,
+        cx: f32,
+        cy: f32,
+        cw: f32,
+        ch: f32,
+        section_width: f32,
+    ) -> Self {
+        strategy.init(cx, cy, cw, ch);
+        Self {
+            strategy,
+            cx,
+            cy,
+            cw,
+            ch,
+            section_width,
+            idx: 0,
+            _phantom: std::marker::PhantomData,
+        }
+    }
+
+    pub fn with_section_count(self, count: usize) -> Self {
+        self.strategy.set_section_count(count);
+        self.strategy.init(self.cx, self.cy, self.cw, self.ch);
+        self
+    }
+
+    pub fn add_section<F>(&mut self, final_pc: &mut P, label: &str, focused: bool, mut render_fn: F)
+    where
+        F: FnMut(&mut SectionContext<'_, P>),
+    {
+        let width = self.strategy.get_column_width().unwrap_or(self.section_width);
+        let mut dummy = P::default();
+        let mut dummy_ctx = SectionContext::new(&mut dummy, 0.0, 0.0, width, label, focused, false);
+        render_fn(&mut dummy_ctx);
+        let wh = dummy_ctx.finish();
+        let (rx, ry, rw, _) = self.strategy.allocate(width, wh);
+        let mut real_ctx = SectionContext::new(final_pc, rx, ry, rw, label, focused, false);
+        let (clip_x, clip_w) = (real_ctx.content_left(), real_ctx.content_width());
+        real_ctx.pc.push_clip_rect(clip_x, ry - SECTION_CLIP_SLACK, clip_w, 2.0 * SECTION_CLIP_SLACK);
+        render_fn(&mut real_ctx);
+        real_ctx.pc.pop_clip_rect();
+        real_ctx.finish();
+        self.idx += 1;
+    }
+
+    pub fn add_section_with_width<F>(&mut self, final_pc: &mut P, width: f32, label: &str, focused: bool, mut render_fn: F)
+    where
+        F: FnMut(&mut SectionContext<'_, P>),
+    {
+        let mut dummy = P::default();
+        let mut dummy_ctx = SectionContext::new(&mut dummy, 0.0, 0.0, width, label, focused, false);
+        render_fn(&mut dummy_ctx);
+        let wh = dummy_ctx.finish();
+        let (rx, ry, rw, _) = self.strategy.allocate(width, wh);
+        let mut real_ctx = SectionContext::new(final_pc, rx, ry, rw, label, focused, false);
+        let (clip_x, clip_w) = (real_ctx.content_left(), real_ctx.content_width());
+        real_ctx.pc.push_clip_rect(clip_x, ry - SECTION_CLIP_SLACK, clip_w, 2.0 * SECTION_CLIP_SLACK);
+        render_fn(&mut real_ctx);
+        real_ctx.pc.pop_clip_rect();
+        real_ctx.finish();
+        self.idx += 1;
+    }
+
+    pub fn add_section_spanned<F>(&mut self, final_pc: &mut P, label: &str, span: usize, focused: bool, render_fn: F)
+    where
+        F: FnMut(&mut SectionContext<'_, P>),
+    {
+        let col_width = self.strategy.get_column_width().unwrap_or(self.section_width);
+        let gap = self.strategy.get_gap();
+        let width = span as f32 * col_width + (span - 1) as f32 * gap;
+        self.add_section_with_width(final_pc, width, label, focused, render_fn);
+    }
+}
+
+pub struct SectionContext<'a, P> {
+    pub pc: &'a mut P,
+    pub left: f32,
+    pub top: f32,
+    pub content_y: f32,
+    pub cw: f32,
+    pub label_width: f32,
+    pub label_x: f32,
+    /// Whether the host renders sections as sunken wells (`section_relief_style`).
+    pub relief_style: bool,
+    /// The title tab box (x, y, w, h) when the host's relief styling laid the
+    /// label out left-aligned — `finish` offers it with the section carve.
+    /// None under relief styling means a label-less section: the well carves
+    /// tabless, flush with the allocation top.
+    pub relief_tab: Option<(f32, f32, f32, f32)>,
+    pub focused: bool,
+    pub is_child: bool,
+    pub grid: Grid,
+    pub last_col: usize,
+    pub content_start_y: f32,
+    pub row_gap: f32,
+}
+
+impl<'a, P: RenderTarget> SectionContext<'a, P> {
+    pub const DEFAULT_MARGIN_X: f32 = 12.0;
+    pub const DEFAULT_ROW_GAP: f32 = 8.0;
+
+    fn estimate_label_width(label: &str, font_size: f32, font_fam: &str) -> f32 {
+        estimate_label_width_helper(label, font_size, font_fam)
+    }
+
+    pub fn padding(&self) -> f32 {
+        section_padding()
+    }
+
+    pub fn new(pc: &'a mut P, left: f32, top: f32, cw: f32, label: &str, focused: bool, is_child: bool) -> Self {
+        let font_setting = if is_child {
+            nested_section_label_font()
+        } else {
+            section_label_font()
+        };
+        let (font_fam, font_size_opt) = parse_font_string(&font_setting);
+        let font_size = font_size_opt.unwrap_or(if is_child { 12.0 } else { 14.0 });
+        let font_color = if is_child { [0.53, 0.53, 0.60, 1.0] } else { [0.83, 0.83, 0.83, 1.0] };
+        let label_width = Self::estimate_label_width(label, font_size, &font_fam);
+        let relief_style = pc.section_relief_style();
+        let label_x = if is_child {
+            let base_x = match nested_section_label_alignment() {
+                0 => left + 12.0,
+                1 => left + (cw - label_width) / 2.0,
+                2 => left + cw - 12.0 - label_width,
+                _ => left + 12.0,
+            };
+            base_x + nested_section_label_offset()
+        } else if relief_style {
+            // Sunken style: the title sits in a tab flush with the well's left
+            // edge (the designer look), not centered on the border.
+            left + 12.0
+        } else {
+            left + (cw - label_width) / 2.0
+        };
+        // Under relief styling the well fills the whole allocated rect (so the
+        // page's gaps and margins are the visual gaps) — the tab tops the
+        // allocation and the label centers inside it.
+        let label_y = if relief_style { top + 4.0 } else { top };
+        pc.text_with_font(label, label_x, label_y, font_size, font_color, &font_fam);
+
+        // The tab wraps the label, flush on the well's top-left corner; clamped
+        // so off-default child alignments can't push it outside the well.
+        let relief_tab = if relief_style && label_width > 0.0 {
+            let tab_x = (label_x - 12.0).max(left);
+            Some((tab_x, top, label_width + 24.0, font_size + 10.0))
+        } else {
+            None
+        };
+
+        let pad = section_padding();
+        let margin_x = 2.0 * pad + 12.0;
+        let usable_w = (cw - 2.0 * margin_x).max(1.0);
+        let min_col_width = 130.0;
+        let gap = 8.0;
+        let max_cols = if is_child {
+            1
+        } else {
+            ((usable_w + gap) / (min_col_width + gap)).floor().max(1.0).min(2.0) as usize
+        };
+        // Under relief styling the content stands off the well's top wall by
+        // the same inset it keeps from the side walls (`margin_x`, which is
+        // also what `finish` leaves below it), so a well reads as one even
+        // frame. The outline style's `pad + 19` predates the title tab: under
+        // relief the tab alone is `font_size + 10` tall, which left the first
+        // line touching the well's top wall at the default padding while the
+        // sides kept 20px and more.
+        let content_start_y = if relief_style {
+            relief_tab.map(|t| t.1 + t.3).unwrap_or(top) + margin_x
+        } else {
+            top + pad + 19.0
+        };
+        let grid = Grid::new(left + margin_x, content_start_y, usable_w, min_col_width, gap, max_cols);
+
+        Self {
+            pc,
+            left,
+            top,
+            content_y: content_start_y,
+            cw,
+            label_width,
+            label_x,
+            relief_style,
+            relief_tab,
+            focused,
+            is_child,
+            grid,
+            last_col: usize::MAX,
+            content_start_y,
+            row_gap: Self::DEFAULT_ROW_GAP,
+        }
+    }
+
+    pub fn with_row_gap(mut self, gap: f32) -> Self {
+        self.row_gap = gap;
+        self
+    }
+
+    /// The section's content-box top edge: the body well's top (the tab's
+    /// bottom) under relief styling, the outline's border line otherwise. Lets
+    /// a page place content at an exact inset from the well's walls.
+    pub fn well_top(&self) -> f32 {
+        if self.relief_style {
+            self.relief_tab.map(|t| t.1 + t.3).unwrap_or(self.top)
+        } else {
+            self.top + 7.0
+        }
+    }
+
+    pub fn set_row_gap(&mut self, gap: f32) {
+        self.row_gap = gap;
+    }
+
+    /// Horizontal inset of section CONTENT from the section's left edge.
+    ///
+    /// One number, used by every content placer in here — `row_layout`, the
+    /// column `Grid`, `widget`, `VStack` and `ax` (so `text`) — because they
+    /// share a section and have to line up inside it. The section's border is
+    /// drawn at `left + padding()` (see `finish`), so content clears the
+    /// border by `padding() + 12`.
+    pub fn content_margin(&self) -> f32 {
+        2.0 * self.padding() + 12.0
+    }
+
+    /// Left edge of the content box: where a row, a widget or a `text(_, 12.0,
+    /// ..)` starts.
+    pub fn content_left(&self) -> f32 {
+        self.left + self.content_margin()
+    }
+
+    /// Width of the content box — the section's width less the inset on both
+    /// sides. Nothing a section draws should extend past `content_left() +
+    /// content_width()`.
+    pub fn content_width(&self) -> f32 {
+        (self.cw - 2.0 * self.content_margin()).max(0.0)
+    }
+
+    /// `x_off` px into the content box's coordinate space, where 12.0 is the
+    /// content's own left edge — the offset 46 of the ~55 call sites in the
+    /// tree already pass, and the one that lines text up with the buttons and
+    /// widgets beside it.
+    ///
+    /// This used to add `padding()` only when `x_off >= 12.0`, which made the
+    /// mapping DISCONTINUOUS: asking for 11 instead of 12 moved the text 5px
+    /// LEFT rather than 1px, and silently dropped it out of alignment with
+    /// every row in the same section. `cce-mail` and two others sit on the
+    /// wrong side of that cliff today.
+    pub fn ax(&self, x_off: f32) -> f32 {
+        self.left + 2.0 * self.padding() + x_off
+    }
+
+    pub fn ay(&self) -> f32 {
+        self.content_y
+    }
+
+    pub fn spacing(&mut self, dy: f32) {
+        if self.grid.col_heights.len() >= 2 {
+            if self.last_col == usize::MAX {
+                for h in &mut self.grid.col_heights {
+                    *h += dy;
+                }
+            } else if self.last_col < self.grid.col_heights.len() {
+                self.grid.col_heights[self.last_col] += dy;
+            }
+            self.content_y = self.grid.max_height();
+        } else {
+            self.content_y += dy;
+            for h in &mut self.grid.col_heights {
+                *h += dy;
+            }
+        }
+    }
+
+    pub fn text(&mut self, text: &str, x_off: f32, y_off: f32, font_size: f32, color: [f32; 4]) {
+        let mut y = self.content_y + y_off;
+        if y > self.content_start_y {
+            y += self.row_gap;
+        }
+        let x = self.ax(x_off);
+        // Bound it to the content box. A section's text was drawn unbounded,
+        // so a string wider than its section simply kept going — over the
+        // border, over whatever sat to the right, and off the window (the
+        // settings app's GPU names did all three). Rows and widgets have
+        // always been sized to the section; text was the one thing that could
+        // leave it. The vertical band is generous on purpose: it is the
+        // horizontal overrun that has to be cut, and a tight band would
+        // shave descenders.
+        let right = self.content_left() + self.content_width();
+        let bounds = if right > x {
+            Some([x, y - font_size, right, y + 2.0 * font_size])
+        } else {
+            None
+        };
+        self.pc.text_with_bounds(text, x, y, font_size, color, bounds);
+        let new_bottom = y + font_size + 4.0;
+        self.content_y = new_bottom;
+        for h in &mut self.grid.col_heights {
+            *h = new_bottom;
+        }
+    }
+
+    pub fn widget<T: WidgetHost + 'static>(&mut self, w: &mut T, _x_off: f32, _ww: f32, mut wh: f32, ctx: &mut UiContext) {
+        if let Some(pref) = w.preferred_height() {
+            wh = pref;
+        }
+        let pad = self.padding();
+        let top_room = w.label_strip();
+        let total_h = wh + top_room;
+
+        let name = w.type_name();
+        let span_full = name == "Trackpad"
+            || name == "Canvas"
+            || name == "UsageBar"
+            || name == "ProgressBar"
+            || name == "ButtonStrip"
+            || name == "Spreadsheet"
+            || name == "Graph";
+
+        if span_full {
+            let margin_x = 2.0 * pad + 12.0;
+            let x = self.left + margin_x;
+            let clamped_w = (self.cw - 2.0 * margin_x).max(0.0);
+            let mut max_h = self.grid.max_height().max(self.content_y);
+            if max_h > self.content_start_y {
+                max_h += self.row_gap;
+            }
+            let y = max_h;
+
+            w.set_row_rect(self.left + pad, self.cw - 2.0 * pad);
+            render_widget(self.pc, w, x, y, clamped_w, total_h, ctx);
+
+            let new_bottom = y + total_h;
+            self.content_y = new_bottom;
+            for h in &mut self.grid.col_heights {
+                *h = new_bottom;
+            }
+        } else {
+            let max_h = self.grid.max_height();
+            if self.content_y > max_h {
+                for h in &mut self.grid.col_heights {
+                    *h = self.content_y;
+                }
+            }
+
+            let col = self.grid.next_column();
+            self.last_col = col;
+            let x = self.grid.col_lefts[col];
+            let mut y = self.grid.col_heights[col];
+            if y > self.content_start_y {
+                y += self.row_gap;
+            }
+
+            let aligned_x = x;
+            let aligned_w = self.grid.col_width;
+
+            w.set_row_rect(aligned_x, aligned_w);
+            render_widget(self.pc, w, aligned_x, y, aligned_w, total_h, ctx);
+            self.grid.col_heights[col] = y + total_h;
+            self.content_y = self.grid.max_height();
+        }
+    }
+
+    pub fn widget_full<T: WidgetHost + 'static>(&mut self, w: &mut T, wh: f32, ctx: &mut UiContext) {
+        let x_off = 12.0;
+        let ww = self.cw - 2.0 * (self.padding() + x_off); // cw - 40.0
+        self.widget(w, x_off, ww, wh, ctx);
+    }
+
+    pub fn separator(&mut self) {
+        let pad = self.padding();
+        let x = self.ax(pad);
+        let max_h = self.grid.max_height().max(self.content_y);
+        let y = max_h;
+        self.pc.rect([0.18, 0.18, 0.27, 1.0], x, y, self.cw - 2.0 * pad, 1.0);
+        self.content_y = max_h + 8.0;
+        for h in &mut self.grid.col_heights {
+            *h = self.content_y;
+        }
+    }
+
+    pub fn rect(&mut self, color: [f32; 4], x_off: f32, w: f32, h: f32) {
+        let max_h = self.grid.max_height().max(self.content_y);
+        self.pc.rect(color, self.ax(x_off), max_h, w, h);
+        self.content_y = max_h + h;
+        for col_h in &mut self.grid.col_heights {
+            *col_h = self.content_y;
+        }
+    }
+
+    pub fn row_layout(&self, count: usize, gap: f32) -> Vec<(f32, f32)> {
+        let margin_x = self.content_margin();
+        let usable_w = self.content_width();
+        if count == 0 {
+            return Vec::new();
+        }
+        let total_gap = gap * (count - 1) as f32;
+        let col_w = (usable_w - total_gap).max(0.0) / count as f32;
+
+        let mut cols = Vec::with_capacity(count);
+        for i in 0..count {
+            let x = self.left + margin_x + i as f32 * (col_w + gap);
+            cols.push((x, col_w));
+        }
+        cols
+    }
+
+    /// A row whose columns are sized to what goes IN them: each gets the width
+    /// it asked for in `needs`, and whatever is left over is shared equally.
+    ///
+    /// `row_layout` splits a row evenly and knows nothing about content, so it
+    /// hands "Reboot" and "Hibernate" the same width — one floats in slack
+    /// while the other is cut off, which is what a row of mismatched labels
+    /// looks like. Sharing the SLACK equally rather than sizing proportionally
+    /// is deliberate: proportional widths would make a two-character label a
+    /// sliver, where what is wanted is "everyone fits, then everyone gets the
+    /// same bonus".
+    ///
+    /// When the needs do not fit, every column is scaled by the same factor, so
+    /// the row still cannot overflow its section and the shortfall is shared
+    /// rather than landing entirely on the last column.
+    pub fn row_layout_for(&self, needs: &[f32], gap: f32) -> Vec<(f32, f32)> {
+        let count = needs.len();
+        if count == 0 {
+            return Vec::new();
+        }
+        let total_gap = gap * (count - 1) as f32;
+        let room = (self.content_width() - total_gap).max(0.0);
+        let total_need: f32 = needs.iter().map(|n| n.max(0.0)).sum();
+
+        let widths: Vec<f32> = if total_need <= room {
+            let extra = (room - total_need) / count as f32;
+            needs.iter().map(|n| n.max(0.0) + extra).collect()
+        } else if total_need > 0.0 {
+            let scale = room / total_need;
+            needs.iter().map(|n| n.max(0.0) * scale).collect()
+        } else {
+            vec![room / count as f32; count]
+        };
+
+        let mut cols = Vec::with_capacity(count);
+        let mut x = self.content_left();
+        for w in widths {
+            cols.push((x, w));
+            x += w + gap;
+        }
+        cols
+    }
+
+
+    pub fn row<F>(&mut self, count: usize, gap: f32, h: f32, mut f: F)
+    where
+        F: FnMut(usize, f32, f32),
+    {
+        let max_h = self.grid.max_height().max(self.content_y);
+        for col_h in &mut self.grid.col_heights {
+            *col_h = max_h;
+        }
+        self.content_y = max_h;
+
+        let cols = self.row_layout(count, gap);
+        for (i, &(x, w)) in cols.iter().enumerate() {
+            f(i, x, w);
+        }
+        self.content_y += h;
+
+        for col_h in &mut self.grid.col_heights {
+            *col_h = self.content_y;
+        }
+    }
+
+    pub fn vstack(&mut self, spacing: f32) -> VStack<'_, 'a, P> {
+        VStack {
+            context: self,
+            spacing,
+        }
+    }
+
+    pub fn add_section<F>(&mut self, label: &str, focused: bool, mut render_fn: F)
+    where
+        F: FnMut(&mut SectionContext<'_, P>),
+    {
+        let pad = self.padding();
+        let (left, top, cw, is_side_by_side) = if self.grid.col_heights.len() >= 2 {
+            let col = self.grid.next_column();
+            self.last_col = col;
+            let x = self.grid.col_lefts[col];
+            let mut y = self.grid.col_heights[col];
+            if y > self.content_start_y {
+                y += self.row_gap;
+            }
+            (x, y, self.grid.col_width, true)
+        } else {
+            let left = self.ax(0.0) + pad;
+            let mut max_h = self.grid.max_height().max(self.content_y);
+            if max_h > self.content_start_y {
+                max_h += self.row_gap;
+            }
+            let top = max_h;
+            let cw = self.cw - 2.0 * pad;
+            (left, top, cw, false)
+        };
+
+        let mut sub_ctx = SectionContext::new(self.pc, left, top, cw, label, focused, true);
+        render_fn(&mut sub_ctx);
+        let new_bottom = sub_ctx.finish();
+
+        if is_side_by_side {
+            let col = self.last_col;
+            self.grid.col_heights[col] = new_bottom;
+            self.content_y = self.grid.max_height();
+        } else {
+            self.content_y = new_bottom;
+            for h in &mut self.grid.col_heights {
+                *h = new_bottom;
+            }
+        }
+    }
+
+    pub fn finish(self) -> f32 {
+        let border: [f32; 4] = if self.is_child {
+            if self.focused {
+                [0.22, 0.38, 0.24, 1.0]
+            } else {
+                [0.18, 0.18, 0.25, 1.0]
+            }
+        } else {
+            if self.focused {
+                [0.30, 0.50, 0.32, 1.0] // Focused green
+            } else {
+                [0.25, 0.25, 0.35, 1.0] // Default gray
+            }
+        };
+        let pad = self.padding();
+        let x = self.left + pad;
+        let y = self.top + 7.0;
+        let w = self.cw - 2.0 * pad;
+        let h = self.content_y - y;
+        // Under relief the well's walls are the allocation's edges, so the
+        // floor below the content matches the inset beside and above it (see
+        // `new`). The outline frame sits `pad` in from its allocation and
+        // keeps its own slack.
+        let extra_bottom = if self.relief_style { self.content_margin() } else { pad + 12.0 };
+        let bottom = y + h + extra_bottom;
+
+        if self.relief_style {
+            // Sunken style: the well spans the full allocated rect — body top
+            // edge at the tab's bottom (the tab is flush ON the body, the
+            // designer union shape; label-less sections carve tabless from the
+            // allocation top), walls on the allocation's edges, and no
+            // trailing slack so the layout gap IS the visual gap.
+            let body_y = self.relief_tab.map(|t| t.1 + t.3).unwrap_or(self.top);
+            let frame = SectionFrame {
+                x: self.left,
+                y: body_y,
+                w: self.cw,
+                h: bottom - body_y,
+                tab: self.relief_tab,
+                focused: self.focused,
+                is_child: self.is_child,
+            };
+            if self.pc.section_relief(&frame) {
+                return self.content_y + extra_bottom;
+            }
+        }
+
+        let left_edge = x;
+        let right_edge = x + w;
+        if self.label_width > 0.0 {
+            let label_x = self.label_x;
+            let gap_margin = 6.0;
+            let gap_start = label_x - gap_margin;
+            let gap_end = label_x + self.label_width + gap_margin;
+            if gap_start > left_edge {
+                self.pc.rect(border, left_edge, y, gap_start - left_edge, 1.0);
+            }
+            if right_edge > gap_end {
+                self.pc.rect(border, gap_end, y, right_edge - gap_end, 1.0);
+            }
+        } else {
+            self.pc.rect(border, left_edge, y, w, 1.0);
+        }
+
+        self.pc.rect(border, x, y + h + extra_bottom, w, 1.0);
+        self.pc.rect(border, x, y, 1.0, h + extra_bottom);
+        self.pc.rect(border, x + w - 1.0, y, 1.0, h + extra_bottom);
+        self.content_y + extra_bottom + 8.0
+    }
+}
+
+
+pub struct VStack<'b, 'a, P> {
+    pub context: &'b mut SectionContext<'a, P>,
+    pub spacing: f32,
+}
+
+impl<'b, 'a, P: RenderTarget> VStack<'b, 'a, P> {
+    pub fn add_widget<T: WidgetHost + 'static>(&mut self, w: &mut T, _ww: f32, wh: f32, ctx: &mut UiContext) {
+        let pad = self.context.padding();
+        let margin_x = 2.0 * pad + 12.0;
+        let x = self.context.left + margin_x;
+        let clamped_w = (self.context.cw - 2.0 * margin_x).max(0.0);
+
+        let mut max_h = self.context.grid.max_height().max(self.context.content_y);
+        if max_h > self.context.content_start_y {
+            max_h += self.context.row_gap;
+        }
+        let y = max_h;
+
+        let pref_h = w.preferred_height().unwrap_or(wh);
+        let top_room = w.label_strip();
+        let total_h = pref_h + top_room;
+
+        w.set_row_rect(self.context.left + pad, self.context.cw - 2.0 * pad);
+        render_widget(self.context.pc, w, x, y, clamped_w, total_h, ctx);
+
+        let new_bottom = y + total_h;
+        self.context.content_y = new_bottom;
+        for h in &mut self.context.grid.col_heights {
+            *h = new_bottom;
+        }
+        self.context.spacing(self.spacing);
+    }
+
+    /// [`add_row`](Self::add_row) with per-column widths from `needs` — see
+    /// [`SectionContext::row_layout_for`]. For a row of buttons, `needs` is
+    /// each label's measured width plus the plate's own inset.
+    pub fn add_row_for<F>(&mut self, needs: &[f32], gap: f32, h: f32, mut f: F)
+    where
+        F: FnMut(&mut SectionContext<'a, P>, usize, f32, f32),
+    {
+        let max_h = self.context.grid.max_height().max(self.context.content_y);
+        for col_h in &mut self.context.grid.col_heights {
+            *col_h = max_h;
+        }
+        self.context.content_y = max_h;
+
+        let cols = self.context.row_layout_for(needs, gap);
+        for (i, &(x, w)) in cols.iter().enumerate() {
+            self.context.content_y = max_h;
+            f(self.context, i, x, w);
+        }
+
+        let new_bottom = max_h + h;
+        self.context.content_y = new_bottom;
+        for col_h in &mut self.context.grid.col_heights {
+            *col_h = new_bottom;
+        }
+        self.context.spacing(self.spacing);
+    }
+
+    pub fn add_row<F>(&mut self, count: usize, gap: f32, h: f32, mut f: F)
+    where
+        F: FnMut(&mut SectionContext<'a, P>, usize, f32, f32),
+    {
+        let max_h = self.context.grid.max_height().max(self.context.content_y);
+        for col_h in &mut self.context.grid.col_heights {
+            *col_h = max_h;
+        }
+        self.context.content_y = max_h;
+
+        let cols = self.context.row_layout(count, gap);
+        for (i, &(x, w)) in cols.iter().enumerate() {
+            self.context.content_y = max_h;
+            f(self.context, i, x, w);
+        }
+
+        let new_bottom = max_h + h;
+        self.context.content_y = new_bottom;
+        for col_h in &mut self.context.grid.col_heights {
+            *col_h = new_bottom;
+        }
+        self.context.spacing(self.spacing);
+    }
+}
+
+pub fn get_system_monospace_font() -> &'static str {
+    static MONOSPACE_FONT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
+    MONOSPACE_FONT.get_or_init(|| {
+        if let Ok(output) = std::process::Command::new("fc-match")
+            .args(["-f", "%{family}", "monospace"])
+            .output()
+        {
+            let name = String::from_utf8_lossy(&output.stdout);
+            let parsed = name.split(',').next().unwrap_or("monospace").trim();
+            if !parsed.is_empty() {
+                return parsed.to_string();
+            }
+        }
+        "monospace".to_string()
+    })
+}
+
+pub fn get_system_sans_serif_font() -> &'static str {
+    static SANS_SERIF_FONT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
+    SANS_SERIF_FONT.get_or_init(|| {
+        if let Ok(output) = std::process::Command::new("fc-match")
+            .args(["-f", "%{family}", "sans-serif"])
+            .output()
+        {
+            let name = String::from_utf8_lossy(&output.stdout);
+            let parsed = name.split(',').next().unwrap_or("sans-serif").trim();
+            if !parsed.is_empty() {
+                return parsed.to_string();
+            }
+        }
+        "sans-serif".to_string()
+    })
+}
diff --git a/src/layout.rs b/src/layout/mod.rs
similarity index 59%
rename from src/layout.rs
rename to src/layout/mod.rs
index dac56e1..0749dee 100644
--- a/src/layout.rs
+++ b/src/layout/mod.rs
@@ -1,469 +1,11 @@
-use crate::widget::WidgetHost;
-use crate::context::UiContext;
 use std::sync::RwLock;
-use std::collections::HashMap;
-use std::sync::OnceLock;
 
-/// Per-thread overrides for runtime style writes, under `cfg(test)` only.
-///
-/// Every `graph_*`, corner-radius and control-height slot in this file is
-/// backed by the one process-wide [`STYLE_REGISTRY`], so a test that pins any
-/// of them pins it for every test running beside it. That is the same defect
-/// as the font flake fixed in 1dc0ab1, and it was live:
-/// `test_graph_style_configuration` sets ~20 style values and restores none,
-/// while `dual_geometry_views_stay_consistent` bakes quads with
-/// `graph_node_corner_radius` and then re-reads that getter to compare — so a
-/// write landing between the two makes them disagree. Widening the write
-/// window to 300ms reproduced it on demand.
-///
-/// Fixing it at the registry rather than per accessor covers every slot it
-/// holds in one place, including ones no test pins yet.
-#[cfg(test)]
-mod test_overlay {
-    use std::cell::RefCell;
-    use std::collections::HashMap;
-    thread_local! {
-        static FLOATS: RefCell<HashMap<String, f32>> = RefCell::new(HashMap::new());
-        static LENS: RefCell<HashMap<String, crate::units::Len>> = RefCell::new(HashMap::new());
-        static STRINGS: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
-    }
-    pub fn set_float(k: &str, v: f32) {
-        LENS.with(|m| m.borrow_mut().remove(k));
-        FLOATS.with(|m| m.borrow_mut().insert(k.to_string(), v));
-    }
-    pub fn set_len(k: &str, v: crate::units::Len) {
-        FLOATS.with(|m| m.borrow_mut().remove(k));
-        LENS.with(|m| m.borrow_mut().insert(k.to_string(), v));
-    }
-    pub fn set_string(k: &str, v: String) {
-        STRINGS.with(|m| m.borrow_mut().insert(k.to_string(), v));
-    }
-    pub fn get_float(k: &str) -> Option<f32> {
-        if let Some(l) = LENS.with(|m| m.borrow().get(k).copied()) {
-            return Some(l.to_px());
-        }
-        FLOATS.with(|m| m.borrow().get(k).copied())
-    }
-    pub fn get_len(k: &str) -> Option<crate::units::Len> {
-        if let Some(l) = LENS.with(|m| m.borrow().get(k).copied()) {
-            return Some(l);
-        }
-        FLOATS.with(|m| m.borrow().get(k).copied()).map(crate::units::Len::px)
-    }
-    pub fn get_string(k: &str) -> Option<String> {
-        STRINGS.with(|m| m.borrow().get(k).cloned())
-    }
-}
-
-#[derive(Debug)]
-pub struct StyleRegistry {
-    pub floats: HashMap<String, f32>,
-    pub strings: HashMap<String, String>,
-    /// Slots whose config value carried a unit (`width=(mm)2.0`). Read
-    /// through `get_float` like any other number, resolved against the
-    /// process metric (`crate::units::metric`) at EVERY read, so a metric
-    /// that arrives after config load — outputs come in after the first
-    /// style read — or changes with the display is honoured live.
-    pub lens: HashMap<String, crate::units::Len>,
-}
-
-impl StyleRegistry {
-    pub fn new() -> Self {
-        Self {
-            floats: HashMap::new(),
-            strings: HashMap::new(),
-            lens: HashMap::new(),
-        }
-    }
-
-    pub fn get_float(&self, key: &str) -> Option<f32> {
-        #[cfg(test)]
-        if let Some(v) = test_overlay::get_float(key) {
-            return Some(v);
-        }
-        if let Some(len) = self.lens.get(key) {
-            return Some(len.to_px());
-        }
-        self.floats.get(key).copied()
-    }
-
-    /// The slot as a length with its unit: the configured `Len` when one was
-    /// given, else the plain number as logical px. For editors that show the
-    /// unit the user chose rather than the resolved pixel count.
-    pub fn get_len(&self, key: &str) -> Option<crate::units::Len> {
-        #[cfg(test)]
-        if let Some(v) = test_overlay::get_len(key) {
-            return Some(v);
-        }
-        if let Some(len) = self.lens.get(key) {
-            return Some(*len);
-        }
-        self.floats.get(key).map(|v| crate::units::Len::px(*v))
-    }
-
-    pub fn get_string(&self, key: &str) -> Option<String> {
-        #[cfg(test)]
-        if let Some(v) = test_overlay::get_string(key) {
-            return Some(v);
-        }
-        self.strings.get(key).cloned()
-    }
-
-    /// A plain number wins over any earlier unit value for the slot — a
-    /// runtime `set_float` is the newest opinion.
-    pub fn set_float(&mut self, key: &str, val: f32) {
-        #[cfg(test)]
-        {
-            test_overlay::set_float(key, val);
-            return;
-        }
-        #[cfg(not(test))]
-        self.load_float(key, val);
-    }
-
-    pub fn set_len(&mut self, key: &str, len: crate::units::Len) {
-        #[cfg(test)]
-        {
-            test_overlay::set_len(key, len);
-            return;
-        }
-        #[cfg(not(test))]
-        self.load_len(key, len);
-    }
-
-    pub fn set_string(&mut self, key: &str, val: String) {
-        #[cfg(test)]
-        {
-            test_overlay::set_string(key, val);
-            return;
-        }
-        #[cfg(not(test))]
-        self.load_string(key, val);
-    }
-
-    /// The CONFIG-LOAD writes, as opposed to the runtime `set_*` ones above.
-    /// Kept apart because under `cfg(test)` a runtime set goes to a per-thread
-    /// overlay while the loaded config must stay the shared base every test
-    /// reads — routing config through `set_*` would put one thread's config
-    /// in its overlay and leave every other thread with an empty registry.
-    pub fn load_float(&mut self, key: &str, val: f32) {
-        self.lens.remove(key);
-        self.floats.insert(key.to_string(), val);
-    }
-
-    pub fn load_len(&mut self, key: &str, len: crate::units::Len) {
-        self.floats.remove(key);
-        self.lens.insert(key.to_string(), len);
-    }
-
-    pub fn load_string(&mut self, key: &str, val: String) {
-        self.strings.insert(key.to_string(), val);
-    }
-}
-
-pub static STYLE_REGISTRY: OnceLock<RwLock<StyleRegistry>> = OnceLock::new();
-
-pub fn get_style_registry() -> &'static RwLock<StyleRegistry> {
-    STYLE_REGISTRY.get_or_init(|| RwLock::new(StyleRegistry::new()))
-}
-
-pub fn lazy_init_style_registry() {
-    use std::sync::Once;
-    static INIT: Once = Once::new();
-    INIT.call_once(|| {
-        reload_config();
-    });
-}
-
-fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props: &mut String) {
-    match val {
-        serde_json::Value::Object(map) => {
-            for (k, v) in map {
-                let next_prefix = if prefix.is_empty() {
-                    k.clone()
-                } else {
-                    format!("{}.{}", prefix, k)
-                };
-                flatten_json_to_flat_props(v, &next_prefix, flat_props);
-            }
-        }
-        _ => {
-            // A retired `window_manager` spelling of a relief key emits NO
-            // line: the fall-through below strips a path's first segment, so
-            // `window_manager.bevel_depth` would otherwise land on the live
-            // `bevel_depth` registry key by accident, alias or not. The
-            // `style.surface.*` retirements need no guard — stripped, they
-            // become `surface.relief.depth` and the like, which nothing reads.
-            if matches!(prefix, "window_manager.bevel_depth" | "window_manager.bevel_width" | "window_manager.bevel_shader") {
-                return;
-            }
-            let flat_key = match prefix {
-                "style.list.font" | "style.data.list.font" => "list_font",
-                "style.list.font_color" | "style.data.list.font_color" => "list_font_color",
-                "style.control.breadcrumb.font" => "breadcrumb_font",
-
-                // The control rung's default radius: every control-scale
-                // `corner_radius` below falls back to it when its own key is unset.
-                "style.control.corner_radius" => "control_corner_radius",
-                "style.control.button.padding" => "button_padding",
-                "style.control.button.height" => "button_height",
-                "style.control.button.corner_radius" => "button_corner_radius",
-                "style.control.button.font" => "button_font",
-                "style.list.corner_radius" | "style.data.list.corner_radius" => "list_corner_radius",
-                "style.control.textbox.corner_radius" | "style.textbox.corner_radius" | "style.data.textbox.corner_radius" => "textbox_corner_radius",
-                "style.control.dropdown.color" => "dropdown_color",
-                "style.control.font_selector.font" => "font_selector_font",
-                "style.container.section.font" | "style.section.font" => "section_label_font",
-                "style.container.section.padding" | "style.section.padding" => "section_padding",
-                "style.control.button_strip.font" => "button_strip_font",
-                "style.control.button_strip.spacing" => "button_strip_spacing",
-                "style.control.dropdown.height" => "dropdown_height",
-                "style.control.dropdown.corner_radius" => "dropdown_corner_radius",
-                "style.control.font_selector.height" => "font_selector_height",
-                "style.control.font_selector.corner_radius" => "font_selector_corner_radius",
-                "style.control.label.font" => "control_label_font",
-                "style.control.label.font_detached" => "control_label_font_detached",
-                "style.control.label.margin" => "control_label_margin",
-                "style.control.slider.height" => "slider_height",
-                "style.control.slider.corner_radius" => "slider_corner_radius",
-                "style.control.slider.band_thickness" => "slider_band_thickness",
-                "style.control.slider.bulge_width" => "slider_bulge_width",
-                "style.control.slider.bulge_height" => "slider_bulge_height",
-                "style.control.progressbar.height" => "progressbar_height",
-                "style.control.rangeslider.height" => "rangeslider_height",
-                "style.control.scrollbar.width" => "scrollbar_width",
-                "style.control.scrollbar.inset" => "scrollbar_inset",
-                "style.control.spinbox.height" => "spinbox_height",
-                "style.control.spinbox.button_padding" => "spinbox_button_padding",
-                "style.control.spinbox.corner_radius" => "spinbox_corner_radius",
-                "style.control.textbox.height" | "style.textbox.height" | "style.data.textbox.height" => "textbox_height",
-                "style.control.textbox.placeholder_text_color" | "style.textbox.placeholder_text_color" | "style.data.textbox.placeholder_text_color" => "textbox_placeholder_text_color",
-                "style.control.textbox.background_color" | "style.textbox.background_color" | "style.data.textbox.background_color" => "textbox_background_color",
-                "style.control.textbox.background_edit_color" | "style.textbox.background_edit_color" | "style.data.textbox.background_edit_color" => "textbox_background_edit_color",
-                "style.control.textbox.multiline.line_wrap" | "style.textbox.multiline.line_wrap" | "style.data.textbox.multiline.line_wrap" => "textbox_line_wrap",
-                "style.control.textbox.multiline.border_width" | "style.textbox.multiline.border_width" | "style.data.textbox.multiline.border_width" => "textbox_multiline_border_width",
-                "style.control.toggle.height" => "toggle_height",
-                "style.control.toggle.border_width" => "toggle_border_width",
-                "style.control.toggle.disabled_color" => "toggle_disabled_color",
-                "style.control.toggle.border_color" => "toggle_border_color",
-                "style.control.toggle.corner_radius" => "toggle_corner_radius",
-                "window_manager.light_source_position" => "light_source_position",
-                // The DE's relief material, home style.surface.relief: these
-                // shade every bevel/boss/recess in the toolkit. `light` is
-                // the spelling (since 2026-09-28): it is the light strength,
-                // not a length. Two older spellings are RETIRED — not read,
-                // reported by path (`color::retired_surface_keys`), removed
-                // by cce-relief's Save: `relief.depth`, what every config
-                // said until that day, and `window_manager.bevel_depth` /
-                // `bevel_width`, the block these keys were born in before
-                // they had a home of their own (the compositor never read
-                // them; the spelling survived as a compat alias until the
-                // evening of 2026-09-28).
-                "style.surface.relief.light" => "bevel_depth",
-                "style.surface.relief.width" => "bevel_width",
-                // The two SHAPES of the relief, each a node under it
-                // (2026-09-28): `wall` is a carve's wall — a recess, boss,
-                // ridge or trough cut into a surface — and `edge` is the
-                // plate's perimeter roll. Each carries `height` (a length:
-                // the carve's drop, the roll's rise; unset = follow the
-                // width) and `profile` (the ramp spec of its curve, written
-                // by cce-relief, installed by reload_config). The registry
-                // keys keep their old names. The pre-rename flat spellings
-                // (`height` / `profile` were the wall's, `edge_height` /
-                // `edge_profile` the edge's) were aliases for the rest of
-                // that day and are RETIRED: not read, reported by path
-                // (`color::retired_surface_keys`), removed by cce-relief's
-                // Save, which also reads them once as a seed. cce-relief's
-                // slider knobs are NOT a style key any more
-                // (`relief.wall.knobs` / `edge.knobs`, before that
-                // `profile_knobs` / `edge_knobs`): editor state, kept in
-                // that app's own state.kdl, and read off a config only by
-                // the editor itself, as a one-time seed.
-                "style.surface.relief.wall.height" => "bevel_height",
-                "style.surface.relief.edge.height" => "roll_height",
-                "style.surface.relief.wall.profile" => "bevel_profile_spec",
-                "style.surface.relief.edge.profile" => "roll_profile_spec",
-                "style.container.section.depth" => "section_depth",
-                "style.surface.param.backdrop_compression" => "param_compression",
-                "style.surface.param.label_layout" => "param_label_layout",
-                // The relief's shader toggle lives with the relief
-                // (`relief shader=(bool)false` is the legacy banded look);
-                // `window_manager.bevel_shader`, its old home, is retired
-                // and reported like the block's other bevel keys.
-                "style.surface.relief.shader" => "bevel_shader",
-                "window_manager.control_relief" => "control_relief",
-                "window_manager.corner_shape" => "corner_shape",
-                "style.control.ramp.height" => "ramp_height",
-                "style.layout.column.gap" => "column_gap",
-                "style.control.control_panel.padding" => "control_panel_padding",
-                "style.control.control_panel.gap" => "control_panel_gap",
-                "style.status.normal_color" => "status_normal_color",
-                "style.status.background_color" => "status_background_color",
-                "style.status.background_blur" => "status_background_blur",
-                "style.highlight.primary" => "primary_highlight_color",
-                "style.window.page_opacity" => "page_opacity",
-                "style.window.page_margin" => "page_margin",
-                "style.window.plate_padding" => "plate_padding",
-                "style.window.transition_duration" => "transition_duration",
-                "style.overlay.behavior" => "overlay_behavior",
-                "style.overlay.width" => "overlay_width",
-                "style.overlay.position" => "overlay_position",
-                "style.overlay.border_gap" => "overlay_border_gap",
-                "style.editor.last_page" | "style.data.editor.last_page" => "last_page",
-                "style.data.tree.corner_radius" => "tree_corner_radius",
-                "style.data.tree.opacity" => "tree_opacity",
-                "style.data.tree.blur" => "tree_blur",
-                "style.data.tree.font" => "tree_font",
-                "style.surface.desktop.gap_color" => "desktop_gap_color",
-                "style.surface.desktop.cell_color" => "desktop_cell_color",
-                "style.surface.desktop.gap_width" => "desktop_gap_width",
-                "style.surface.desktop.cell_fade_inset" => "desktop_cell_fade_inset",
-                "style.surface.desktop.mode" => "desktop_mode",
-                "style.surface.desktop.solid_color" => "desktop_solid_color",
-                "style.surface.desktop.grid_cell_size" => "desktop_grid_scale",
-                "style.surface.desktop.grid_cell_width" => "grid_cell_width",
-                "style.surface.desktop.grid_cell_height" => "grid_cell_height",
-                "style.surface.plate.padding" => "plate_padding",
-                "style.surface.plate.gap" => "plate_gap",
-                "style.control.gap" => "control_gap",
-                // The context menu's own radius (`menu_corner_radius`): a
-                // popover's corner is control-scale, not pane-scale.
-                "style.surface.menu.corner_radius" => "menu_corner_radius",
-                // `style.surface.plate.root.*` is the one spelling of the
-                // root-plate style (RFC Phase 7a). The slot names keep the
-                // historical `root_plate_` prefix; the legacy `root plate.*`
-                // config read-alias was removed 2026-09-06.
-                "style.surface.plate.root.padding" => "root_plate_padding",
-                "style.surface.plate.root.gap" => "root_plate_gap",
-                "style.surface.plate.root.color" => "root_plate_color",
-                "style.surface.plate.root.blur" => "root_plate_blur",
-                "style.surface.plate.root.corner_radius" => "root_plate_corner_radius",
-                "style.surface.plate.root.menubar.color" => "root_plate_menubar_color",
-                "style.surface.plate.root.menubar.text_color" => "root_plate_menubar_text_color",
-                "style.surface.plate.root.menubar.blur" => "root_plate_menubar_blur",
-                "style.surface.plate.root.menubar.font" => "menubar_font",
-                "style.surface.statusbar.color" => "root_plate_statusbar_color",
-                "style.surface.statusbar.text_color" => "root_plate_statusbar_text_color",
-                "style.surface.statusbar.blur" => "root_plate_statusbar_blur",
-                "style.surface.statusbar.font" => "statusbar_font",
-                "style.surface.page.opacity" => "page_opacity",
-                "style.surface.page.margin" => "page_margin",
-                "style.surface.graph.grid_color" => "graph_grid_color",
-                "style.surface.graph.opacity" => "graph_opacity",
-                "style.surface.graph.node.opacity" => "graph_node_opacity",
-                "style.surface.graph.spacing_x" => "graph_spacing_x",
-                "style.surface.graph.spacing_y" => "graph_spacing_y",
-                "style.surface.graph.line_width" => "graph_line_width",
-                "style.surface.graph.node.width" => "graph_node_width",
-                "style.surface.graph.node.height" => "graph_node_height",
-                "style.surface.graph.grid_snap" => "graph_grid_snap",
-                "style.surface.graph.blur" => "graph_blur",
-                "style.surface.graph.font" => "graph_font",
-                "style.surface.graph.node.color" => "graph_node_color",
-                "style.surface.graph.node.font" => "graph_node_font",
-                "style.surface.graph.node.delete" => "graph_node_delete",
-                "style.surface.graph.node.selected_color" => "graph_node_selected_color",
-                "style.surface.graph.node.drag_color" => "graph_node_drag_color",
-                "style.surface.graph.node.corner_radius" => "graph_node_corner_radius",
-                "style.surface.graph.node.wire_color" => "graph_wire_color",
-                "style.surface.graph.node.wire_highlight_color" => "graph_wire_highlight_color",
-                "style.surface.graph.node.wire_size" => "graph_wire_size",
-                "style.surface.graph.node.wire_style" => "graph_wire_style",
-                "style.surface.graph.node.wire_activation_radius" => "graph_wire_activation_radius",
-                "style.surface.graph.node.connector_color" => "graph_connector_color",
-                "style.surface.graph.node.connector_highlight_color" => "graph_connector_highlight_color",
-                "style.surface.graph.node.connector_size" => "graph_connector_size",
-                "style.surface.graph.node.connector_activation_radius" => "graph_connector_activation_radius",
-                "style.surface.plate.color" => "plate_color",
-                "style.surface.plate.border_color" => "plate_border_color",
-                "style.surface.plate.border_thickness" => "plate_border_thickness",
-                "input.touchpad.natural_scroll" => "touchpad_natural_scroll",
-                
-                other => {
-                    if let Some(rest) = other.strip_prefix("layout.") {
-                        rest
-                    } else if let Some(rest) = other.strip_prefix("transparency.") {
-                        rest
-                    } else {
-                        if let Some(idx) = other.find('.') {
-                            &other[idx + 1..]
-                        } else {
-                            other
-                        }
-                    }
-                }
-            };
-            
-            if let Some(s) = val.as_str() {
-                flat_props.push_str(&format!("{} = \"{}\"\n", flat_key, s));
-            } else if let Some(b) = val.as_bool() {
-                flat_props.push_str(&format!("{} = {}\n", flat_key, b));
-            } else if let Some(n) = val.as_f64() {
-                flat_props.push_str(&format!("{} = {}\n", flat_key, n));
-            } else if let Some(n) = val.as_i64() {
-                flat_props.push_str(&format!("{} = {}\n", flat_key, n));
-            }
-        }
-    }
-}
-
-// There is no alias-precedence pass any more. For the rest of 2026-09-28
-// `prefer_relief_spellings` ran here, dropping a relief key's legacy
-// spelling whenever its current one was present (`height` under
-// `wall.height`, `depth` under `light`), because two spellings of one
-// registry key would otherwise be decided by the order the JSON handed
-// the lines out. Every legacy spelling of the relief is retired now — not
-// read at all, reported by `color::retired_surface_keys` — so there is
-// nothing left to prefer, and a config is what it says.
-
-fn read_config() -> Option<String> {
-    let path = crate::config::get_config_path();
-    if let Ok(content) = std::fs::read_to_string(&path) {
-        let val = crate::config::parse_kdl_to_json(&content);
-        let mut flat_props = String::new();
-        flatten_json_to_flat_props(&val, "", &mut flat_props);
-        return Some(flat_props);
-    }
-    None
-}
-
-pub fn read_config_value(target_key: &str) -> Option<String> {
-    let path = crate::config::get_config_path();
-    if let Ok(content) = std::fs::read_to_string(&path) {
-        let val = crate::config::parse_kdl_to_json(&content);
-        let mut flat_props = String::new();
-        flatten_json_to_flat_props(&val, "", &mut flat_props);
-        for line in flat_props.lines() {
-            let trimmed = line.trim();
-            if let Some(eq_idx) = trimmed.find('=') {
-                let key = trimmed[..eq_idx].trim();
-                if key == target_key {
-                    let val_str = trimmed[eq_idx + 1..].trim().trim_matches('"').trim();
-                    return Some(val_str.to_string());
-                }
-            }
-        }
-    }
-    None
-}
-
-pub fn parse_font_string(s: &str) -> (String, Option<f32>) {
-    let (family, size) = split_font_string(s);
-    (family.to_string(), size)
-}
-
-/// [`parse_font_string`] borrowing the family from `s` instead of copying it.
-pub fn split_font_string(s: &str) -> (&str, Option<f32>) {
-    let s = s.trim();
-    if let Some(last_space_idx) = s.rfind(' ') {
-        let (family, size_str) = s.split_at(last_space_idx);
-        let size_str = size_str.trim();
-        if let Ok(size) = size_str.parse::<f32>() {
-            return (family.trim(), Some(size));
-        }
-    }
-    (s, None)
-}
+mod registry;
+pub use registry::*;
+mod bridge;
+pub use bridge::*;
+mod legacy;
+pub use legacy::*;
 
 static SECTION_PADDING: RwLock<f32> = RwLock::new(8.0);
 
@@ -3984,2569 +3526,65 @@ pub fn set_rangeslider_height(height: f32) {
 
 
 
-/// One step carve a widget's `paint` draws, handed to a flat-path host through
-/// [`RenderTarget::relief_carve`] so it can re-emit it as a real prim.
+/// The DE's font families, from the shared config's `fonts { }` block:
+/// `(sans_serif, serif, monospace, terminal)`.
 ///
-/// Geometry always comes from the WIDGET (`TextBox::well`, `Toggle::well` /
-/// `Toggle::slide_plate`), never re-derived here — a second copy of that math
-/// in the bridge is exactly how the flat host's carve and the drawn one drift
-/// apart.
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub struct ReliefCarve {
-    pub kind: CarveKind,
-    pub x: f32,
-    pub y: f32,
-    pub w: f32,
-    pub h: f32,
-    /// Per-corner radii, clockwise from top-left.
-    pub radii: (f32, f32, f32, f32),
-    /// Full width of the step's transition band.
-    pub depth: f32,
-    /// Which walls the carve has (top, right, bottom, left). A suppressed wall
-    /// means the step runs flush to its neighbour there — a Spinbox's field
-    /// running into its button column, where the two meet in ONE step rather
-    /// than two facing walls.
-    pub edges: (bool, bool, bool, bool),
-}
-
-/// Which way a [`ReliefCarve`] steps.
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum CarveKind {
-    /// Interior one step DOWN ([`crate::scene::paint::PaintCtx::recess_edges`]).
-    /// `tint` lights the rim in the focus accent (`recess_tinted`).
-    Recess { tint: Option<[f32; 3]> },
-    /// Interior one step UP ([`crate::scene::paint::PaintCtx::boss_edges`]).
-    Boss { tint: Option<[f32; 3]> },
-    /// A FLUSH inset ([`crate::scene::paint::PaintCtx::trough_edges`]): the
-    /// interior stays level with the surface and a valley seam runs the
-    /// boundary — the closed dropdown's chrome, for a control that is part
-    /// of the plate rather than a step up or down from it.
-    Trough,
-}
-
-impl ReliefCarve {
-    /// This carve shifted vertically — the page-scroll adjustment a host
-    /// applies when it re-emits collected carves.
-    pub fn shifted_y(self, dy: f32) -> Self {
-        Self { y: self.y + dy, ..self }
-    }
-}
-
-pub trait RenderTarget {
-    fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32);
-    fn rect_with_radius(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, _radius: f32) {
-        self.rect(color, x, y, w, h);
-    }
-    fn rect_with_radius_corners(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, _corners: (bool, bool, bool, bool)) {
-        self.rect_with_radius(color, x, y, w, h, radius);
-    }
-    fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
-    fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], _font: &str) {
-        self.text(content, x, y, size, color);
-    }
-    fn text_with_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], _bounds: Option<[f32; 4]>) {
-        self.text(content, x, y, size, color);
-    }
-    fn text_with_font_and_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str, _bounds: Option<[f32; 4]>) {
-        self.text_with_font(content, x, y, size, color, font);
-    }
-    fn push_clip_rect(&mut self, _x: f32, _y: f32, _w: f32, _h: f32) {}
-    fn pop_clip_rect(&mut self) {}
-    /// A bundled cce-icons glyph — see `PaintCtx::icon`. A target that
-    /// cannot draw images (the legacy `PopoverCollector`) draws nothing.
-    fn icon(&mut self, _name: &str, _rect: crate::scene::layout::Rect, _color: [f32; 4]) {}
-    /// A straight stroke — see `PaintCtx::vector`. What a graph's wires are
-    /// made of; a target that draws none (the default) shows no wires.
-    fn line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _thickness: f32, _color: [f32; 4], _cap: crate::scene::paint::Cap) {}
-    /// A stroked arc, `radius` its OUTER edge — see `PaintCtx::arc`. A
-    /// rounded wire's bends.
-    fn arc(&mut self, _cx: f32, _cy: f32, _radius: f32, _thickness: f32, _start: f32, _end: f32, _color: [f32; 4]) {}
-    /// A filled disc — see `PaintCtx::circle`. A graph's ports.
-    fn circle(&mut self, _cx: f32, _cy: f32, _radius: f32, _color: [f32; 4]) {}
-    /// A flush inset control plate ([`PaintCtx::inset_plate`]) — the raised
-    /// control surface (groove ring down, beveled lip back up). Lets a popover
-    /// draw the ACTUAL widget surface expanded (the Dropdown's grown trigger).
-    /// Hosts without relief prims degrade to a flat rounded fill.
-    fn inset_plate(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, _depth: f32) {
-        self.rect_with_radius(color, x, y, w, h, radius);
-    }
-    /// [`inset_plate`](Self::inset_plate) with the rim lit — the focused
-    /// control plate's ring (`ControlPlate::with_tint`). Hosts without relief
-    /// prims draw the plain plate.
-    fn inset_plate_tinted(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32, _tint: [f32; 3]) {
-        self.inset_plate(color, x, y, w, h, radius, depth);
-    }
-    /// One step carve from a widget's `paint` ([`ReliefCarve`]) — offered here
-    /// for the same reason as `inset_plate`: the legacy `all_quads` stream
-    /// carries no relief prims, so a flat-path host never sees them.
-    ///
-    /// The default is deliberately a NO-OP, not a fill. These controls have
-    /// transparent faces by design (the host surface IS the well floor / the
-    /// plate's face), so the carve is their entire decoration — a host that
-    /// can't carve has nothing truthful to draw, and a solid box here would
-    /// paint every text field a flat slab it never had.
-    fn relief_carve(&mut self, _carve: &ReliefCarve) {}
-    /// Whether this host renders sections as sunken wells (the designer idiom).
-    /// `SectionContext` then lays the title out left-aligned over its tab box
-    /// instead of centered on the top border.
-    fn section_relief_style(&self) -> bool {
-        false
-    }
-    /// The section frame hatch: `SectionContext::finish` offers the frame here
-    /// before falling back to the legacy 1px outline. A relief-capable host
-    /// returns true and carves the section into its plate instead (the
-    /// designer sunken-well idiom); the tuple hosts keep the default.
-    fn section_relief(&mut self, _frame: &SectionFrame) -> bool {
-        false
-    }
-}
-
-/// A section frame offered to [`RenderTarget::section_relief`]: the content
-/// body box plus, under [`RenderTarget::section_relief_style`], the title tab
-/// box the label was laid out in — the tab sits flush on the body's top edge
-/// (the designer union-carve shape).
-pub struct SectionFrame {
-    pub x: f32,
-    pub y: f32,
-    pub w: f32,
-    pub h: f32,
-    pub tab: Option<(f32, f32, f32, f32)>,
-    pub focused: bool,
-    pub is_child: bool,
-}
-
-pub struct PopoverCollector {
-    pub rects: Vec<([f32; 4], f32, f32, f32, f32)>,
-    pub texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>, Option<[f32; 4]>)>,
-}
-
-impl PopoverCollector {
-    pub fn new() -> Self {
-        Self { rects: Vec::new(), texts: Vec::new() }
-    }
-}
-
-impl RenderTarget for PopoverCollector {
-    fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
-        self.rects.push((color, x, y, w, h));
-    }
-
-    fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
-        self.texts.push((content.to_string(), size, x, y, color, None, None));
-    }
-
-    fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str) {
-        self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), None));
-    }
-
-    fn text_with_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], bounds: Option<[f32; 4]>) {
-        self.texts.push((content.to_string(), size, x, y, color, None, bounds));
-    }
-
-    fn text_with_font_and_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str, bounds: Option<[f32; 4]>) {
-        self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), bounds));
-    }
-}
-
-
-pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut T, x: f32, y: f32, ww: f32, wh: f32, ctx: &mut UiContext) {
-    ctx.register_host(w);
-    // The flat-host contract, the same block `set_rect` takes: `(x, y)` is the top of
-    // the detached label and `wh` the block height, label strip included. `layout`
-    // takes the CONTENT origin and height, so step down by the strip.
-    let strip = w.label_strip();
-    let content_h = (wh - strip).max(0.0);
-    w.layout(crate::widget::Point { x, y: y + strip }, crate::widget::LayoutConstraints::new(ww, ww, content_h, content_h), ctx);
-
-    // Shape, which on this path nobody else does. A flat host consumes
-    // `all_quads`, so `prepare_text` — where a TextBox records the per-glyph x
-    // offsets its selection highlight, caret and click->index mapping all read
-    // — was never called for the widgets it draws. Those three then fell back
-    // to `measure_text_width("M")`, an SVG-rasterized INKED extent rather than
-    // an advance, so the highlight under-ran the glyphs by a few px per
-    // character (a full glyph by the end of "example.com"). Hosts that shape
-    // for themselves (cce-files, the TreeList) just re-read the shared buffer
-    // cache here.
-    if let Ok(mut fs) = crate::geometry_font_system().lock() {
-        w.prepare_text(&mut fs);
-    }
-    let (style_r, corners) = w.corner_style();
-    let r = if corners != (false, false, false, false) { style_r } else { 0.0 };
-    let (wx, mut wy, www, mut whh) = w.rect();
-    let top_room = w.label_strip();
-    wy += top_room;
-    whh -= top_room;
-
-    // ONE ordered replay of the paint walk — the same walk the live display-
-    // list render runs (`scene::painter`), every prim in the order the widget
-    // painted it, each mapped onto the flat host's RenderTarget surface.
-    //
-    // This used to be three passes over three typed views of the same paint:
-    // the relief prims (downcast per widget type and re-derived from the
-    // widget's accessors — the Dropdown's inset plate, the TextBox's well, the
-    // Button's face, the Toggle's faces and steps), then every plain quad
-    // (`all_quads`), then every rounded quad (`all_rounded_quads`). Splitting
-    // one paint into typed streams loses the order between them, and the
-    // order is the picture: a TextBox draws its rounded background, carves
-    // its well, THEN lays the selection highlight and caret on top — the
-    // three-pass replay put the well under the highlight and the background
-    // over both. A Dropdown's hovered row is drawn after its menu plate; a
-    // host that replays plates after rects buries the highlight. The prim
-    // walk keeps the widget's order, covers every widget instead of the four
-    // that had a special case, and carries the relief prims' own per-corner
-    // radii and depth (the re-derivations rounded those off).
-    //
-    // Mapping onto the tuple surface: Quad and RoundedRect are the two
-    // native fills (the root's plain background keeps its solid-border
-    // expansion and window-corner resolution); a zero-stroke Border is an
-    // inset plate's FACE and is held until the Trough that follows it, so the
-    // pair reaches the host as ONE `inset_plate` call (a relief host carves
-    // it for real; the default degrades to the flat fill); Recess/Boss go to
-    // `relief_carve`; a Bevel degrades to its fill — what a flat host can
-    // draw of a raised plate. Ridges, circles, arcs, vectors and images have
-    // no flat-surface counterpart and are skipped, as they always were.
-    let solid_border = w.solid_border();
-    let mut text_scratch = crate::scene::paint::PaintCtx::new();
-    crate::scene::painter::paint_root_into(&*ctx, &*w, &mut text_scratch);
-
-    // A zero-stroke Border waiting for its Trough: (rect, radii, fill).
-    let mut pending_face: Option<(crate::scene::layout::Rect, crate::scene::paint::Radii, [f32; 4])> = None;
-    fn same_rect(a: crate::scene::layout::Rect, b: crate::scene::layout::Rect) -> bool {
-        (a.x - b.x).abs() < 0.1 && (a.y - b.y).abs() < 0.1 && (a.width - b.width).abs() < 0.1 && (a.height - b.height).abs() < 0.1
-    }
-    fn emit_rounded(pc: &mut dyn RenderTarget, rect: crate::scene::layout::Rect, radii: crate::scene::paint::Radii, color: [f32; 4]) {
-        let (r1, r2, r3, r4) = radii;
-        let radius = r1.max(r2).max(r3).max(r4);
-        let mask = (r1 > 0.0, r2 > 0.0, r3 > 0.0, r4 > 0.0);
-        pc.rect_with_radius_corners(color, rect.x, rect.y, rect.width, rect.height, radius, mask);
-    }
-
-    for item in text_scratch.finish().items {
-        use crate::scene::paint::Prim;
-        let trough_for_face = match (&item.prim, &pending_face) {
-            (Prim::Trough { rect, .. }, Some((face_rect, _, _))) => same_rect(*rect, *face_rect),
-            // `inset_plate`'s edge: a field that is all run.
-            (Prim::Field { rect, split, end, .. }, Some((face_rect, _, _))) => {
-                *split <= rect.x && *end >= rect.x + rect.width && same_rect(*rect, *face_rect)
-            }
-            _ => false,
-        };
-        if !trough_for_face {
-            if let Some((frect, fradii, fill)) = pending_face.take() {
-                emit_rounded(pc, frect, fradii, fill);
-            }
-        }
-        match item.prim {
-            Prim::Quad { rect, color: qc } => {
-                let (qx, qy, qw, qh) = (rect.x, rect.y, rect.width, rect.height);
-                let extra_corners = (
-                    corners.0 && qx <= wx + 1.5 && qy <= wy + 1.5,
-                    corners.1 && qx + qw >= wx + www - 1.5 && qy <= wy + 1.5,
-                    corners.2 && qx + qw >= wx + www - 1.5 && qy + qh >= wy + whh - 1.5,
-                    corners.3 && qx <= wx + 1.5 && qy + qh >= wy + whh - 1.5,
-                );
-
-                let (resolved_r, resolved_corners) = if r <= 0.1 || corners == (false, false, false, false) || extra_corners == (false, false, false, false) {
-                    (0.0, (false, false, false, false))
-                } else {
-                    (r, extra_corners)
-                };
-
-                // The widget's own background quad with a solid border: full-size
-                // border quad, then the inset background over it.
-                let mut border_drawn = false;
-                let is_bg_quad = (qx - wx).abs() < 0.1 && (qy - wy).abs() < 0.1 && (qw - www).abs() < 0.1 && (qh - whh).abs() < 0.1;
-                if is_bg_quad {
-                    if let Some((border_color, thickness)) = solid_border {
-                        if thickness > 0.0 {
-                            pc.rect_with_radius_corners(border_color, qx, qy, qw, qh, resolved_r, resolved_corners);
-                            pc.rect_with_radius_corners(
-                                qc,
-                                qx + thickness,
-                                qy + thickness,
-                                (qw - 2.0 * thickness).max(0.0),
-                                (qh - 2.0 * thickness).max(0.0),
-                                (resolved_r - thickness).max(0.0),
-                                resolved_corners,
-                            );
-                            border_drawn = true;
-                        }
-                    }
-                }
-
-                if !border_drawn {
-                    pc.rect_with_radius_corners(qc, qx, qy, qw, qh, resolved_r, resolved_corners);
-                }
-            }
-            Prim::RoundedRect { rect, radius, corners: qcorners, color: qc } => {
-                pc.rect_with_radius_corners(qc, rect.x, rect.y, rect.width, rect.height, radius, qcorners);
-            }
-            Prim::Border { rect, radii, fill, border, thickness } => {
-                if thickness > 0.0 && border[3].abs() > 0.001 {
-                    emit_rounded(pc, rect, radii, border);
-                    if fill[3].abs() > 0.001 {
-                        let inner = crate::scene::layout::Rect {
-                            x: rect.x + thickness,
-                            y: rect.y + thickness,
-                            width: (rect.width - 2.0 * thickness).max(0.0),
-                            height: (rect.height - 2.0 * thickness).max(0.0),
-                        };
-                        let (r1, r2, r3, r4) = radii;
-                        let shrink = |v: f32| if v > 0.0 { (v - thickness).max(0.0) } else { 0.0 };
-                        emit_rounded(pc, inner, (shrink(r1), shrink(r2), shrink(r3), shrink(r4)), fill);
-                    }
-                } else if fill[3].abs() > 0.001 {
-                    // abs(): a negative alpha is the frost sentinel, a real face.
-                    pending_face = Some((rect, radii, fill));
-                }
-            }
-            // A flat host has no blended outline: the two-box form
-            // `Prim::Field` replaced — the well to the seam, the run's flush
-            // plate past it.
-            Prim::Field { rect, radii, depth, split, end, tint } => {
-                let (fl, fr) = (rect.x, rect.x + rect.width);
-                let (well_l, well_r) = (split > fl, end < fr);
-                // All run and no well (`PaintCtx::inset_plate`'s edge): the
-                // plate alone, with the face that came before it.
-                let face = if !well_l && !well_r { pending_face.take().map(|(_, _, fill)| fill) } else { None }.unwrap_or([0.0; 4]);
-                let rx = split.max(fl);
-                let rw = (end.min(fr) - rx).max(0.0);
-                let mut well = |x: f32, w: f32, radii: (f32, f32, f32, f32)| {
-                    pc.relief_carve(&ReliefCarve {
-                        kind: CarveKind::Recess { tint },
-                        x,
-                        y: rect.y,
-                        w,
-                        h: rect.height,
-                        radii,
-                        depth,
-                        edges: (true, true, true, true),
-                    });
-                };
-                if well_l {
-                    well(fl, rx - fl, (radii.0, 0.0, 0.0, radii.3));
-                }
-                if well_r {
-                    well(rx + rw, fr - rx - rw, (0.0, radii.1, radii.2, 0.0));
-                }
-                let r = if well_r { radii.0.max(radii.3) } else { radii.1.max(radii.2) };
-                match tint {
-                    Some(t) => pc.inset_plate_tinted(face, rx, rect.y, rw, rect.height, r, depth, t),
-                    None => pc.inset_plate(face, rx, rect.y, rw, rect.height, r, depth),
-                }
-            }
-            Prim::Trough { rect, radii, depth, tint, .. } => {
-                let face = pending_face.take().map(|(_, _, fill)| fill).unwrap_or([0.0; 4]);
-                let (r1, r2, r3, r4) = radii;
-                let r = r1.max(r2).max(r3).max(r4);
-                match tint {
-                    Some(t) => pc.inset_plate_tinted(face, rect.x, rect.y, rect.width, rect.height, r, depth, t),
-                    None => pc.inset_plate(face, rect.x, rect.y, rect.width, rect.height, r, depth),
-                }
-            }
-            Prim::Bevel { rect, radii, material, .. } => {
-                let color = material.fill(crate::scene::material::PlateRole::Nested);
-                if color[3].abs() > 0.001 {
-                    emit_rounded(pc, rect, radii, color);
-                }
-            }
-            Prim::Recess { rect, radii, depth, edges, tint } => {
-                pc.relief_carve(&ReliefCarve {
-                    kind: CarveKind::Recess { tint },
-                    x: rect.x,
-                    y: rect.y,
-                    w: rect.width,
-                    h: rect.height,
-                    radii,
-                    depth,
-                    edges,
-                });
-            }
-            Prim::Boss { rect, radii, depth, edges, tint } => {
-                pc.relief_carve(&ReliefCarve {
-                    kind: CarveKind::Boss { tint },
-                    x: rect.x,
-                    y: rect.y,
-                    w: rect.width,
-                    h: rect.height,
-                    radii,
-                    depth,
-                    edges,
-                });
-            }
-            // Text via the paint walk: each widget's Text prims (content font + scroll-ancestor
-            // clip) exactly as the live display-list render does; the prim already carries the
-            // per-widget font + bounds.
-            Prim::Text { text, x, y, font_size, color, font, bounds, .. } => {
-                let color_f32 = [
-                    color[0] as f32 / 255.0,
-                    color[1] as f32 / 255.0,
-                    color[2] as f32 / 255.0,
-                    1.0,
-                ];
-                // Compose the walk's container clip with the prim's own bounds (the engine's dl-text
-                // merge), so a clipping ancestor still bounds the text.
-                let clip = item.clip.map(|c| [c.x, c.y, c.x + c.width, c.y + c.height]);
-                let merged = match (clip, bounds) {
-                    (Some(a), Some(b)) => Some([a[0].max(b[0]), a[1].max(b[1]), a[2].min(b[2]), a[3].min(b[3])]),
-                    (Some(a), None) => Some(a),
-                    (None, b) => b,
-                };
-                match font {
-                    Some(ref f) => pc.text_with_font_and_bounds(&text, x, y, font_size, color_f32, f, merged),
-                    None => pc.text_with_bounds(&text, x, y, font_size, color_f32, merged),
-                }
-            }
-            // Strokes, arcs and discs — a graph's wires and ports, chiefly.
-            // Until 2026-10-06 these were dropped too, so a graph in a flat
-            // host (cce-files' Graph page) drew its nodes and no wires.
-            Prim::Vector { x1, y1, x2, y2, thickness, color, cap } => {
-                if let Some(c) = item.clip {
-                    pc.push_clip_rect(c.x, c.y, c.width, c.height);
-                }
-                pc.line(x1, y1, x2, y2, thickness, color, cap);
-                if item.clip.is_some() {
-                    pc.pop_clip_rect();
-                }
-            }
-            Prim::Arc { cx, cy, radius, thickness, start, end, color } => {
-                if let Some(c) = item.clip {
-                    pc.push_clip_rect(c.x, c.y, c.width, c.height);
-                }
-                pc.arc(cx, cy, radius, thickness, start, end, color);
-                if item.clip.is_some() {
-                    pc.pop_clip_rect();
-                }
-            }
-            Prim::Circle { cx, cy, radius, color } => {
-                if let Some(c) = item.clip {
-                    pc.push_clip_rect(c.x, c.y, c.width, c.height);
-                }
-                pc.circle(cx, cy, radius, color);
-                if item.clip.is_some() {
-                    pc.pop_clip_rect();
-                }
-            }
-            // A widget's GLYPH (a dropdown's arrow, a spinbox's −/+, a menu
-            // mark): drawn by name through the host's `RenderTarget::icon`,
-            // since an image id means nothing to a flat host. Until this arm
-            // every widget glyph vanished from a flat host — the toolkit drew
-            // its symbols as text before 2026-10-05, and this replay dropped
-            // images. An image that is not a bundled glyph is still dropped.
-            Prim::Image { image, rect, alpha } => {
-                if let Some((name, _, tint)) = crate::icon_source(image) {
-                    let [r, g, b] = tint.unwrap_or([255, 255, 255]);
-                    let color = [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, alpha];
-                    if let Some(c) = item.clip {
-                        pc.push_clip_rect(c.x, c.y, c.width, c.height);
-                    }
-                    pc.icon(&name, rect, color);
-                    if item.clip.is_some() {
-                        pc.pop_clip_rect();
-                    }
-                }
-            }
-            _ => {}
-        }
-    }
-    if let Some((frect, fradii, fill)) = pending_face.take() {
-        emit_rounded(pc, frect, fradii, fill);
-    }
-    if w.popover_rect().is_some() {
-        ctx.register_popover(w);
-    }
-}
-
-pub fn render_popovers(pc: &mut dyn RenderTarget, ctx: &UiContext) {
-    for &pop_id in &ctx.active_popovers {
-        if let Some(ptr) = ctx.tree.get_ptr(pop_id) {
-            unsafe {
-                (*ptr).render_popover(pc);
-            }
-        }
-    }
-    // Registry sweep for open popovers the app never registered (popover
-    // registration is optional and spotty) — the same fallback the coverage
-    // check and the engine's outside-press close use.
-    for (id, ptr) in ctx.tree.iter_registered() {
-        if ctx.active_popovers.contains(&id) {
-            continue;
-        }
-        unsafe {
-            if let Some(w) = ptr.as_ref() {
-                if w.visible() && w.popover_rect().is_some() {
-                    w.render_popover(pc);
-                }
-            }
-        }
-    }
-}
-
-pub fn partition_concentric_corners(
-    x: f32, y: f32, w: f32, h: f32,
-    _r_std: f32,
-    r_adjust: [f32; 4],
-    color: [f32; 4],
-) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
-    let mut quads = Vec::new();
-
-    let r0 = r_adjust[0];
-    let r1 = r_adjust[1];
-    let r2 = r_adjust[2];
-    let r3 = r_adjust[3];
-
-    let max_left = r0.max(r3);
-    let max_right = r1.max(r2);
-
-    let mut push_valid_quad = |qx: f32, qy: f32, qw: f32, qh: f32, qr: f32, qcorners: (bool, bool, bool, bool)| {
-        if qw > 0.001 && qh > 0.001 {
-            quads.push((qx, qy, qw, qh, qr, color, qcorners));
-        }
+/// These used to live in `~/.config/fontconfig/fonts.conf`, read back out of
+/// fontconfig's XML by alias. Three of the seven aliases it carried
+/// (`window-borders`, `status-interface`, `fuzzel`) were cce inventions
+/// squatting in fontconfig's family namespace, and by the end none of them was
+/// read by anything: window borders lost their text when titlebars went away,
+/// the status bar moved to `module { font }` / `/style/status/font` in KDL and
+/// only ever consulted the alias as a last-resort fallback, and fuzzel was
+/// replaced by cce-cloud. The settings app's Fonts page — which edited that
+/// file, rewriting it wholesale and preserving only its `<dir>` lines — went
+/// with them.
+///
+/// fonts.conf is still fontconfig's file and still governs GTK/Electron apps;
+/// cce simply no longer reads or writes it. Per-app overrides work here because
+/// this reads the merged config, the same way the status bar's font does.
+pub fn read_preferred_fonts() -> (String, String, String, String) {
+    let get = |key: &str, fallback: &str| {
+        crate::config::get_string(&format!("/fonts/{key}"))
+            .filter(|s| !s.trim().is_empty())
+            .unwrap_or_else(|| fallback.to_string())
     };
-
-    // 1. Center vertical block
-    push_valid_quad(x + max_left, y, w - max_left - max_right, h, 0.0, (false, false, false, false));
-
-    // 2. Left block
-    push_valid_quad(x, y + r0, max_left, h - r0 - r3, 0.0, (false, false, false, false));
-
-    // 3. Top-left transition
-    push_valid_quad(x + r0, y, max_left - r0, r0, 0.0, (false, false, false, false));
-
-    // 4. Bottom-left transition
-    push_valid_quad(x + r3, y + h - r3, max_left - r3, r3, 0.0, (false, false, false, false));
-
-    // 5. Right block
-    push_valid_quad(x + w - max_right, y + r1, max_right, h - r1 - r2, 0.0, (false, false, false, false));
-
-    // 6. Top-right transition
-    push_valid_quad(x + w - max_right, y, max_right - r1, r1, 0.0, (false, false, false, false));
-
-    // 7. Bottom-right transition
-    push_valid_quad(x + w - max_right, y + h - r2, max_right - r2, r2, 0.0, (false, false, false, false));
-
-    // 8. Corner 0 (top-left)
-    push_valid_quad(x, y, r0, r0, r0, (true, false, false, false));
-
-    // 9. Corner 1 (top-right)
-    push_valid_quad(x + w - r1, y, r1, r1, r1, (false, true, false, false));
-
-    // 10. Corner 2 (bottom-right)
-    push_valid_quad(x + w - r2, y + h - r2, r2, r2, r2, (false, false, true, false));
-
-    // 11. Corner 3 (bottom-left)
-    push_valid_quad(x, y + h - r3, r3, r3, r3, (false, false, false, true));
-
-    quads
+    (
+        get("sans_serif", "Noto Sans"),
+        get("serif", "Noto Serif"),
+        get("monospace", "Noto Sans Mono"),
+        get("terminal", "Noto Sans Mono"),
+    )
 }
 
-pub struct UiFrame;
-
-impl UiFrame {
-    pub fn start(scroll_offset: f32) -> Self {
-        crate::widget::hover_animation::reset_frame_registration();
-        crate::widget::hover_animation::set_scroll_offset(scroll_offset);
-        Self
-    }
-
-    pub fn finish(self, pc: &mut dyn RenderTarget) {
-        crate::widget::hover_animation::post_render_check();
-        if let Some((qx, qy, qw, qh, qc)) = crate::widget::hover_animation::get_quad() {
-            pc.rect(qc, qx, qy, qw, qh);
-        }
-        // render_popovers(pc);
+impl crate::widget::ContainerLayout for FlexLayout {
+    fn box_clone_container(&self) -> Box<dyn crate::widget::ContainerLayout> {
+        Box::new(self.clone())
     }
 }
 
-pub struct Column {
-    ox: f32,
-    oy: f32,
-    cx: f32,
-    pub y: f32,
-    pub cw: f32,
-}
-
-impl Column {
-    pub fn new(ox: f32, oy: f32, cx: f32, cy: f32, cw: f32) -> Self {
-        Self { ox, oy, cx, y: cy, cw }
-    }
-
-    pub fn ax(&self, x_off: f32) -> f32 {
-        self.ox + self.cx + x_off
-    }
-
-    pub fn ay(&self) -> f32 {
-        self.oy + self.y
-    }
-
-    pub fn rect(&mut self, pc: &mut dyn RenderTarget, color: [f32; 4], x_off: f32, w: f32, h: f32) {
-        pc.rect(color, self.ax(x_off), self.ay(), w, h);
-        self.y += h;
-    }
-
-    pub fn advance(&mut self, dy: f32) {
-        self.y += dy;
-    }
-
-    pub fn spacing(&mut self, dy: f32) {
-        self.y += dy;
-    }
-
-    pub fn separator(&mut self, pc: &mut dyn RenderTarget) {
-        let x = self.ax(8.0);
-        let y = self.ay();
-        pc.rect([0.18, 0.18, 0.27, 1.0], x, y, self.cw - 16.0, 1.0);
-        self.y += 8.0;
-    }
-
-    pub fn header(&mut self, pc: &mut dyn RenderTarget, text: &str, x_off: f32) {
-        let x = self.ax(x_off);
-        let y = self.ay();
-        pc.text(text, x, y, 14.0, [0.83, 0.83, 0.83, 1.0]);
-        self.y += 22.0;
-    }
-
-    pub fn text(&mut self, pc: &mut dyn RenderTarget, text: &str, x_off: f32, y_off: f32, font_size: f32, color: [f32; 4]) {
-        let x = self.ax(x_off);
-        let y = self.ay() + y_off;
-        pc.text(text, x, y, font_size, color);
-    }
-
-    pub fn widget<T: WidgetHost + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, x_off: f32, ww: f32, mut wh: f32, ctx: &mut UiContext) {
-        if let Some(pref) = w.preferred_height() {
-            wh = pref;
-        }
-        let top_room = w.label_strip();
-        let total_h = wh + top_room;
-        let x = self.ax(x_off);
-        let y = self.ay();
-        w.set_row_rect(self.ox + self.cx + 8.0, self.cw - 16.0);
-        let clamped_w = ww.min((self.cw - x_off).max(0.0));
-        render_widget(pc, w, x, y, clamped_w, total_h, ctx);
-        self.y += total_h;
-    }
-
-    pub fn row<F: FnOnce(&mut Row)>(&mut self, pc: &mut dyn RenderTarget, h: f32, f: F) {
-        let row_y = self.ay();
-        let mut row = Row {
-            pc: &mut *pc,
-            base_x: self.ox + self.cx,
-            y: row_y,
-            cursor_x: 0.0,
-            spacing: CONTROL_GAP,
-        };
-        f(&mut row);
-        self.y = self.y + h;
+impl crate::widget::ContainerLayout for ColumnLayout {
+    fn box_clone_container(&self) -> Box<dyn crate::widget::ContainerLayout> {
+        Box::new(self.clone())
     }
 }
 
-pub struct Row<'a> {
-    pc: &'a mut dyn RenderTarget,
-    base_x: f32,
-    y: f32,
-    pub cursor_x: f32,
-    pub spacing: f32,
-}
-
-impl<'a> Row<'a> {
-    pub fn set_spacing(&mut self, spacing: f32) {
-        self.spacing = spacing;
-    }
-
-    pub fn gap(&mut self, width: f32) {
-        self.cursor_x += width;
-    }
-
-    pub fn text(&mut self, text: &str, y_off: f32, font_size: f32, color: [f32; 4], width: f32) {
-        self.pc
-            .text(text, self.base_x + self.cursor_x, self.y + y_off, font_size, color);
-        self.cursor_x += width + self.spacing;
-    }
-
-    pub fn widget<T: WidgetHost + 'static>(&mut self, w: &mut T, ww: f32, mut wh: f32, ctx: &mut UiContext) {
-        if let Some(pref) = w.preferred_height() {
-            wh = pref;
-        }
-        render_widget(self.pc, w, self.base_x + self.cursor_x, self.y, ww, wh, ctx);
-        self.cursor_x += ww + self.spacing;
+impl crate::widget::ContainerLayout for AdaptiveGrid {
+    fn box_clone_container(&self) -> Box<dyn crate::widget::ContainerLayout> {
+        Box::new(self.clone())
     }
 }
 
-fn estimate_label_width_helper(label: &str, font_size: f32, font_fam: &str) -> f32 {
-    let fam_lower = font_fam.to_lowercase();
-    let is_mono = fam_lower.contains("mono") || fam_lower.contains("courier") || fam_lower == "monospace";
-    if is_mono {
-        label.chars().count() as f32 * font_size * 0.60
-    } else {
-        let mut width = 0.0;
-        for c in label.chars() {
-            let factor = match c {
-                'i' | 'l' | 'I' | ' ' | '.' | ',' | '!' | ';' | ':' | '\'' | '"' | '(' | ')' | '[' | ']' | '-' => 0.30,
-                'f' | 'j' | 't' => 0.35,
-                'r' | 's' | 'c' | 'z' => 0.50,
-                'a' | 'b' | 'd' | 'e' | 'g' | 'h' | 'k' | 'n' | 'o' | 'p' | 'q' | 'u' | 'v' | 'x' | 'y' => 0.60,
-                'm' | 'w' | 'M' | 'W' | '&' | '@' | 'O' | 'Q' | 'G' => 0.85,
-                'A' | 'B' | 'C' | 'D' | 'H' | 'N' | 'U' | 'V' | 'X' | 'Y' => 0.75,
-                'E' | 'F' | 'K' | 'L' | 'P' | 'R' | 'S' | 'T' | 'Z' | 'J' => 0.68,
-                '0'..='9' => 0.60,
-                _ => 0.60,
-            };
-            width += factor * font_size;
-        }
-        width
+impl crate::widget::ContainerLayout for RadialLayout {
+    fn box_clone_container(&self) -> Box<dyn crate::widget::ContainerLayout> {
+        Box::new(self.clone())
     }
 }
 
-pub struct Section {
-    pub left: f32,
-    pub top: f32,
-    pub content_y: f32,
-    pub cw: f32,
-    pub label_width: f32,
-    pub is_child: bool,
-    pub grid: Grid,
-    pub last_col: usize,
-}
-
-impl Section {
-    pub const DEFAULT_MARGIN_X: f32 = 12.0;
-    pub const DEFAULT_ROW_GAP: f32 = 8.0;
-
-    fn estimate_label_width(label: &str, font_size: f32, font_fam: &str) -> f32 {
-        estimate_label_width_helper(label, font_size, font_fam)
-    }
+#[cfg(test)]
+mod tests {
+    use crate::widget::WidgetHost;
 
-    pub fn padding(&self) -> f32 {
-        section_padding()
-    }
-
-    pub fn new(pc: &mut dyn RenderTarget, left: f32, top: f32, cw: f32, label: &str) -> Self {
-        Self::new_opt(pc, left, top, cw, label, false)
-    }
-
-    pub fn new_opt(pc: &mut dyn RenderTarget, left: f32, top: f32, cw: f32, label: &str, is_child: bool) -> Self {
-        let font_setting = if is_child {
-            nested_section_label_font()
-        } else {
-            section_label_font()
-        };
-        let (font_fam, font_size_opt) = parse_font_string(&font_setting);
-        let font_size = font_size_opt.unwrap_or(if is_child { 12.0 } else { 14.0 });
-        let font_color = if is_child { [0.53, 0.53, 0.60, 1.0] } else { [0.83, 0.83, 0.83, 1.0] };
-        let label_width = Self::estimate_label_width(label, font_size, &font_fam);
-        let label_x = if is_child {
-            let base_x = match nested_section_label_alignment() {
-                0 => left + 12.0,
-                1 => left + (cw - label_width) / 2.0,
-                2 => left + cw - 12.0 - label_width,
-                _ => left + 12.0,
-            };
-            base_x + nested_section_label_offset()
-        } else {
-            left + (cw - label_width) / 2.0
-        };
-        pc.text_with_font(label, label_x, top, font_size, font_color, &font_fam);
-
-        let pad = section_padding();
-        let margin_x = 2.0 * pad + 12.0;
-        let usable_w = (cw - 2.0 * margin_x).max(1.0);
-        let min_col_width = 130.0;
-        let gap = 8.0;
-        let max_cols = if is_child {
-            1
-        } else {
-            ((usable_w + gap) / (min_col_width + gap)).floor().max(1.0).min(2.0) as usize
-        };
-        let content_start_y = top + pad + 19.0;
-        let grid = Grid::new(left + margin_x, content_start_y, usable_w, min_col_width, gap, max_cols);
-
-        Self { left, top, content_y: content_start_y, cw, label_width, is_child, grid, last_col: usize::MAX }
-    }
-
-    /// Horizontal inset of content from this section's left edge — the same
-    /// one `row_layout`, the column `Grid` and `widget` use, so everything in
-    /// a section lines up. See `SectionContext::content_margin`.
-    pub fn content_margin(&self) -> f32 {
-        2.0 * self.padding() + 12.0
-    }
-
-    pub fn content_left(&self) -> f32 {
-        self.left + self.content_margin()
-    }
-
-    pub fn content_width(&self) -> f32 {
-        (self.cw - 2.0 * self.content_margin()).max(0.0)
-    }
-
-    /// See `SectionContext::ax` — same mapping, same reason it is no longer
-    /// stepped at `x_off == 12.0`.
-    pub fn ax(&self, x_off: f32) -> f32 {
-        self.left + 2.0 * self.padding() + x_off
-    }
-
-    pub fn ay(&self) -> f32 { self.content_y }
-
-    pub fn spacing(&mut self, dy: f32) {
-        if self.grid.col_heights.len() >= 2 {
-            if self.last_col == usize::MAX {
-                for h in &mut self.grid.col_heights {
-                    *h += dy;
-                }
-            } else if self.last_col < self.grid.col_heights.len() {
-                self.grid.col_heights[self.last_col] += dy;
-            }
-            self.content_y = self.grid.max_height();
-        } else {
-            self.content_y += dy;
-            for h in &mut self.grid.col_heights {
-                *h += dy;
-            }
-        }
-    }
-
-    pub fn text(&mut self, pc: &mut dyn RenderTarget, text: &str, x_off: f32, y_off: f32, font_size: f32, color: [f32; 4]) {
-        let x = self.ax(x_off);
-        let y = self.ay() + y_off;
-        // Bounded to the content box, like `SectionContext::text` — text is
-        // the only thing a section draws that is not already sized to fit it.
-        let right = self.content_left() + self.content_width();
-        let bounds = if right > x {
-            Some([x, y - font_size, right, y + 2.0 * font_size])
-        } else {
-            None
-        };
-        pc.text_with_bounds(text, x, y, font_size, color, bounds);
-    }
-
-    pub fn widget<T: WidgetHost + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, _x_off: f32, _ww: f32, mut wh: f32, ctx: &mut UiContext) {
-        if let Some(pref) = w.preferred_height() {
-            wh = pref;
-        }
-        let pad = self.padding();
-        let top_room = w.label_strip();
-        let total_h = wh + top_room;
-
-        let name = w.type_name();
-        let span_full = name == "Trackpad"
-            || name == "Canvas"
-            || name == "UsageBar"
-            || name == "ProgressBar"
-            || name == "ButtonStrip"
-            || name == "Spreadsheet"
-            || name == "Graph";
-
-        if span_full {
-            let margin_x = 2.0 * pad + 12.0;
-            let x = self.left + margin_x;
-            let clamped_w = (self.cw - 2.0 * margin_x).max(0.0);
-            let max_h = self.grid.max_height().max(self.content_y);
-            let y = max_h;
-
-            w.set_row_rect(self.left + pad, self.cw - 2.0 * pad);
-            render_widget(pc, w, x, y, clamped_w, total_h, ctx);
-
-            let new_bottom = y + total_h;
-            self.content_y = new_bottom;
-            for h in &mut self.grid.col_heights {
-                *h = new_bottom;
-            }
-        } else {
-            let max_h = self.grid.max_height();
-            if self.content_y > max_h {
-                for h in &mut self.grid.col_heights {
-                    *h = self.content_y;
-                }
-            }
-
-            let col = self.grid.next_column();
-            self.last_col = col;
-            let x = self.grid.col_lefts[col];
-            let y = self.grid.col_heights[col];
-
-            w.set_row_rect(x, self.grid.col_width);
-            render_widget(pc, w, x, y, self.grid.col_width, total_h, ctx);
-            self.grid.col_heights[col] += total_h;
-            self.content_y = self.grid.max_height();
-        }
-    }
-
-    pub fn widget_full<T: WidgetHost + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, wh: f32, ctx: &mut UiContext) {
-        let x_off = 12.0;
-        let ww = self.cw - 2.0 * (self.padding() + x_off);
-        self.widget(pc, w, x_off, ww, wh, ctx);
-    }
-
-    pub fn separator(&mut self, pc: &mut dyn RenderTarget) {
-        let pad = self.padding();
-        let x = self.ax(pad);
-        let max_h = self.grid.max_height().max(self.content_y);
-        let y = max_h;
-        pc.rect([0.18, 0.18, 0.27, 1.0], x, y, self.cw - 2.0 * pad, 1.0);
-        self.content_y = max_h + 8.0;
-        for h in &mut self.grid.col_heights {
-            *h = self.content_y;
-        }
-    }
-
-    pub fn rect(&mut self, pc: &mut dyn RenderTarget, color: [f32; 4], x_off: f32, w: f32, h: f32) {
-        let max_h = self.grid.max_height().max(self.content_y);
-        pc.rect(color, self.ax(x_off), max_h, w, h);
-        self.content_y = max_h + h;
-        for col_h in &mut self.grid.col_heights {
-            *col_h = self.content_y;
-        }
-    }
-
-    pub fn row_layout(&self, count: usize, gap: f32) -> Vec<(f32, f32)> {
-        let margin_x = self.content_margin();
-        let usable_w = self.content_width();
-        if count == 0 {
-            return Vec::new();
-        }
-        let total_gap = gap * (count - 1) as f32;
-        let col_w = (usable_w - total_gap).max(0.0) / count as f32;
-
-        let mut cols = Vec::with_capacity(count);
-        for i in 0..count {
-            let x = self.left + margin_x + i as f32 * (col_w + gap);
-            cols.push((x, col_w));
-        }
-        cols
-    }
-
-    pub fn row<F>(&mut self, count: usize, gap: f32, h: f32, mut f: F)
-    where
-        F: FnMut(usize, f32, f32),
-    {
-        let max_h = self.grid.max_height().max(self.content_y);
-        for col_h in &mut self.grid.col_heights {
-            *col_h = max_h;
-        }
-        self.content_y = max_h;
-
-        let cols = self.row_layout(count, gap);
-        for (i, &(x, w)) in cols.iter().enumerate() {
-            f(i, x, w);
-        }
-        self.content_y += h;
-
-        for col_h in &mut self.grid.col_heights {
-            *col_h = self.content_y;
-        }
-    }
-
-    pub fn finish(&mut self, pc: &mut dyn RenderTarget) -> f32 {
-        self.finish_focused(pc, false)
-    }
-
-    pub fn finish_focused(&mut self, pc: &mut dyn RenderTarget, focused: bool) -> f32 {
-        let border: [f32; 4] = if self.is_child {
-            if focused {
-                [0.22, 0.38, 0.24, 1.0]
-            } else {
-                [0.18, 0.18, 0.25, 1.0]
-            }
-        } else {
-            if focused {
-                [0.30, 0.50, 0.32, 1.0]
-            } else {
-                [0.25, 0.25, 0.35, 1.0]
-            }
-        };
-        let pad = self.padding();
-        let x = self.left + pad;
-        let y = self.top + 7.0;
-        let w = self.cw - 2.0 * pad;
-        let h = self.content_y - y;
-        
-        let left_edge = x;
-        let right_edge = x + w;
-        if self.label_width > 0.0 {
-            let label_x = if self.is_child {
-                let base_x = match nested_section_label_alignment() {
-                    0 => self.left + 12.0,
-                    1 => self.left + (self.cw - self.label_width) / 2.0,
-                    2 => self.left + self.cw - 12.0 - self.label_width,
-                    _ => self.left + 12.0,
-                };
-                base_x + nested_section_label_offset()
-            } else {
-                self.left + (self.cw - self.label_width) / 2.0
-            };
-            let gap_margin = 6.0;
-            let gap_start = label_x - gap_margin;
-            let gap_end = label_x + self.label_width + gap_margin;
-            if gap_start > left_edge {
-                pc.rect(border, left_edge, y, gap_start - left_edge, 1.0);
-            }
-            if right_edge > gap_end {
-                pc.rect(border, gap_end, y, right_edge - gap_end, 1.0);
-            }
-        } else {
-            pc.rect(border, left_edge, y, w, 1.0);
-        }
-
-        let extra_bottom = pad + 12.0;
-        pc.rect(border, x, y + h + extra_bottom, w, 1.0);
-        pc.rect(border, x, y, 1.0, h + extra_bottom);
-        pc.rect(border, x + w - 1.0, y, 1.0, h + extra_bottom);
-        self.content_y + extra_bottom + 8.0
-    }
-
-    pub fn vstack<'a>(&'a mut self, pc: &'a mut dyn RenderTarget, spacing: f32) -> SectionVStack<'a> {
-        SectionVStack {
-            section: self,
-            pc,
-            spacing,
-        }
-    }
-}
-
-pub struct SectionVStack<'a> {
-    section: &'a mut Section,
-    pc: &'a mut dyn RenderTarget,
-    spacing: f32,
-}
-
-impl<'a> SectionVStack<'a> {
-    pub fn add_widget<T: WidgetHost + 'static>(&mut self, w: &mut T, ww: f32, wh: f32, ctx: &mut UiContext) {
-        self.section.widget(self.pc, w, Section::DEFAULT_MARGIN_X, ww, wh, ctx);
-        self.section.spacing(self.spacing);
-    }
-
-    pub fn add_row<F>(&mut self, count: usize, gap: f32, h: f32, f: F)
-    where
-        F: FnMut(usize, f32, f32),
-    {
-        self.section.row(count, gap, h, f);
-        self.section.spacing(self.spacing);
-    }
-}
-
-
-pub struct SplitterLayout {
-    pub splitter1_x: f32,
-    pub splitter2_x: f32,
-    pub splitter_width: f32,
-    pub min_column_width: f32,
-}
-
-impl SplitterLayout {
-    pub fn new(width: f32, splitter_width: f32, min_column_width: f32) -> Self {
-        let s1 = (width - 2.0 * splitter_width) / 3.0;
-        let s2 = s1 + splitter_width + (width - 2.0 * splitter_width) / 3.0;
-        Self {
-            splitter1_x: s1,
-            splitter2_x: s2,
-            splitter_width,
-            min_column_width,
-        }
-    }
-
-    pub fn clamp(&mut self, total_width: f32, detached_circular_network: bool) {
-        if detached_circular_network {
-            let min_s2 = self.min_column_width;
-            let max_s2 = (total_width - self.min_column_width).max(min_s2);
-            self.splitter2_x = self.splitter2_x.clamp(min_s2, max_s2);
-        } else {
-            let min_s1 = self.min_column_width;
-            let max_s1 = (self.splitter2_x - self.splitter_width - self.min_column_width).max(min_s1);
-            self.splitter1_x = self.splitter1_x.clamp(min_s1, max_s1);
-            let min_s2 = self.splitter1_x + self.splitter_width + self.min_column_width;
-            let max_s2 = (total_width - self.min_column_width).max(min_s2);
-            self.splitter2_x = self.splitter2_x.clamp(min_s2, max_s2);
-        }
-    }
-
-    pub fn scale(&mut self, factor: f32) {
-        self.splitter1_x *= factor;
-        self.splitter2_x *= factor;
-    }
-
-    pub fn left_col(&self) -> (f32, f32) { // (x, width)
-        (0.0, self.splitter1_x)
-    }
-
-    pub fn center_col(&self) -> (f32, f32) { // (x, width)
-        let x = self.splitter1_x + self.splitter_width;
-        (x, self.splitter2_x - x)
-    }
-
-    pub fn right_col(&self, total_width: f32) -> (f32, f32) { // (x, width)
-        let x = self.splitter2_x + self.splitter_width;
-        (x, (total_width - x).max(0.0))
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Grid {
-    pub left: f32,
-    pub top: f32,
-    pub width: f32,
-    pub col_width: f32,
-    pub gap: f32,
-    pub col_heights: Vec<f32>,
-    pub col_lefts: Vec<f32>,
-}
-
-impl Grid {
-    pub fn new(left: f32, top: f32, width: f32, min_col_width: f32, gap: f32, count: usize) -> Self {
-        let total_gap = gap * (count - 1) as f32;
-        let col_width = if count > 0 {
-            (width - total_gap).max(0.0) / count as f32
-        } else {
-            min_col_width
-        };
-        let left_offset = 0.0;
-
-        let mut col_lefts = Vec::with_capacity(count);
-        let col_heights = vec![top; count];
-        for i in 0..count {
-            col_lefts.push(left + left_offset + i as f32 * (col_width + gap));
-        }
-
-        Self {
-            left,
-            top,
-            width,
-            col_width,
-            gap,
-            col_heights,
-            col_lefts,
-        }
-    }
-
-    pub fn next_column(&self) -> usize {
-        let mut min_idx = 0;
-        let mut min_h = self.col_heights[0];
-        for i in 1..self.col_heights.len() {
-            if self.col_heights[i] < min_h {
-                min_h = self.col_heights[i];
-                min_idx = i;
-            }
-        }
-        min_idx
-    }
-
-    pub fn max_height(&self) -> f32 {
-        let mut max_h = self.col_heights[0];
-        for i in 1..self.col_heights.len() {
-            if self.col_heights[i] > max_h {
-                max_h = self.col_heights[i];
-            }
-        }
-        max_h
-    }
-}
-
-pub struct CircularPaneLayout {
-    pub x: f32,
-    pub y: f32,
-    pub r: f32,
-}
-
-impl CircularPaneLayout {
-    pub fn new(x: f32, y: f32, r: f32) -> Self {
-        Self { x, y, r }
-    }
-
-    pub fn hit_test_content(&self, cx: f32, cy: f32, menubar_h: f32, breadcrumb_h: f32) -> bool {
-        let dx = cx - self.x;
-        let dy = cy - self.y;
-        let dist_sq = dx * dx + dy * dy;
-        dist_sq <= self.r * self.r && cy >= self.y - self.r + 45.0 + menubar_h + breadcrumb_h
-    }
-
-    pub fn hit_test_menubar(&self, cx: f32, cy: f32, menubar_h: f32) -> bool {
-        let dx = cx - self.x;
-        let dy = cy - self.y;
-        let dist = (dx * dx + dy * dy).sqrt();
-        dist >= self.r - menubar_h && dist <= self.r && cy < self.y
-    }
-
-    pub fn hit_test_breadcrumb(&self, cx: f32, cy: f32, menubar_h: f32, breadcrumb_h: f32) -> bool {
-        let dx = cx - self.x;
-        let dy = cy - self.y;
-        let dist_sq = dx * dx + dy * dy;
-        dist_sq <= self.r * self.r && cy >= self.y - self.r + menubar_h && cy < self.y - self.r + 45.0 + menubar_h + breadcrumb_h
-    }
-
-    pub fn hit_test_border(&self, cx: f32, cy: f32, border_thickness: f32) -> bool {
-        let dx = cx - self.x;
-        let dy = cy - self.y;
-        let dist = (dx * dx + dy * dy).sqrt();
-        dist >= self.r - border_thickness && dist <= self.r
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Radial {
-    pub center_x: f32,
-    pub center_y: f32,
-    pub aspect_ratio: f32,
-    pub base_spacing: f32,
-}
-
-impl Radial {
-    pub fn new(center_x: f32, center_y: f32, aspect_ratio: f32, base_spacing: f32) -> Self {
-        Self { center_x, center_y, aspect_ratio, base_spacing }
-    }
-
-    pub fn widget_rect(&self, idx: usize, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
-        if idx == 0 {
-            (self.center_x - ww / 2.0, self.center_y - wh / 2.0, ww, wh)
-        } else {
-            let mut ring = 1;
-            let mut ring_start = 1;
-            loop {
-                let ring_capacity = ring * 6;
-                if idx < ring_start + ring_capacity {
-                    let pos_in_ring = idx - ring_start;
-                    let angle = (pos_in_ring as f32) * (2.0 * std::f32::consts::PI / ring_capacity as f32);
-                    let radius = (ring as f32) * self.base_spacing;
-
-                    let x_offset = radius * angle.cos() * self.aspect_ratio;
-                    let y_offset = radius * angle.sin();
-
-                    return (
-                        self.center_x + x_offset - ww / 2.0,
-                        self.center_y + y_offset - wh / 2.0,
-                        ww,
-                        wh,
-                    );
-                }
-                ring_start += ring_capacity;
-                ring += 1;
-            }
-        }
-    }
-
-}
-
-
-
-pub trait LayoutStrategy: std::fmt::Debug {
-    fn init(&mut self, left: f32, top: f32, width: f32, height: f32);
-    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32);
-    fn set_section_count(&mut self, _count: usize) {}
-    fn get_column_width(&self) -> Option<f32> { None }
-    fn get_gap(&self) -> f32 { 20.0 }
-
-    fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &mut crate::context::UiContext) -> f32;
-    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size;
-    fn box_clone(&self) -> Box<dyn LayoutStrategy>;
-}
-
-impl Clone for Box<dyn LayoutStrategy> {
-    fn clone(&self) -> Self {
-        self.box_clone()
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum FlexDirection {
-    Row,
-    Column,
-}
-
-#[derive(Debug, Clone)]
-pub struct FlexLayout {
-    left: f32,
-    top: f32,
-    width: f32,
-    height: f32,
-    direction: FlexDirection,
-    spacing: f32,
-    current_x: f32,
-    current_y: f32,
-}
-
-impl FlexLayout {
-    pub fn new(direction: FlexDirection, spacing: f32) -> Self {
-        Self {
-            left: 0.0,
-            top: 0.0,
-            width: 0.0,
-            height: 0.0,
-            direction,
-            spacing,
-            current_x: 0.0,
-            current_y: 0.0,
-        }
-    }
-}
-
-impl LayoutStrategy for FlexLayout {
-    fn init(&mut self, left: f32, top: f32, width: f32, height: f32) {
-        self.left = left;
-        self.top = top;
-        self.width = width;
-        self.height = height;
-        self.current_x = left;
-        self.current_y = top;
-    }
-
-    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
-        match self.direction {
-            FlexDirection::Row => {
-                let rx = self.current_x;
-                let ry = self.current_y;
-                self.current_x += ww + self.spacing;
-                (rx, ry, ww, wh)
-            }
-            FlexDirection::Column => {
-                let rx = self.current_x;
-                let ry = self.current_y;
-                self.current_y += wh + self.spacing;
-                (rx, ry, ww, wh)
-            }
-        }
-    }
-
-    fn get_gap(&self) -> f32 {
-        self.spacing
-    }
-
-    fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &mut crate::context::UiContext) -> f32 {
-        let (cur_x, cur_y) = (x, y);
-        // Blocks: the label row (`label_lead`) above every child's content, the
-        // gap between blocks, so a row's controls are level and a carve-out tab
-        // sits a full gap from its neighbour.
-        let lead = crate::widget::container::container_layout::label_lead(children);
-        match self.direction {
-            FlexDirection::Row => {
-                let mut cur_x = cur_x;
-                for &child_ptr in children {
-                    unsafe {
-                        let child = &mut *child_ptr;
-                        let child_w = child.rect().2;
-                        let child_h = crate::widget::container::container_layout::content_height(child);
-                        let use_h = if child_h > 0.0 { child_h } else { h };
-                        child.layout(
-                            crate::widget::Point { x: cur_x, y: cur_y + lead },
-                            crate::widget::LayoutConstraints::new(child_w, child_w, use_h, use_h),
-                            ctx,
-                        );
-                        cur_x += child_w + self.spacing;
-                    }
-                }
-                (cur_x - x).max(0.0)
-            }
-            FlexDirection::Column => {
-                let mut cur_y = cur_y;
-                for &child_ptr in children {
-                    unsafe {
-                        let child = &mut *child_ptr;
-                        let child_h = crate::widget::container::container_layout::content_height(child);
-                        let use_h = if child_h > 0.0 { child_h } else { 44.0 };
-                        child.layout(
-                            crate::widget::Point { x, y: cur_y + lead },
-                            crate::widget::LayoutConstraints::new(w, w, use_h, use_h),
-                            ctx,
-                        );
-                        cur_y += lead + use_h + self.spacing;
-                    }
-                }
-                (cur_y - y).max(0.0)
-            }
-        }
-    }
-
-    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size {
-        match self.direction {
-            FlexDirection::Row => {
-                let mut total_w = 0.0f32;
-                let mut max_h = 0.0f32;
-                for (i, &child_ptr) in children.iter().enumerate() {
-                    unsafe {
-                        let size = (*child_ptr).measure(constraints, ctx);
-                        total_w += size.width;
-                        max_h = max_h.max(size.height);
-                        if i > 0 {
-                            total_w += self.spacing;
-                        }
-                    }
-                }
-                crate::widget::Size {
-                    width: total_w.clamp(constraints.min_width, constraints.max_width),
-                    height: max_h.clamp(constraints.min_height, constraints.max_height),
-                }
-            }
-            FlexDirection::Column => {
-                let mut total_h = 0.0f32;
-                let mut max_w = 0.0f32;
-                for (i, &child_ptr) in children.iter().enumerate() {
-                    unsafe {
-                        let size = (*child_ptr).measure(constraints, ctx);
-                        total_h += size.height;
-                        max_w = max_w.max(size.width);
-                        if i > 0 {
-                            total_h += self.spacing;
-                        }
-                    }
-                }
-                crate::widget::Size {
-                    width: max_w.clamp(constraints.min_width, constraints.max_width),
-                    height: total_h.clamp(constraints.min_height, constraints.max_height),
-                }
-            }
-        }
-    }
-
-    fn box_clone(&self) -> Box<dyn LayoutStrategy> {
-        Box::new(self.clone())
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct ColumnLayout {
-    left: f32,
-    top: f32,
-    width: f32,
-    current_y: f32,
-    gap: f32,
-}
-
-impl ColumnLayout {
-    pub fn new(gap: f32) -> Self {
-        Self {
-            left: 0.0,
-            top: 0.0,
-            width: 0.0,
-            current_y: 0.0,
-            gap,
-        }
-    }
-}
-
-impl LayoutStrategy for ColumnLayout {
-    fn init(&mut self, left: f32, top: f32, width: f32, _height: f32) {
-        self.left = left;
-        self.top = top;
-        self.width = width;
-        self.current_y = top;
-    }
-
-    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
-        let x = self.left;
-        let y = self.current_y;
-        self.current_y += wh + self.gap;
-        (x, y, ww, wh)
-    }
-
-    fn get_column_width(&self) -> Option<f32> {
-        Some(self.width)
-    }
-
-    fn get_gap(&self) -> f32 {
-        self.gap
-    }
-
-    fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], _ctx: &mut crate::context::UiContext) -> f32 {
-        let mut cur_y = y;
-        for &child_ptr in children {
-            unsafe {
-                let child = &mut *child_ptr;
-                let child_h = child.preferred_height().unwrap_or(child.rect().3);
-                let use_h = if child_h > 0.0 { child_h } else { 44.0 };
-                child.set_rect(x, cur_y, w, use_h);
-                cur_y += use_h + self.gap;
-            }
-        }
-        (cur_y - y).max(0.0)
-    }
-
-    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size {
-        let mut total_h = 0.0f32;
-        let mut max_w = 0.0f32;
-        for (i, &child_ptr) in children.iter().enumerate() {
-            unsafe {
-                let size = (*child_ptr).measure(constraints, ctx);
-                total_h += size.height;
-                max_w = max_w.max(size.width);
-                if i > 0 {
-                    total_h += self.gap;
-                }
-            }
-        }
-        crate::widget::Size {
-            width: max_w.clamp(constraints.min_width, constraints.max_width),
-            height: total_h.clamp(constraints.min_height, constraints.max_height),
-        }
-    }
-
-    fn box_clone(&self) -> Box<dyn LayoutStrategy> {
-        Box::new(self.clone())
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct AdaptiveGrid {
-    grid: Option<Grid>,
-    #[allow(dead_code)]
-    min_col_width: f32,
-    #[allow(dead_code)]
-    gap: f32,
-    num_sections: Option<usize>,
-}
-
-impl AdaptiveGrid {
-    pub fn new(min_col_width: f32, gap: f32) -> Self {
-        Self {
-            grid: None,
-            min_col_width,
-            gap,
-            num_sections: None,
-        }
-    }
-}
-
-impl LayoutStrategy for AdaptiveGrid {
-    fn init(&mut self, left: f32, top: f32, width: f32, _height: f32) {
-        let min_col_width = crate::layout::grid_min_col_width();
-        let gap = crate::layout::grid_gap();
-        let max_cols = ((width + gap) / (min_col_width + gap)).floor().max(1.0) as usize;
-        let count = if let Some(n) = self.num_sections {
-            n.min(max_cols).max(1)
-        } else {
-            max_cols
-        };
-        self.grid = Some(Grid::new(left, top, width, min_col_width, gap, count));
-    }
-
-    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
-        if let Some(ref mut grid) = self.grid {
-            let num_cols = grid.col_heights.len();
-            let num_cols_spanned = (((ww + grid.gap) / (grid.col_width + grid.gap)).round() as usize)
-                .min(num_cols)
-                .max(1);
-
-            if num_cols_spanned >= num_cols {
-                let y = grid.max_height();
-                let x = grid.left;
-                let allocated_w = grid.width;
-                for col_h in &mut grid.col_heights {
-                    *col_h = y + wh + grid.gap;
-                }
-                (x, y, allocated_w, wh)
-            } else if num_cols_spanned == 1 {
-                let col = grid.next_column();
-                let x = grid.col_lefts[col];
-                let y = grid.col_heights[col];
-                grid.col_heights[col] += wh + grid.gap;
-                (x, y, grid.col_width, wh)
-            } else {
-                let n = num_cols_spanned;
-                let mut best_start_col = 0;
-                let mut min_max_h = f32::MAX;
-                for c in 0..=(num_cols - n) {
-                    let mut max_h = 0.0f32;
-                    for i in 0..n {
-                        if grid.col_heights[c + i] > max_h {
-                            max_h = grid.col_heights[c + i];
-                        }
-                    }
-                    if max_h < min_max_h {
-                        min_max_h = max_h;
-                        best_start_col = c;
-                    }
-                }
-                let x = grid.col_lefts[best_start_col];
-                let y = min_max_h;
-                let allocated_w = n as f32 * grid.col_width + (n - 1) as f32 * grid.gap;
-                for i in 0..n {
-                    grid.col_heights[best_start_col + i] = y + wh + grid.gap;
-                }
-                (x, y, allocated_w, wh)
-            }
-        } else {
-            (0.0, 0.0, 0.0, wh)
-        }
-    }
-
-    fn set_section_count(&mut self, count: usize) {
-        self.num_sections = Some(count);
-    }
-
-    fn get_column_width(&self) -> Option<f32> {
-        self.grid.as_ref().map(|g| g.col_width)
-    }
-
-    fn get_gap(&self) -> f32 {
-        crate::layout::grid_gap()
-    }
-
-    fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], _ctx: &mut crate::context::UiContext) -> f32 {
-        let usable_w = w.max(1.0);
-        let min_col_width = crate::layout::grid_min_col_width();
-        let gap = crate::layout::grid_gap();
-        let cols = (((usable_w + gap) / (min_col_width + gap)).floor().max(1.0)) as usize;
-        let count = if let Some(n) = self.num_sections {
-            n.min(cols).max(1)
-        } else {
-            cols
-        };
-
-        let total_gap = gap * (count - 1) as f32;
-        let available_w = (w - total_gap).max(1.0);
-        let col_w = available_w / count as f32;
-        
-        let mut col_heights = vec![y; count];
-
-        for &child_ptr in children {
-            unsafe {
-                let child = &mut *child_ptr;
-                let ch = child.preferred_height().unwrap_or(child.rect().3);
-                let use_h = if ch > 0.0 { ch } else { 44.0 };
-                
-                let mut min_col = 0;
-                let mut min_h = col_heights[0];
-                for i in 1..count {
-                    if col_heights[i] < min_h {
-                        min_h = col_heights[i];
-                        min_col = i;
-                    }
-                }
-                
-                let cx = x + min_col as f32 * (col_w + gap);
-                let cy = col_heights[min_col];
-                child.set_rect(cx, cy, col_w, use_h);
-                col_heights[min_col] += use_h + gap;
-            }
-        }
-        
-        let max_h = col_heights.iter().cloned().fold(0.0f32, |a, b| a.max(b));
-        (max_h - y).max(0.0)
-    }
-
-    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size {
-        let usable_w = constraints.max_width.max(1.0);
-        let min_col_width = crate::layout::grid_min_col_width();
-        let gap = crate::layout::grid_gap();
-        let cols = (((usable_w + gap) / (min_col_width + gap)).floor().max(1.0)) as usize;
-        let count = if let Some(n) = self.num_sections {
-            n.min(cols).max(1)
-        } else {
-            cols
-        };
-
-        let mut col_heights = vec![0.0f32; count];
-        let total_gap = gap * (count - 1) as f32;
-        let available_w = (constraints.max_width - total_gap).max(1.0);
-        let col_w = available_w / count as f32;
-        
-        let child_constraints = crate::widget::LayoutConstraints::new(col_w, col_w, constraints.min_height, constraints.max_height);
-
-        for &child_ptr in children {
-            unsafe {
-                let size = (*child_ptr).measure(child_constraints, ctx);
-                let mut min_col = 0;
-                let mut min_h = col_heights[0];
-                for i in 1..count {
-                    if col_heights[i] < min_h {
-                        min_h = col_heights[i];
-                        min_col = i;
-                    }
-                }
-                col_heights[min_col] += size.height + gap;
-            }
-        }
-        
-        let max_h = col_heights.iter().cloned().fold(0.0f32, |a, b| a.max(b));
-        crate::widget::Size {
-            width: constraints.max_width,
-            height: max_h.clamp(constraints.min_height, constraints.max_height),
-        }
-    }
-
-    fn box_clone(&self) -> Box<dyn LayoutStrategy> {
-        Box::new(self.clone())
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct RadialLayout {
-    radial: Option<Radial>,
-    aspect_ratio: f32,
-    base_spacing: f32,
-    idx: usize,
-}
-
-impl RadialLayout {
-    pub fn new(aspect_ratio: f32, base_spacing: f32) -> Self {
-        Self {
-            radial: None,
-            aspect_ratio,
-            base_spacing,
-            idx: 0,
-        }
-    }
-}
-
-impl LayoutStrategy for RadialLayout {
-    fn init(&mut self, left: f32, top: f32, width: f32, height: f32) {
-        let cx = left + width / 2.0;
-        let cy = top + height / 2.0;
-        let aspect = if self.aspect_ratio > 0.0 {
-            self.aspect_ratio
-        } else {
-            let screen_aspect = (width / height.max(1.0)).max(0.1);
-            1.0 + (screen_aspect - 1.0) * 0.4
-        };
-        self.radial = Some(Radial::new(cx, cy, aspect, self.base_spacing));
-        self.idx = 0;
-    }
-
-    fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
-        if let Some(ref radial) = self.radial {
-            let rect = radial.widget_rect(self.idx, ww, wh);
-            self.idx += 1;
-            rect
-        } else {
-            (0.0, 0.0, ww, wh)
-        }
-    }
-
-    fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], _ctx: &mut crate::context::UiContext) -> f32 {
-        let cx = x + w / 2.0;
-        let cy = y + h / 2.0;
-        let aspect = if self.aspect_ratio > 0.0 {
-            self.aspect_ratio
-        } else {
-            let screen_aspect = (w / h.max(1.0)).max(0.1);
-            1.0 + (screen_aspect - 1.0) * 0.4
-        };
-        let radial = Radial::new(cx, cy, aspect, self.base_spacing);
-        for (idx, &child_ptr) in children.iter().enumerate() {
-            unsafe {
-                let child = &mut *child_ptr;
-                let cw = child.rect().2;
-                let ch = child.preferred_height().unwrap_or(child.rect().3);
-                let use_h = if ch > 0.0 { ch } else { 44.0 };
-                let (rx, ry, rw, rh) = radial.widget_rect(idx, cw, use_h);
-                child.set_rect(rx, ry, rw, rh);
-            }
-        }
-        h
-    }
-
-    fn measure(&self, constraints: crate::widget::LayoutConstraints, children: &[*mut (dyn crate::widget::WidgetHost + 'static)], ctx: &crate::context::UiContext) -> crate::widget::Size {
-        let cx = constraints.max_width / 2.0;
-        let cy = constraints.max_height / 2.0;
-        let aspect = if self.aspect_ratio > 0.0 {
-            self.aspect_ratio
-        } else {
-            let screen_aspect = (constraints.max_width / constraints.max_height.max(1.0)).max(0.1);
-            1.0 + (screen_aspect - 1.0) * 0.4
-        };
-        let radial = Radial::new(cx, cy, aspect, self.base_spacing);
-        let mut max_w = 0.0f32;
-        let mut max_h = 0.0f32;
-        for (idx, &child_ptr) in children.iter().enumerate() {
-            unsafe {
-                let size = (*child_ptr).measure(constraints, ctx);
-                let (rx, ry, rw, rh) = radial.widget_rect(idx, size.width, size.height);
-                max_w = max_w.max(rx + rw);
-                max_h = max_h.max(ry + rh);
-            }
-        }
-        crate::widget::Size {
-            width: max_w.clamp(constraints.min_width, constraints.max_width),
-            height: max_h.clamp(constraints.min_height, constraints.max_height),
-        }
-    }
-
-    fn box_clone(&self) -> Box<dyn LayoutStrategy> {
-        Box::new(self.clone())
-    }
-}
-
-/// Vertical slack for a section's content clip. The clip exists to stop
-/// content escaping its section SIDEWAYS, which is the axis a section's width
-/// actually fixes; a section's height is only known once its content has been
-/// placed, so bounding that axis too would risk cutting content off rather
-/// than keeping it in. Deliberately far larger than any section.
-const SECTION_CLIP_SLACK: f32 = 100_000.0;
-
-pub struct PageLayoutBuilder<'a, P> {
-    pub strategy: &'a mut dyn LayoutStrategy,
-    pub cx: f32,
-    pub cy: f32,
-    pub cw: f32,
-    pub ch: f32,
-    pub section_width: f32,
-    pub idx: usize,
-    _phantom: std::marker::PhantomData<P>,
-}
-
-impl<'a, P: RenderTarget + Default> PageLayoutBuilder<'a, P> {
-    pub fn new(
-        strategy: &'a mut dyn LayoutStrategy,
-        cx: f32,
-        cy: f32,
-        cw: f32,
-        ch: f32,
-        section_width: f32,
-    ) -> Self {
-        strategy.init(cx, cy, cw, ch);
-        Self {
-            strategy,
-            cx,
-            cy,
-            cw,
-            ch,
-            section_width,
-            idx: 0,
-            _phantom: std::marker::PhantomData,
-        }
-    }
-
-    pub fn with_section_count(self, count: usize) -> Self {
-        self.strategy.set_section_count(count);
-        self.strategy.init(self.cx, self.cy, self.cw, self.ch);
-        self
-    }
-
-    pub fn add_section<F>(&mut self, final_pc: &mut P, label: &str, focused: bool, mut render_fn: F)
-    where
-        F: FnMut(&mut SectionContext<'_, P>),
-    {
-        let width = self.strategy.get_column_width().unwrap_or(self.section_width);
-        let mut dummy = P::default();
-        let mut dummy_ctx = SectionContext::new(&mut dummy, 0.0, 0.0, width, label, focused, false);
-        render_fn(&mut dummy_ctx);
-        let wh = dummy_ctx.finish();
-        let (rx, ry, rw, _) = self.strategy.allocate(width, wh);
-        let mut real_ctx = SectionContext::new(final_pc, rx, ry, rw, label, focused, false);
-        let (clip_x, clip_w) = (real_ctx.content_left(), real_ctx.content_width());
-        real_ctx.pc.push_clip_rect(clip_x, ry - SECTION_CLIP_SLACK, clip_w, 2.0 * SECTION_CLIP_SLACK);
-        render_fn(&mut real_ctx);
-        real_ctx.pc.pop_clip_rect();
-        real_ctx.finish();
-        self.idx += 1;
-    }
-
-    pub fn add_section_with_width<F>(&mut self, final_pc: &mut P, width: f32, label: &str, focused: bool, mut render_fn: F)
-    where
-        F: FnMut(&mut SectionContext<'_, P>),
-    {
-        let mut dummy = P::default();
-        let mut dummy_ctx = SectionContext::new(&mut dummy, 0.0, 0.0, width, label, focused, false);
-        render_fn(&mut dummy_ctx);
-        let wh = dummy_ctx.finish();
-        let (rx, ry, rw, _) = self.strategy.allocate(width, wh);
-        let mut real_ctx = SectionContext::new(final_pc, rx, ry, rw, label, focused, false);
-        let (clip_x, clip_w) = (real_ctx.content_left(), real_ctx.content_width());
-        real_ctx.pc.push_clip_rect(clip_x, ry - SECTION_CLIP_SLACK, clip_w, 2.0 * SECTION_CLIP_SLACK);
-        render_fn(&mut real_ctx);
-        real_ctx.pc.pop_clip_rect();
-        real_ctx.finish();
-        self.idx += 1;
-    }
-
-    pub fn add_section_spanned<F>(&mut self, final_pc: &mut P, label: &str, span: usize, focused: bool, render_fn: F)
-    where
-        F: FnMut(&mut SectionContext<'_, P>),
-    {
-        let col_width = self.strategy.get_column_width().unwrap_or(self.section_width);
-        let gap = self.strategy.get_gap();
-        let width = span as f32 * col_width + (span - 1) as f32 * gap;
-        self.add_section_with_width(final_pc, width, label, focused, render_fn);
-    }
-}
-
-pub struct SectionContext<'a, P> {
-    pub pc: &'a mut P,
-    pub left: f32,
-    pub top: f32,
-    pub content_y: f32,
-    pub cw: f32,
-    pub label_width: f32,
-    pub label_x: f32,
-    /// Whether the host renders sections as sunken wells (`section_relief_style`).
-    pub relief_style: bool,
-    /// The title tab box (x, y, w, h) when the host's relief styling laid the
-    /// label out left-aligned — `finish` offers it with the section carve.
-    /// None under relief styling means a label-less section: the well carves
-    /// tabless, flush with the allocation top.
-    pub relief_tab: Option<(f32, f32, f32, f32)>,
-    pub focused: bool,
-    pub is_child: bool,
-    pub grid: Grid,
-    pub last_col: usize,
-    pub content_start_y: f32,
-    pub row_gap: f32,
-}
-
-impl<'a, P: RenderTarget> SectionContext<'a, P> {
-    pub const DEFAULT_MARGIN_X: f32 = 12.0;
-    pub const DEFAULT_ROW_GAP: f32 = 8.0;
-
-    fn estimate_label_width(label: &str, font_size: f32, font_fam: &str) -> f32 {
-        estimate_label_width_helper(label, font_size, font_fam)
-    }
-
-    pub fn padding(&self) -> f32 {
-        section_padding()
-    }
-
-    pub fn new(pc: &'a mut P, left: f32, top: f32, cw: f32, label: &str, focused: bool, is_child: bool) -> Self {
-        let font_setting = if is_child {
-            nested_section_label_font()
-        } else {
-            section_label_font()
-        };
-        let (font_fam, font_size_opt) = parse_font_string(&font_setting);
-        let font_size = font_size_opt.unwrap_or(if is_child { 12.0 } else { 14.0 });
-        let font_color = if is_child { [0.53, 0.53, 0.60, 1.0] } else { [0.83, 0.83, 0.83, 1.0] };
-        let label_width = Self::estimate_label_width(label, font_size, &font_fam);
-        let relief_style = pc.section_relief_style();
-        let label_x = if is_child {
-            let base_x = match nested_section_label_alignment() {
-                0 => left + 12.0,
-                1 => left + (cw - label_width) / 2.0,
-                2 => left + cw - 12.0 - label_width,
-                _ => left + 12.0,
-            };
-            base_x + nested_section_label_offset()
-        } else if relief_style {
-            // Sunken style: the title sits in a tab flush with the well's left
-            // edge (the designer look), not centered on the border.
-            left + 12.0
-        } else {
-            left + (cw - label_width) / 2.0
-        };
-        // Under relief styling the well fills the whole allocated rect (so the
-        // page's gaps and margins are the visual gaps) — the tab tops the
-        // allocation and the label centers inside it.
-        let label_y = if relief_style { top + 4.0 } else { top };
-        pc.text_with_font(label, label_x, label_y, font_size, font_color, &font_fam);
-
-        // The tab wraps the label, flush on the well's top-left corner; clamped
-        // so off-default child alignments can't push it outside the well.
-        let relief_tab = if relief_style && label_width > 0.0 {
-            let tab_x = (label_x - 12.0).max(left);
-            Some((tab_x, top, label_width + 24.0, font_size + 10.0))
-        } else {
-            None
-        };
-
-        let pad = section_padding();
-        let margin_x = 2.0 * pad + 12.0;
-        let usable_w = (cw - 2.0 * margin_x).max(1.0);
-        let min_col_width = 130.0;
-        let gap = 8.0;
-        let max_cols = if is_child {
-            1
-        } else {
-            ((usable_w + gap) / (min_col_width + gap)).floor().max(1.0).min(2.0) as usize
-        };
-        // Under relief styling the content stands off the well's top wall by
-        // the same inset it keeps from the side walls (`margin_x`, which is
-        // also what `finish` leaves below it), so a well reads as one even
-        // frame. The outline style's `pad + 19` predates the title tab: under
-        // relief the tab alone is `font_size + 10` tall, which left the first
-        // line touching the well's top wall at the default padding while the
-        // sides kept 20px and more.
-        let content_start_y = if relief_style {
-            relief_tab.map(|t| t.1 + t.3).unwrap_or(top) + margin_x
-        } else {
-            top + pad + 19.0
-        };
-        let grid = Grid::new(left + margin_x, content_start_y, usable_w, min_col_width, gap, max_cols);
-
-        Self {
-            pc,
-            left,
-            top,
-            content_y: content_start_y,
-            cw,
-            label_width,
-            label_x,
-            relief_style,
-            relief_tab,
-            focused,
-            is_child,
-            grid,
-            last_col: usize::MAX,
-            content_start_y,
-            row_gap: Self::DEFAULT_ROW_GAP,
-        }
-    }
-
-    pub fn with_row_gap(mut self, gap: f32) -> Self {
-        self.row_gap = gap;
-        self
-    }
-
-    /// The section's content-box top edge: the body well's top (the tab's
-    /// bottom) under relief styling, the outline's border line otherwise. Lets
-    /// a page place content at an exact inset from the well's walls.
-    pub fn well_top(&self) -> f32 {
-        if self.relief_style {
-            self.relief_tab.map(|t| t.1 + t.3).unwrap_or(self.top)
-        } else {
-            self.top + 7.0
-        }
-    }
-
-    pub fn set_row_gap(&mut self, gap: f32) {
-        self.row_gap = gap;
-    }
-
-    /// Horizontal inset of section CONTENT from the section's left edge.
-    ///
-    /// One number, used by every content placer in here — `row_layout`, the
-    /// column `Grid`, `widget`, `VStack` and `ax` (so `text`) — because they
-    /// share a section and have to line up inside it. The section's border is
-    /// drawn at `left + padding()` (see `finish`), so content clears the
-    /// border by `padding() + 12`.
-    pub fn content_margin(&self) -> f32 {
-        2.0 * self.padding() + 12.0
-    }
-
-    /// Left edge of the content box: where a row, a widget or a `text(_, 12.0,
-    /// ..)` starts.
-    pub fn content_left(&self) -> f32 {
-        self.left + self.content_margin()
-    }
-
-    /// Width of the content box — the section's width less the inset on both
-    /// sides. Nothing a section draws should extend past `content_left() +
-    /// content_width()`.
-    pub fn content_width(&self) -> f32 {
-        (self.cw - 2.0 * self.content_margin()).max(0.0)
-    }
-
-    /// `x_off` px into the content box's coordinate space, where 12.0 is the
-    /// content's own left edge — the offset 46 of the ~55 call sites in the
-    /// tree already pass, and the one that lines text up with the buttons and
-    /// widgets beside it.
-    ///
-    /// This used to add `padding()` only when `x_off >= 12.0`, which made the
-    /// mapping DISCONTINUOUS: asking for 11 instead of 12 moved the text 5px
-    /// LEFT rather than 1px, and silently dropped it out of alignment with
-    /// every row in the same section. `cce-mail` and two others sit on the
-    /// wrong side of that cliff today.
-    pub fn ax(&self, x_off: f32) -> f32 {
-        self.left + 2.0 * self.padding() + x_off
-    }
-
-    pub fn ay(&self) -> f32 {
-        self.content_y
-    }
-
-    pub fn spacing(&mut self, dy: f32) {
-        if self.grid.col_heights.len() >= 2 {
-            if self.last_col == usize::MAX {
-                for h in &mut self.grid.col_heights {
-                    *h += dy;
-                }
-            } else if self.last_col < self.grid.col_heights.len() {
-                self.grid.col_heights[self.last_col] += dy;
-            }
-            self.content_y = self.grid.max_height();
-        } else {
-            self.content_y += dy;
-            for h in &mut self.grid.col_heights {
-                *h += dy;
-            }
-        }
-    }
-
-    pub fn text(&mut self, text: &str, x_off: f32, y_off: f32, font_size: f32, color: [f32; 4]) {
-        let mut y = self.content_y + y_off;
-        if y > self.content_start_y {
-            y += self.row_gap;
-        }
-        let x = self.ax(x_off);
-        // Bound it to the content box. A section's text was drawn unbounded,
-        // so a string wider than its section simply kept going — over the
-        // border, over whatever sat to the right, and off the window (the
-        // settings app's GPU names did all three). Rows and widgets have
-        // always been sized to the section; text was the one thing that could
-        // leave it. The vertical band is generous on purpose: it is the
-        // horizontal overrun that has to be cut, and a tight band would
-        // shave descenders.
-        let right = self.content_left() + self.content_width();
-        let bounds = if right > x {
-            Some([x, y - font_size, right, y + 2.0 * font_size])
-        } else {
-            None
-        };
-        self.pc.text_with_bounds(text, x, y, font_size, color, bounds);
-        let new_bottom = y + font_size + 4.0;
-        self.content_y = new_bottom;
-        for h in &mut self.grid.col_heights {
-            *h = new_bottom;
-        }
-    }
-
-    pub fn widget<T: WidgetHost + 'static>(&mut self, w: &mut T, _x_off: f32, _ww: f32, mut wh: f32, ctx: &mut UiContext) {
-        if let Some(pref) = w.preferred_height() {
-            wh = pref;
-        }
-        let pad = self.padding();
-        let top_room = w.label_strip();
-        let total_h = wh + top_room;
-
-        let name = w.type_name();
-        let span_full = name == "Trackpad"
-            || name == "Canvas"
-            || name == "UsageBar"
-            || name == "ProgressBar"
-            || name == "ButtonStrip"
-            || name == "Spreadsheet"
-            || name == "Graph";
-
-        if span_full {
-            let margin_x = 2.0 * pad + 12.0;
-            let x = self.left + margin_x;
-            let clamped_w = (self.cw - 2.0 * margin_x).max(0.0);
-            let mut max_h = self.grid.max_height().max(self.content_y);
-            if max_h > self.content_start_y {
-                max_h += self.row_gap;
-            }
-            let y = max_h;
-
-            w.set_row_rect(self.left + pad, self.cw - 2.0 * pad);
-            render_widget(self.pc, w, x, y, clamped_w, total_h, ctx);
-
-            let new_bottom = y + total_h;
-            self.content_y = new_bottom;
-            for h in &mut self.grid.col_heights {
-                *h = new_bottom;
-            }
-        } else {
-            let max_h = self.grid.max_height();
-            if self.content_y > max_h {
-                for h in &mut self.grid.col_heights {
-                    *h = self.content_y;
-                }
-            }
-
-            let col = self.grid.next_column();
-            self.last_col = col;
-            let x = self.grid.col_lefts[col];
-            let mut y = self.grid.col_heights[col];
-            if y > self.content_start_y {
-                y += self.row_gap;
-            }
-
-            let aligned_x = x;
-            let aligned_w = self.grid.col_width;
-
-            w.set_row_rect(aligned_x, aligned_w);
-            render_widget(self.pc, w, aligned_x, y, aligned_w, total_h, ctx);
-            self.grid.col_heights[col] = y + total_h;
-            self.content_y = self.grid.max_height();
-        }
-    }
-
-    pub fn widget_full<T: WidgetHost + 'static>(&mut self, w: &mut T, wh: f32, ctx: &mut UiContext) {
-        let x_off = 12.0;
-        let ww = self.cw - 2.0 * (self.padding() + x_off); // cw - 40.0
-        self.widget(w, x_off, ww, wh, ctx);
-    }
-
-    pub fn separator(&mut self) {
-        let pad = self.padding();
-        let x = self.ax(pad);
-        let max_h = self.grid.max_height().max(self.content_y);
-        let y = max_h;
-        self.pc.rect([0.18, 0.18, 0.27, 1.0], x, y, self.cw - 2.0 * pad, 1.0);
-        self.content_y = max_h + 8.0;
-        for h in &mut self.grid.col_heights {
-            *h = self.content_y;
-        }
-    }
-
-    pub fn rect(&mut self, color: [f32; 4], x_off: f32, w: f32, h: f32) {
-        let max_h = self.grid.max_height().max(self.content_y);
-        self.pc.rect(color, self.ax(x_off), max_h, w, h);
-        self.content_y = max_h + h;
-        for col_h in &mut self.grid.col_heights {
-            *col_h = self.content_y;
-        }
-    }
-
-    pub fn row_layout(&self, count: usize, gap: f32) -> Vec<(f32, f32)> {
-        let margin_x = self.content_margin();
-        let usable_w = self.content_width();
-        if count == 0 {
-            return Vec::new();
-        }
-        let total_gap = gap * (count - 1) as f32;
-        let col_w = (usable_w - total_gap).max(0.0) / count as f32;
-
-        let mut cols = Vec::with_capacity(count);
-        for i in 0..count {
-            let x = self.left + margin_x + i as f32 * (col_w + gap);
-            cols.push((x, col_w));
-        }
-        cols
-    }
-
-    /// A row whose columns are sized to what goes IN them: each gets the width
-    /// it asked for in `needs`, and whatever is left over is shared equally.
-    ///
-    /// `row_layout` splits a row evenly and knows nothing about content, so it
-    /// hands "Reboot" and "Hibernate" the same width — one floats in slack
-    /// while the other is cut off, which is what a row of mismatched labels
-    /// looks like. Sharing the SLACK equally rather than sizing proportionally
-    /// is deliberate: proportional widths would make a two-character label a
-    /// sliver, where what is wanted is "everyone fits, then everyone gets the
-    /// same bonus".
-    ///
-    /// When the needs do not fit, every column is scaled by the same factor, so
-    /// the row still cannot overflow its section and the shortfall is shared
-    /// rather than landing entirely on the last column.
-    pub fn row_layout_for(&self, needs: &[f32], gap: f32) -> Vec<(f32, f32)> {
-        let count = needs.len();
-        if count == 0 {
-            return Vec::new();
-        }
-        let total_gap = gap * (count - 1) as f32;
-        let room = (self.content_width() - total_gap).max(0.0);
-        let total_need: f32 = needs.iter().map(|n| n.max(0.0)).sum();
-
-        let widths: Vec<f32> = if total_need <= room {
-            let extra = (room - total_need) / count as f32;
-            needs.iter().map(|n| n.max(0.0) + extra).collect()
-        } else if total_need > 0.0 {
-            let scale = room / total_need;
-            needs.iter().map(|n| n.max(0.0) * scale).collect()
-        } else {
-            vec![room / count as f32; count]
-        };
-
-        let mut cols = Vec::with_capacity(count);
-        let mut x = self.content_left();
-        for w in widths {
-            cols.push((x, w));
-            x += w + gap;
-        }
-        cols
-    }
-
-
-    pub fn row<F>(&mut self, count: usize, gap: f32, h: f32, mut f: F)
-    where
-        F: FnMut(usize, f32, f32),
-    {
-        let max_h = self.grid.max_height().max(self.content_y);
-        for col_h in &mut self.grid.col_heights {
-            *col_h = max_h;
-        }
-        self.content_y = max_h;
-
-        let cols = self.row_layout(count, gap);
-        for (i, &(x, w)) in cols.iter().enumerate() {
-            f(i, x, w);
-        }
-        self.content_y += h;
-
-        for col_h in &mut self.grid.col_heights {
-            *col_h = self.content_y;
-        }
-    }
-
-    pub fn vstack(&mut self, spacing: f32) -> VStack<'_, 'a, P> {
-        VStack {
-            context: self,
-            spacing,
-        }
-    }
-
-    pub fn add_section<F>(&mut self, label: &str, focused: bool, mut render_fn: F)
-    where
-        F: FnMut(&mut SectionContext<'_, P>),
-    {
-        let pad = self.padding();
-        let (left, top, cw, is_side_by_side) = if self.grid.col_heights.len() >= 2 {
-            let col = self.grid.next_column();
-            self.last_col = col;
-            let x = self.grid.col_lefts[col];
-            let mut y = self.grid.col_heights[col];
-            if y > self.content_start_y {
-                y += self.row_gap;
-            }
-            (x, y, self.grid.col_width, true)
-        } else {
-            let left = self.ax(0.0) + pad;
-            let mut max_h = self.grid.max_height().max(self.content_y);
-            if max_h > self.content_start_y {
-                max_h += self.row_gap;
-            }
-            let top = max_h;
-            let cw = self.cw - 2.0 * pad;
-            (left, top, cw, false)
-        };
-
-        let mut sub_ctx = SectionContext::new(self.pc, left, top, cw, label, focused, true);
-        render_fn(&mut sub_ctx);
-        let new_bottom = sub_ctx.finish();
-
-        if is_side_by_side {
-            let col = self.last_col;
-            self.grid.col_heights[col] = new_bottom;
-            self.content_y = self.grid.max_height();
-        } else {
-            self.content_y = new_bottom;
-            for h in &mut self.grid.col_heights {
-                *h = new_bottom;
-            }
-        }
-    }
-
-    pub fn finish(self) -> f32 {
-        let border: [f32; 4] = if self.is_child {
-            if self.focused {
-                [0.22, 0.38, 0.24, 1.0]
-            } else {
-                [0.18, 0.18, 0.25, 1.0]
-            }
-        } else {
-            if self.focused {
-                [0.30, 0.50, 0.32, 1.0] // Focused green
-            } else {
-                [0.25, 0.25, 0.35, 1.0] // Default gray
-            }
-        };
-        let pad = self.padding();
-        let x = self.left + pad;
-        let y = self.top + 7.0;
-        let w = self.cw - 2.0 * pad;
-        let h = self.content_y - y;
-        // Under relief the well's walls are the allocation's edges, so the
-        // floor below the content matches the inset beside and above it (see
-        // `new`). The outline frame sits `pad` in from its allocation and
-        // keeps its own slack.
-        let extra_bottom = if self.relief_style { self.content_margin() } else { pad + 12.0 };
-        let bottom = y + h + extra_bottom;
-
-        if self.relief_style {
-            // Sunken style: the well spans the full allocated rect — body top
-            // edge at the tab's bottom (the tab is flush ON the body, the
-            // designer union shape; label-less sections carve tabless from the
-            // allocation top), walls on the allocation's edges, and no
-            // trailing slack so the layout gap IS the visual gap.
-            let body_y = self.relief_tab.map(|t| t.1 + t.3).unwrap_or(self.top);
-            let frame = SectionFrame {
-                x: self.left,
-                y: body_y,
-                w: self.cw,
-                h: bottom - body_y,
-                tab: self.relief_tab,
-                focused: self.focused,
-                is_child: self.is_child,
-            };
-            if self.pc.section_relief(&frame) {
-                return self.content_y + extra_bottom;
-            }
-        }
-
-        let left_edge = x;
-        let right_edge = x + w;
-        if self.label_width > 0.0 {
-            let label_x = self.label_x;
-            let gap_margin = 6.0;
-            let gap_start = label_x - gap_margin;
-            let gap_end = label_x + self.label_width + gap_margin;
-            if gap_start > left_edge {
-                self.pc.rect(border, left_edge, y, gap_start - left_edge, 1.0);
-            }
-            if right_edge > gap_end {
-                self.pc.rect(border, gap_end, y, right_edge - gap_end, 1.0);
-            }
-        } else {
-            self.pc.rect(border, left_edge, y, w, 1.0);
-        }
-
-        self.pc.rect(border, x, y + h + extra_bottom, w, 1.0);
-        self.pc.rect(border, x, y, 1.0, h + extra_bottom);
-        self.pc.rect(border, x + w - 1.0, y, 1.0, h + extra_bottom);
-        self.content_y + extra_bottom + 8.0
-    }
-}
-
-
-pub struct VStack<'b, 'a, P> {
-    pub context: &'b mut SectionContext<'a, P>,
-    pub spacing: f32,
-}
-
-impl<'b, 'a, P: RenderTarget> VStack<'b, 'a, P> {
-    pub fn add_widget<T: WidgetHost + 'static>(&mut self, w: &mut T, _ww: f32, wh: f32, ctx: &mut UiContext) {
-        let pad = self.context.padding();
-        let margin_x = 2.0 * pad + 12.0;
-        let x = self.context.left + margin_x;
-        let clamped_w = (self.context.cw - 2.0 * margin_x).max(0.0);
-
-        let mut max_h = self.context.grid.max_height().max(self.context.content_y);
-        if max_h > self.context.content_start_y {
-            max_h += self.context.row_gap;
-        }
-        let y = max_h;
-
-        let pref_h = w.preferred_height().unwrap_or(wh);
-        let top_room = w.label_strip();
-        let total_h = pref_h + top_room;
-
-        w.set_row_rect(self.context.left + pad, self.context.cw - 2.0 * pad);
-        render_widget(self.context.pc, w, x, y, clamped_w, total_h, ctx);
-
-        let new_bottom = y + total_h;
-        self.context.content_y = new_bottom;
-        for h in &mut self.context.grid.col_heights {
-            *h = new_bottom;
-        }
-        self.context.spacing(self.spacing);
-    }
-
-    /// [`add_row`](Self::add_row) with per-column widths from `needs` — see
-    /// [`SectionContext::row_layout_for`]. For a row of buttons, `needs` is
-    /// each label's measured width plus the plate's own inset.
-    pub fn add_row_for<F>(&mut self, needs: &[f32], gap: f32, h: f32, mut f: F)
-    where
-        F: FnMut(&mut SectionContext<'a, P>, usize, f32, f32),
-    {
-        let max_h = self.context.grid.max_height().max(self.context.content_y);
-        for col_h in &mut self.context.grid.col_heights {
-            *col_h = max_h;
-        }
-        self.context.content_y = max_h;
-
-        let cols = self.context.row_layout_for(needs, gap);
-        for (i, &(x, w)) in cols.iter().enumerate() {
-            self.context.content_y = max_h;
-            f(self.context, i, x, w);
-        }
-
-        let new_bottom = max_h + h;
-        self.context.content_y = new_bottom;
-        for col_h in &mut self.context.grid.col_heights {
-            *col_h = new_bottom;
-        }
-        self.context.spacing(self.spacing);
-    }
-
-    pub fn add_row<F>(&mut self, count: usize, gap: f32, h: f32, mut f: F)
-    where
-        F: FnMut(&mut SectionContext<'a, P>, usize, f32, f32),
-    {
-        let max_h = self.context.grid.max_height().max(self.context.content_y);
-        for col_h in &mut self.context.grid.col_heights {
-            *col_h = max_h;
-        }
-        self.context.content_y = max_h;
-
-        let cols = self.context.row_layout(count, gap);
-        for (i, &(x, w)) in cols.iter().enumerate() {
-            self.context.content_y = max_h;
-            f(self.context, i, x, w);
-        }
-
-        let new_bottom = max_h + h;
-        self.context.content_y = new_bottom;
-        for col_h in &mut self.context.grid.col_heights {
-            *col_h = new_bottom;
-        }
-        self.context.spacing(self.spacing);
-    }
-}
-
-pub fn get_system_monospace_font() -> &'static str {
-    static MONOSPACE_FONT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
-    MONOSPACE_FONT.get_or_init(|| {
-        if let Ok(output) = std::process::Command::new("fc-match")
-            .args(["-f", "%{family}", "monospace"])
-            .output()
-        {
-            let name = String::from_utf8_lossy(&output.stdout);
-            let parsed = name.split(',').next().unwrap_or("monospace").trim();
-            if !parsed.is_empty() {
-                return parsed.to_string();
-            }
-        }
-        "monospace".to_string()
-    })
-}
-
-pub fn get_system_sans_serif_font() -> &'static str {
-    static SANS_SERIF_FONT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
-    SANS_SERIF_FONT.get_or_init(|| {
-        if let Ok(output) = std::process::Command::new("fc-match")
-            .args(["-f", "%{family}", "sans-serif"])
-            .output()
-        {
-            let name = String::from_utf8_lossy(&output.stdout);
-            let parsed = name.split(',').next().unwrap_or("sans-serif").trim();
-            if !parsed.is_empty() {
-                return parsed.to_string();
-            }
-        }
-        "sans-serif".to_string()
-    })
-}
-
-/// The DE's font families, from the shared config's `fonts { }` block:
-/// `(sans_serif, serif, monospace, terminal)`.
-///
-/// These used to live in `~/.config/fontconfig/fonts.conf`, read back out of
-/// fontconfig's XML by alias. Three of the seven aliases it carried
-/// (`window-borders`, `status-interface`, `fuzzel`) were cce inventions
-/// squatting in fontconfig's family namespace, and by the end none of them was
-/// read by anything: window borders lost their text when titlebars went away,
-/// the status bar moved to `module { font }` / `/style/status/font` in KDL and
-/// only ever consulted the alias as a last-resort fallback, and fuzzel was
-/// replaced by cce-cloud. The settings app's Fonts page — which edited that
-/// file, rewriting it wholesale and preserving only its `<dir>` lines — went
-/// with them.
-///
-/// fonts.conf is still fontconfig's file and still governs GTK/Electron apps;
-/// cce simply no longer reads or writes it. Per-app overrides work here because
-/// this reads the merged config, the same way the status bar's font does.
-pub fn read_preferred_fonts() -> (String, String, String, String) {
-    let get = |key: &str, fallback: &str| {
-        crate::config::get_string(&format!("/fonts/{key}"))
-            .filter(|s| !s.trim().is_empty())
-            .unwrap_or_else(|| fallback.to_string())
-    };
-    (
-        get("sans_serif", "Noto Sans"),
-        get("serif", "Noto Serif"),
-        get("monospace", "Noto Sans Mono"),
-        get("terminal", "Noto Sans Mono"),
-    )
-}
-
-impl crate::widget::ContainerLayout for FlexLayout {
-    fn box_clone_container(&self) -> Box<dyn crate::widget::ContainerLayout> {
-        Box::new(self.clone())
-    }
-}
-
-impl crate::widget::ContainerLayout for ColumnLayout {
-    fn box_clone_container(&self) -> Box<dyn crate::widget::ContainerLayout> {
-        Box::new(self.clone())
-    }
-}
-
-impl crate::widget::ContainerLayout for AdaptiveGrid {
-    fn box_clone_container(&self) -> Box<dyn crate::widget::ContainerLayout> {
-        Box::new(self.clone())
-    }
-}
-
-impl crate::widget::ContainerLayout for RadialLayout {
-    fn box_clone_container(&self) -> Box<dyn crate::widget::ContainerLayout> {
-        Box::new(self.clone())
-    }
-}
-
-#[cfg(test)]
-mod tests {
     #[test]
     fn unit_slots_resolve_through_the_metric_at_read_time() {
         use crate::units::{Len, Metric, MetricSource};
diff --git a/src/layout/registry.rs b/src/layout/registry.rs
new file mode 100644
index 0000000..2533539
--- /dev/null
+++ b/src/layout/registry.rs
@@ -0,0 +1,469 @@
+//! The style registry: the config flattened into one process-wide map of style keys, the
+//! per-thread test overlay over it, and the font-string helpers its readers share. Every
+//! getter in `layout` and `color` reads through it.
+
+use super::*;
+use std::sync::RwLock;
+use std::collections::HashMap;
+use std::sync::OnceLock;
+
+/// Per-thread overrides for runtime style writes, under `cfg(test)` only.
+///
+/// Every `graph_*`, corner-radius and control-height slot in this file is
+/// backed by the one process-wide [`STYLE_REGISTRY`], so a test that pins any
+/// of them pins it for every test running beside it. That is the same defect
+/// as the font flake fixed in 1dc0ab1, and it was live:
+/// `test_graph_style_configuration` sets ~20 style values and restores none,
+/// while `dual_geometry_views_stay_consistent` bakes quads with
+/// `graph_node_corner_radius` and then re-reads that getter to compare — so a
+/// write landing between the two makes them disagree. Widening the write
+/// window to 300ms reproduced it on demand.
+///
+/// Fixing it at the registry rather than per accessor covers every slot it
+/// holds in one place, including ones no test pins yet.
+#[cfg(test)]
+mod test_overlay {
+    use std::cell::RefCell;
+    use std::collections::HashMap;
+    thread_local! {
+        static FLOATS: RefCell<HashMap<String, f32>> = RefCell::new(HashMap::new());
+        static LENS: RefCell<HashMap<String, crate::units::Len>> = RefCell::new(HashMap::new());
+        static STRINGS: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
+    }
+    pub fn set_float(k: &str, v: f32) {
+        LENS.with(|m| m.borrow_mut().remove(k));
+        FLOATS.with(|m| m.borrow_mut().insert(k.to_string(), v));
+    }
+    pub fn set_len(k: &str, v: crate::units::Len) {
+        FLOATS.with(|m| m.borrow_mut().remove(k));
+        LENS.with(|m| m.borrow_mut().insert(k.to_string(), v));
+    }
+    pub fn set_string(k: &str, v: String) {
+        STRINGS.with(|m| m.borrow_mut().insert(k.to_string(), v));
+    }
+    pub fn get_float(k: &str) -> Option<f32> {
+        if let Some(l) = LENS.with(|m| m.borrow().get(k).copied()) {
+            return Some(l.to_px());
+        }
+        FLOATS.with(|m| m.borrow().get(k).copied())
+    }
+    pub fn get_len(k: &str) -> Option<crate::units::Len> {
+        if let Some(l) = LENS.with(|m| m.borrow().get(k).copied()) {
+            return Some(l);
+        }
+        FLOATS.with(|m| m.borrow().get(k).copied()).map(crate::units::Len::px)
+    }
+    pub fn get_string(k: &str) -> Option<String> {
+        STRINGS.with(|m| m.borrow().get(k).cloned())
+    }
+}
+
+#[derive(Debug)]
+pub struct StyleRegistry {
+    pub floats: HashMap<String, f32>,
+    pub strings: HashMap<String, String>,
+    /// Slots whose config value carried a unit (`width=(mm)2.0`). Read
+    /// through `get_float` like any other number, resolved against the
+    /// process metric (`crate::units::metric`) at EVERY read, so a metric
+    /// that arrives after config load — outputs come in after the first
+    /// style read — or changes with the display is honoured live.
+    pub lens: HashMap<String, crate::units::Len>,
+}
+
+impl StyleRegistry {
+    pub fn new() -> Self {
+        Self {
+            floats: HashMap::new(),
+            strings: HashMap::new(),
+            lens: HashMap::new(),
+        }
+    }
+
+    pub fn get_float(&self, key: &str) -> Option<f32> {
+        #[cfg(test)]
+        if let Some(v) = test_overlay::get_float(key) {
+            return Some(v);
+        }
+        if let Some(len) = self.lens.get(key) {
+            return Some(len.to_px());
+        }
+        self.floats.get(key).copied()
+    }
+
+    /// The slot as a length with its unit: the configured `Len` when one was
+    /// given, else the plain number as logical px. For editors that show the
+    /// unit the user chose rather than the resolved pixel count.
+    pub fn get_len(&self, key: &str) -> Option<crate::units::Len> {
+        #[cfg(test)]
+        if let Some(v) = test_overlay::get_len(key) {
+            return Some(v);
+        }
+        if let Some(len) = self.lens.get(key) {
+            return Some(*len);
+        }
+        self.floats.get(key).map(|v| crate::units::Len::px(*v))
+    }
+
+    pub fn get_string(&self, key: &str) -> Option<String> {
+        #[cfg(test)]
+        if let Some(v) = test_overlay::get_string(key) {
+            return Some(v);
+        }
+        self.strings.get(key).cloned()
+    }
+
+    /// A plain number wins over any earlier unit value for the slot — a
+    /// runtime `set_float` is the newest opinion.
+    pub fn set_float(&mut self, key: &str, val: f32) {
+        #[cfg(test)]
+        {
+            test_overlay::set_float(key, val);
+            return;
+        }
+        #[cfg(not(test))]
+        self.load_float(key, val);
+    }
+
+    pub fn set_len(&mut self, key: &str, len: crate::units::Len) {
+        #[cfg(test)]
+        {
+            test_overlay::set_len(key, len);
+            return;
+        }
+        #[cfg(not(test))]
+        self.load_len(key, len);
+    }
+
+    pub fn set_string(&mut self, key: &str, val: String) {
+        #[cfg(test)]
+        {
+            test_overlay::set_string(key, val);
+            return;
+        }
+        #[cfg(not(test))]
+        self.load_string(key, val);
+    }
+
+    /// The CONFIG-LOAD writes, as opposed to the runtime `set_*` ones above.
+    /// Kept apart because under `cfg(test)` a runtime set goes to a per-thread
+    /// overlay while the loaded config must stay the shared base every test
+    /// reads — routing config through `set_*` would put one thread's config
+    /// in its overlay and leave every other thread with an empty registry.
+    pub fn load_float(&mut self, key: &str, val: f32) {
+        self.lens.remove(key);
+        self.floats.insert(key.to_string(), val);
+    }
+
+    pub fn load_len(&mut self, key: &str, len: crate::units::Len) {
+        self.floats.remove(key);
+        self.lens.insert(key.to_string(), len);
+    }
+
+    pub fn load_string(&mut self, key: &str, val: String) {
+        self.strings.insert(key.to_string(), val);
+    }
+}
+
+pub static STYLE_REGISTRY: OnceLock<RwLock<StyleRegistry>> = OnceLock::new();
+
+pub fn get_style_registry() -> &'static RwLock<StyleRegistry> {
+    STYLE_REGISTRY.get_or_init(|| RwLock::new(StyleRegistry::new()))
+}
+
+pub fn lazy_init_style_registry() {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        reload_config();
+    });
+}
+
+pub(super) fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props: &mut String) {
+    match val {
+        serde_json::Value::Object(map) => {
+            for (k, v) in map {
+                let next_prefix = if prefix.is_empty() {
+                    k.clone()
+                } else {
+                    format!("{}.{}", prefix, k)
+                };
+                flatten_json_to_flat_props(v, &next_prefix, flat_props);
+            }
+        }
+        _ => {
+            // A retired `window_manager` spelling of a relief key emits NO
+            // line: the fall-through below strips a path's first segment, so
+            // `window_manager.bevel_depth` would otherwise land on the live
+            // `bevel_depth` registry key by accident, alias or not. The
+            // `style.surface.*` retirements need no guard — stripped, they
+            // become `surface.relief.depth` and the like, which nothing reads.
+            if matches!(prefix, "window_manager.bevel_depth" | "window_manager.bevel_width" | "window_manager.bevel_shader") {
+                return;
+            }
+            let flat_key = match prefix {
+                "style.list.font" | "style.data.list.font" => "list_font",
+                "style.list.font_color" | "style.data.list.font_color" => "list_font_color",
+                "style.control.breadcrumb.font" => "breadcrumb_font",
+
+                // The control rung's default radius: every control-scale
+                // `corner_radius` below falls back to it when its own key is unset.
+                "style.control.corner_radius" => "control_corner_radius",
+                "style.control.button.padding" => "button_padding",
+                "style.control.button.height" => "button_height",
+                "style.control.button.corner_radius" => "button_corner_radius",
+                "style.control.button.font" => "button_font",
+                "style.list.corner_radius" | "style.data.list.corner_radius" => "list_corner_radius",
+                "style.control.textbox.corner_radius" | "style.textbox.corner_radius" | "style.data.textbox.corner_radius" => "textbox_corner_radius",
+                "style.control.dropdown.color" => "dropdown_color",
+                "style.control.font_selector.font" => "font_selector_font",
+                "style.container.section.font" | "style.section.font" => "section_label_font",
+                "style.container.section.padding" | "style.section.padding" => "section_padding",
+                "style.control.button_strip.font" => "button_strip_font",
+                "style.control.button_strip.spacing" => "button_strip_spacing",
+                "style.control.dropdown.height" => "dropdown_height",
+                "style.control.dropdown.corner_radius" => "dropdown_corner_radius",
+                "style.control.font_selector.height" => "font_selector_height",
+                "style.control.font_selector.corner_radius" => "font_selector_corner_radius",
+                "style.control.label.font" => "control_label_font",
+                "style.control.label.font_detached" => "control_label_font_detached",
+                "style.control.label.margin" => "control_label_margin",
+                "style.control.slider.height" => "slider_height",
+                "style.control.slider.corner_radius" => "slider_corner_radius",
+                "style.control.slider.band_thickness" => "slider_band_thickness",
+                "style.control.slider.bulge_width" => "slider_bulge_width",
+                "style.control.slider.bulge_height" => "slider_bulge_height",
+                "style.control.progressbar.height" => "progressbar_height",
+                "style.control.rangeslider.height" => "rangeslider_height",
+                "style.control.scrollbar.width" => "scrollbar_width",
+                "style.control.scrollbar.inset" => "scrollbar_inset",
+                "style.control.spinbox.height" => "spinbox_height",
+                "style.control.spinbox.button_padding" => "spinbox_button_padding",
+                "style.control.spinbox.corner_radius" => "spinbox_corner_radius",
+                "style.control.textbox.height" | "style.textbox.height" | "style.data.textbox.height" => "textbox_height",
+                "style.control.textbox.placeholder_text_color" | "style.textbox.placeholder_text_color" | "style.data.textbox.placeholder_text_color" => "textbox_placeholder_text_color",
+                "style.control.textbox.background_color" | "style.textbox.background_color" | "style.data.textbox.background_color" => "textbox_background_color",
+                "style.control.textbox.background_edit_color" | "style.textbox.background_edit_color" | "style.data.textbox.background_edit_color" => "textbox_background_edit_color",
+                "style.control.textbox.multiline.line_wrap" | "style.textbox.multiline.line_wrap" | "style.data.textbox.multiline.line_wrap" => "textbox_line_wrap",
+                "style.control.textbox.multiline.border_width" | "style.textbox.multiline.border_width" | "style.data.textbox.multiline.border_width" => "textbox_multiline_border_width",
+                "style.control.toggle.height" => "toggle_height",
+                "style.control.toggle.border_width" => "toggle_border_width",
+                "style.control.toggle.disabled_color" => "toggle_disabled_color",
+                "style.control.toggle.border_color" => "toggle_border_color",
+                "style.control.toggle.corner_radius" => "toggle_corner_radius",
+                "window_manager.light_source_position" => "light_source_position",
+                // The DE's relief material, home style.surface.relief: these
+                // shade every bevel/boss/recess in the toolkit. `light` is
+                // the spelling (since 2026-09-28): it is the light strength,
+                // not a length. Two older spellings are RETIRED — not read,
+                // reported by path (`color::retired_surface_keys`), removed
+                // by cce-relief's Save: `relief.depth`, what every config
+                // said until that day, and `window_manager.bevel_depth` /
+                // `bevel_width`, the block these keys were born in before
+                // they had a home of their own (the compositor never read
+                // them; the spelling survived as a compat alias until the
+                // evening of 2026-09-28).
+                "style.surface.relief.light" => "bevel_depth",
+                "style.surface.relief.width" => "bevel_width",
+                // The two SHAPES of the relief, each a node under it
+                // (2026-09-28): `wall` is a carve's wall — a recess, boss,
+                // ridge or trough cut into a surface — and `edge` is the
+                // plate's perimeter roll. Each carries `height` (a length:
+                // the carve's drop, the roll's rise; unset = follow the
+                // width) and `profile` (the ramp spec of its curve, written
+                // by cce-relief, installed by reload_config). The registry
+                // keys keep their old names. The pre-rename flat spellings
+                // (`height` / `profile` were the wall's, `edge_height` /
+                // `edge_profile` the edge's) were aliases for the rest of
+                // that day and are RETIRED: not read, reported by path
+                // (`color::retired_surface_keys`), removed by cce-relief's
+                // Save, which also reads them once as a seed. cce-relief's
+                // slider knobs are NOT a style key any more
+                // (`relief.wall.knobs` / `edge.knobs`, before that
+                // `profile_knobs` / `edge_knobs`): editor state, kept in
+                // that app's own state.kdl, and read off a config only by
+                // the editor itself, as a one-time seed.
+                "style.surface.relief.wall.height" => "bevel_height",
+                "style.surface.relief.edge.height" => "roll_height",
+                "style.surface.relief.wall.profile" => "bevel_profile_spec",
+                "style.surface.relief.edge.profile" => "roll_profile_spec",
+                "style.container.section.depth" => "section_depth",
+                "style.surface.param.backdrop_compression" => "param_compression",
+                "style.surface.param.label_layout" => "param_label_layout",
+                // The relief's shader toggle lives with the relief
+                // (`relief shader=(bool)false` is the legacy banded look);
+                // `window_manager.bevel_shader`, its old home, is retired
+                // and reported like the block's other bevel keys.
+                "style.surface.relief.shader" => "bevel_shader",
+                "window_manager.control_relief" => "control_relief",
+                "window_manager.corner_shape" => "corner_shape",
+                "style.control.ramp.height" => "ramp_height",
+                "style.layout.column.gap" => "column_gap",
+                "style.control.control_panel.padding" => "control_panel_padding",
+                "style.control.control_panel.gap" => "control_panel_gap",
+                "style.status.normal_color" => "status_normal_color",
+                "style.status.background_color" => "status_background_color",
+                "style.status.background_blur" => "status_background_blur",
+                "style.highlight.primary" => "primary_highlight_color",
+                "style.window.page_opacity" => "page_opacity",
+                "style.window.page_margin" => "page_margin",
+                "style.window.plate_padding" => "plate_padding",
+                "style.window.transition_duration" => "transition_duration",
+                "style.overlay.behavior" => "overlay_behavior",
+                "style.overlay.width" => "overlay_width",
+                "style.overlay.position" => "overlay_position",
+                "style.overlay.border_gap" => "overlay_border_gap",
+                "style.editor.last_page" | "style.data.editor.last_page" => "last_page",
+                "style.data.tree.corner_radius" => "tree_corner_radius",
+                "style.data.tree.opacity" => "tree_opacity",
+                "style.data.tree.blur" => "tree_blur",
+                "style.data.tree.font" => "tree_font",
+                "style.surface.desktop.gap_color" => "desktop_gap_color",
+                "style.surface.desktop.cell_color" => "desktop_cell_color",
+                "style.surface.desktop.gap_width" => "desktop_gap_width",
+                "style.surface.desktop.cell_fade_inset" => "desktop_cell_fade_inset",
+                "style.surface.desktop.mode" => "desktop_mode",
+                "style.surface.desktop.solid_color" => "desktop_solid_color",
+                "style.surface.desktop.grid_cell_size" => "desktop_grid_scale",
+                "style.surface.desktop.grid_cell_width" => "grid_cell_width",
+                "style.surface.desktop.grid_cell_height" => "grid_cell_height",
+                "style.surface.plate.padding" => "plate_padding",
+                "style.surface.plate.gap" => "plate_gap",
+                "style.control.gap" => "control_gap",
+                // The context menu's own radius (`menu_corner_radius`): a
+                // popover's corner is control-scale, not pane-scale.
+                "style.surface.menu.corner_radius" => "menu_corner_radius",
+                // `style.surface.plate.root.*` is the one spelling of the
+                // root-plate style (RFC Phase 7a). The slot names keep the
+                // historical `root_plate_` prefix; the legacy `root plate.*`
+                // config read-alias was removed 2026-09-06.
+                "style.surface.plate.root.padding" => "root_plate_padding",
+                "style.surface.plate.root.gap" => "root_plate_gap",
+                "style.surface.plate.root.color" => "root_plate_color",
+                "style.surface.plate.root.blur" => "root_plate_blur",
+                "style.surface.plate.root.corner_radius" => "root_plate_corner_radius",
+                "style.surface.plate.root.menubar.color" => "root_plate_menubar_color",
+                "style.surface.plate.root.menubar.text_color" => "root_plate_menubar_text_color",
+                "style.surface.plate.root.menubar.blur" => "root_plate_menubar_blur",
+                "style.surface.plate.root.menubar.font" => "menubar_font",
+                "style.surface.statusbar.color" => "root_plate_statusbar_color",
+                "style.surface.statusbar.text_color" => "root_plate_statusbar_text_color",
+                "style.surface.statusbar.blur" => "root_plate_statusbar_blur",
+                "style.surface.statusbar.font" => "statusbar_font",
+                "style.surface.page.opacity" => "page_opacity",
+                "style.surface.page.margin" => "page_margin",
+                "style.surface.graph.grid_color" => "graph_grid_color",
+                "style.surface.graph.opacity" => "graph_opacity",
+                "style.surface.graph.node.opacity" => "graph_node_opacity",
+                "style.surface.graph.spacing_x" => "graph_spacing_x",
+                "style.surface.graph.spacing_y" => "graph_spacing_y",
+                "style.surface.graph.line_width" => "graph_line_width",
+                "style.surface.graph.node.width" => "graph_node_width",
+                "style.surface.graph.node.height" => "graph_node_height",
+                "style.surface.graph.grid_snap" => "graph_grid_snap",
+                "style.surface.graph.blur" => "graph_blur",
+                "style.surface.graph.font" => "graph_font",
+                "style.surface.graph.node.color" => "graph_node_color",
+                "style.surface.graph.node.font" => "graph_node_font",
+                "style.surface.graph.node.delete" => "graph_node_delete",
+                "style.surface.graph.node.selected_color" => "graph_node_selected_color",
+                "style.surface.graph.node.drag_color" => "graph_node_drag_color",
+                "style.surface.graph.node.corner_radius" => "graph_node_corner_radius",
+                "style.surface.graph.node.wire_color" => "graph_wire_color",
+                "style.surface.graph.node.wire_highlight_color" => "graph_wire_highlight_color",
+                "style.surface.graph.node.wire_size" => "graph_wire_size",
+                "style.surface.graph.node.wire_style" => "graph_wire_style",
+                "style.surface.graph.node.wire_activation_radius" => "graph_wire_activation_radius",
+                "style.surface.graph.node.connector_color" => "graph_connector_color",
+                "style.surface.graph.node.connector_highlight_color" => "graph_connector_highlight_color",
+                "style.surface.graph.node.connector_size" => "graph_connector_size",
+                "style.surface.graph.node.connector_activation_radius" => "graph_connector_activation_radius",
+                "style.surface.plate.color" => "plate_color",
+                "style.surface.plate.border_color" => "plate_border_color",
+                "style.surface.plate.border_thickness" => "plate_border_thickness",
+                "input.touchpad.natural_scroll" => "touchpad_natural_scroll",
+                
+                other => {
+                    if let Some(rest) = other.strip_prefix("layout.") {
+                        rest
+                    } else if let Some(rest) = other.strip_prefix("transparency.") {
+                        rest
+                    } else {
+                        if let Some(idx) = other.find('.') {
+                            &other[idx + 1..]
+                        } else {
+                            other
+                        }
+                    }
+                }
+            };
+            
+            if let Some(s) = val.as_str() {
+                flat_props.push_str(&format!("{} = \"{}\"\n", flat_key, s));
+            } else if let Some(b) = val.as_bool() {
+                flat_props.push_str(&format!("{} = {}\n", flat_key, b));
+            } else if let Some(n) = val.as_f64() {
+                flat_props.push_str(&format!("{} = {}\n", flat_key, n));
+            } else if let Some(n) = val.as_i64() {
+                flat_props.push_str(&format!("{} = {}\n", flat_key, n));
+            }
+        }
+    }
+}
+
+// There is no alias-precedence pass any more. For the rest of 2026-09-28
+// `prefer_relief_spellings` ran here, dropping a relief key's legacy
+// spelling whenever its current one was present (`height` under
+// `wall.height`, `depth` under `light`), because two spellings of one
+// registry key would otherwise be decided by the order the JSON handed
+// the lines out. Every legacy spelling of the relief is retired now — not
+// read at all, reported by `color::retired_surface_keys` — so there is
+// nothing left to prefer, and a config is what it says.
+
+pub(super) fn read_config() -> Option<String> {
+    let path = crate::config::get_config_path();
+    if let Ok(content) = std::fs::read_to_string(&path) {
+        let val = crate::config::parse_kdl_to_json(&content);
+        let mut flat_props = String::new();
+        flatten_json_to_flat_props(&val, "", &mut flat_props);
+        return Some(flat_props);
+    }
+    None
+}
+
+pub fn read_config_value(target_key: &str) -> Option<String> {
+    let path = crate::config::get_config_path();
+    if let Ok(content) = std::fs::read_to_string(&path) {
+        let val = crate::config::parse_kdl_to_json(&content);
+        let mut flat_props = String::new();
+        flatten_json_to_flat_props(&val, "", &mut flat_props);
+        for line in flat_props.lines() {
+            let trimmed = line.trim();
+            if let Some(eq_idx) = trimmed.find('=') {
+                let key = trimmed[..eq_idx].trim();
+                if key == target_key {
+                    let val_str = trimmed[eq_idx + 1..].trim().trim_matches('"').trim();
+                    return Some(val_str.to_string());
+                }
+            }
+        }
+    }
+    None
+}
+
+pub fn parse_font_string(s: &str) -> (String, Option<f32>) {
+    let (family, size) = split_font_string(s);
+    (family.to_string(), size)
+}
+
+/// [`parse_font_string`] borrowing the family from `s` instead of copying it.
+pub fn split_font_string(s: &str) -> (&str, Option<f32>) {
+    let s = s.trim();
+    if let Some(last_space_idx) = s.rfind(' ') {
+        let (family, size_str) = s.split_at(last_space_idx);
+        let size_str = size_str.trim();
+        if let Ok(size) = size_str.parse::<f32>() {
+            return (family.trim(), Some(size));
+        }
+    }
+    (s, None)
+}
diff --git a/src/lib.rs b/src/lib.rs
index 7d54195..b9146ed 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -55,8 +55,6 @@ pub mod colors {
 pub use cosmic_text;
 
 
-pub static IS_VERTICAL: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
-pub static BAR_THICKNESS: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(24);
 
 /// `CCE_SCROLL_DEBUG=1` traces the wheel pipeline to stderr: raw coalesced
 /// axis input (runner), routing decisions (ParametersBg), slider gate/value
diff --git a/src/widget/display/label.rs b/src/widget/display/label.rs
index fca7442..2121aa4 100644
--- a/src/widget/display/label.rs
+++ b/src/widget/display/label.rs
@@ -164,13 +164,13 @@ impl StyledLabel {
     pub fn new_with_family(fs: &mut cosmic_text::FontSystem, text: &str, size: f32, color: [f32; 4], family: &str) -> Self {
         let scale = crate::scale::scale_factor();
         let mut final_text = text.to_string();
-        let is_vert = crate::IS_VERTICAL.load(std::sync::atomic::Ordering::Relaxed);
+        let is_vert = crate::backend::text::vertical_text().is_some();
         if is_vert {
             final_text = text.chars().map(|c| c.to_string()).collect::<Vec<_>>().join("\n");
         }
         let mut buffer = crate::backend::text::get_text_buffer(fs, &final_text, size, Some(family));
         if is_vert {
-            let bar_thickness = crate::BAR_THICKNESS.load(std::sync::atomic::Ordering::Relaxed) as f32;
+            let bar_thickness = crate::backend::text::vertical_text().unwrap_or(24) as f32;
             buffer.set_size(fs, Some(bar_thickness * scale as f32), None);
             for line in &mut buffer.lines {
                 line.set_align(Some(cosmic_text::Align::Center));
@@ -190,7 +190,7 @@ impl StyledLabel {
         // Vertical text is boxed: the per-char-newline `final_text` wrapped to the bar
         // thickness and centered — the same set_size + center-align the buffer path applied.
         let prim_layout = if is_vert {
-            let bar_thickness = crate::BAR_THICKNESS.load(std::sync::atomic::Ordering::Relaxed) as f32;
+            let bar_thickness = crate::backend::text::vertical_text().unwrap_or(24) as f32;
             Some(crate::scene::paint::TextLayout {
                 // Effectively unbounded height (the legacy vertical path used height None);
                 // align_v Top means no vertical offset, so only set_size's height sees this.
@@ -220,7 +220,7 @@ impl StyledLabel {
     /// (Phase 6ak): the source text, size, family, and — for vertical bars — the box layout.
     /// `x, y` are the draw position; vertical labels pin `y` to 0 (as `draw` did).
     pub fn into_prim(self, x: f32, y: f32) -> LabelPrim {
-        let is_vert = crate::IS_VERTICAL.load(std::sync::atomic::Ordering::Relaxed);
+        let is_vert = crate::backend::text::vertical_text().is_some();
         LabelPrim {
             text: self.src_text,
             size: self.src_size,
@@ -248,7 +248,7 @@ impl StyledLabel {
 
     pub fn draw(self, text_items: &mut Vec<TextItem>, x: f32, y: f32) -> f32 {
         let w = self.w;
-        let is_vert = crate::IS_VERTICAL.load(std::sync::atomic::Ordering::Relaxed);
+        let is_vert = crate::backend::text::vertical_text().is_some();
         text_items.push(TextItem {
             buffer: self.buffer,
             x,
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 733ef61..c441965 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -1753,7 +1753,7 @@ impl Paint for TextBox {
             font: font_fam.clone(),
             attrs: self.font_attrs,
             scale_bits: scale.to_bits(),
-            vertical: crate::IS_VERTICAL.load(std::sync::atomic::Ordering::Relaxed),
+            vertical: crate::backend::text::vertical_text().is_some(),
             wrap,
         };
         if self.prep_key.as_ref() != Some(&key) {