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

src/color/mod.rs (68.1K)

   1 mod load;
   2 pub use load::*;
   3 mod math;
   4 pub use math::*;
   5 mod chords;
   6 pub use chords::*;
   7 mod materials;
   8 pub use materials::*;
   9 
  10 pub const HEADER_BG: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
  11 pub const HEADER_ACCENT: [f32; 4] = [0.60, 0.40, 0.20, 1.0];
  12 pub const SIDEBAR_BG: [f32; 4] = [0.10, 0.10, 0.13, 1.0];
  13 pub const CONTENT_BG: [f32; 4] = [0.13, 0.13, 0.16, 0.2];
  14 pub const PANEL_IDLE: [f32; 4] = [0.14, 0.70, 0.38, 1.0];
  15 pub const PANEL_DRAG: [f32; 4] = [0.24, 0.85, 0.50, 1.0];
  16 pub const NODE_IDLE: [f32; 4] = [0.10, 0.45, 0.70, 1.0];
  17 pub const NODE_SELECTED: [f32; 4] = [0.20, 0.65, 0.90, 1.0];
  18 pub const NODE_DRAG: [f32; 4] = [0.30, 0.80, 1.00, 1.0];
  19 pub const BUTTON_IDLE: [f32; 4] = [0.20, 0.40, 0.65, 0.4];
  20 pub const BUTTON_HOVER: [f32; 4] = [0.30, 0.52, 0.78, 0.6];
  21 pub const BUTTON_PRESS: [f32; 4] = [0.12, 0.28, 0.50, 0.8];
  22 pub const STATUS_BG: [f32; 4] = [0.06, 0.06, 0.10, 1.0];
  23 pub const STATUS_ACCENT: [f32; 4] = [0.20, 0.20, 0.25, 1.0];
  24 pub const RESET_BTN_IDLE: [f32; 4] = [0.55, 0.20, 0.20, 0.4];
  25 pub const RESET_BTN_HOVER: [f32; 4] = [0.70, 0.30, 0.30, 0.6];
  26 pub const RESET_BTN_PRESS: [f32; 4] = [0.40, 0.12, 0.12, 0.8];
  27 pub const TOGGLE_OFF: [f32; 4] = [0.25, 0.25, 0.30, 1.0];
  28 pub const TOGGLE_ON: [f32; 4] = [0.14, 0.70, 0.38, 1.0];
  29 pub const TOGGLE_HOVER: [f32; 4] = [0.30, 0.30, 0.35, 1.0];
  30 pub const SLIDER_TRACK: [f32; 4] = [0.18, 0.18, 0.22, 1.0];
  31 
  32 
  33 // This module's part of the one style snapshot (`crate::style`): every slot below,
  34 // with its default. Each slot's `static` handle stands where its lock did.
  35 crate::style::style_slots! {
  36     NAMED_MATERIALS: Vec<(String, crate::scene::material::MaterialDef)> = Vec::new();
  37     MATERIAL_BINDINGS: [Option<String>; 3] = [None, None, None];
  38     PAGE_LOW_COLOR: [f32; 4] = [0.0600316, 0.0600316, 0.080219, 1.0];
  39     COLOR_BORDERS_COLOR: [f32; 4] = [0.2039, 0.2039, 0.2530, 1.0];
  40     NODE_COLOR: [f32; 4] = NODE_IDLE;
  41     NODE_SELECTED_COLOR: [f32; 4] = NODE_SELECTED;
  42     NODE_DRAG_COLOR: [f32; 4] = NODE_DRAG;
  43     SIDEBAR_BG_COLOR: [f32; 4] = SIDEBAR_BG;
  44     HIGHLIGHT_PRIMARY_COLOR: [f32; 4] = HIGHLIGHT_PRIMARY;
  45     MENUBAR_TAB_LABEL_COLOR: [f32; 4] = [0.90196, 0.90196, 0.94902, 1.0];
  46     CONTROL_LABEL_COLOR: [f32; 4] = [0.61206, 0.61206, 0.68666, 1.0];
  47     CONTROL_LABEL_COLOR_DETACHED: [f32; 4] = [0.22416, 0.22416, 0.2526, 1.0];
  48     CONTROL_LABEL_HOVER_COLOR: Option<[f32; 4]> = None;
  49     CONTROL_LABEL_FOCUS_COLOR: Option<[f32; 4]> = None;
  50     OPACITY: Option<f32> = None;
  51     ROOT_PLATE_OPACITY: Option<f32> = None;
  52     LIST_BG_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 0.3];
  53     LIST_ENTRY_BG_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 0.04];
  54     LIST_ENTRY_HIGHLIGHT_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 0.8];
  55     LIST_FONT_COLOR: [f32; 4] = [0.80, 0.80, 0.85, 1.0];
  56     BREADCRUMB_BG_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
  57     POPOVER_BG_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
  58     PAGE_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
  59     LAYER_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
  60     ROOT_PLATE_CORNER_RADIUS: f32 = 12.0;
  61     DROPDOWN_BACKGROUND_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
  62     TEXTBOX_PLACEHOLDER_TEXT_COLOR: [u8; 3] = [0x60, 0x60, 0x6a];
  63     TEXTBOX_BACKGROUND_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
  64     TEXTBOX_BACKGROUND_EDIT_COLOR: [f32; 4] = [0.06, 0.10, 0.18, 1.0];
  65     ROOT_PLATE_MENUBAR_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
  66     ROOT_PLATE_MENUBAR_TEXT_COLOR: [f32; 4] = [0.90196, 0.90196, 0.94902, 1.0];
  67     ROOT_PLATE_MENUBAR_BLUR: bool = false;
  68     MENU_OPACITY: f32 = 0.8;
  69     MENU_COMPRESSION: f32 = 0.6;
  70     MENU_COLOR: Option<[f32; 4]> = None;
  71     ROOT_PLATE_STATUSBAR_COLOR: [f32; 4] = [0.06, 0.06, 0.10, 1.0];
  72     ROOT_PLATE_STATUSBAR_TEXT_COLOR: [f32; 4] = [0.6666, 0.6666, 0.7333, 1.0];
  73     ROOT_PLATE_STATUSBAR_BLUR: bool = false;
  74     BUTTON_BACKGROUND_COLOR: [f32; 4] = BUTTON_IDLE;
  75     RAMP_BACKGROUND_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
  76     RAMP_BORDER_COLOR: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
  77     CONTROL_PANEL_COLOR: [f32; 4] = [0.075, 0.082, 0.11, 1.0];
  78     CONTROL_PANEL_BORDER_COLOR: [f32; 4] = [0.161, 0.173, 0.216, 1.0];
  79     PROGRESS_BG_COLOR: [f32; 4] = PROGRESS_BG;
  80     PROGRESS_FILL_COLOR: [f32; 4] = PROGRESS_FILL;
  81     SPINBOX_DISPLAY_COLOR: [f32; 4] = SPINBOX_DISPLAY;
  82     SPINBOX_BUTTON_COLOR: [f32; 4] = SPINBOX_BUTTON;
  83     SPINBOX_BUTTON_HOVER_COLOR: [f32; 4] = SPINBOX_BUTTON_HOVER;
  84     SPINBOX_TEXT_COLOR: [f32; 4] = [0.8, 0.8, 0.83, 1.0];
  85     BUTTON_BORDER_COLOR: Option<[f32; 4]> = None;
  86     DROPDOWN_BORDER_COLOR: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
  87     DROPDOWN_TEXT_COLOR: [f32; 4] = [0.72305, 0.72305, 0.76008, 1.0];
  88     SLIDER_THUMB_COLOR: [f32; 4] = SLIDER_THUMB;
  89     SLIDER_THUMB_DRAG_COLOR: [f32; 4] = SLIDER_THUMB_DRAG;
  90     RANGE_SLIDER_THUMB_COLOR: [f32; 4] = SLIDER_THUMB;
  91     RANGE_SLIDER_THUMB_DRAG_COLOR: [f32; 4] = SLIDER_THUMB_DRAG;
  92     TREE_BACKGROUND_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 0.3];
  93     TREE_BORDER_COLOR: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
  94     TREE_BORDER_HOVER_COLOR: [f32; 4] = [0.25, 0.25, 0.35, 1.0];
  95     TREE_BORDER_FOCUS_COLOR: [f32; 4] = [0.30, 0.50, 0.32, 1.0];
  96     TREE_OPEN_SEARCH_KEY: String = String::new();
  97     LIST_OPEN_SEARCH_KEY: String = String::new();
  98     LIST_CLOSE_SEARCH_KEY: String = String::new();
  99     TREE_SECTION_BG_COLOR: [f32; 4] = [0.07, 0.07, 0.09, 1.0];
 100     TREE_SECTION_BG_HOVER_COLOR: [f32; 4] = [0.10, 0.12, 0.18, 1.0];
 101     TREE_LEAF_BG_EVEN_COLOR: [f32; 4] = [0.09, 0.09, 0.11, 1.0];
 102     TREE_LEAF_BG_ODD_COLOR: [f32; 4] = [0.08, 0.08, 0.10, 1.0];
 103     TREE_LEAF_BG_HOVER_COLOR: [f32; 4] = [0.12, 0.12, 0.16, 1.0];
 104     TREE_LEAF_BG_SELECTED_COLOR: [f32; 4] = [0.15, 0.20, 0.30, 1.0];
 105     TREE_SECTION_TEXT_COLOR: [f32; 4] = [0.38, 0.69, 0.94, 1.0];
 106     TREE_LEAF_TEXT_COLOR: [f32; 4] = [0.80, 0.80, 0.83, 1.0];
 107     TREE_LEAF_TEXT_SELECTED_COLOR: [f32; 4] = [0.49, 1.0, 1.0, 1.0];
 108     TREE_TYPE_TEXT_COLOR: [f32; 4] = [0.78, 0.47, 0.87, 1.0];
 109     TREE_VALUE_TEXT_COLOR: [f32; 4] = [0.51, 0.51, 0.54, 1.0];
 110     TREE_SEPARATOR_COLOR: [f32; 4] = [0.15, 0.15, 0.19, 1.0];
 111     SCROLLBAR_TRACK_COLOR: [f32; 4] = [0.15, 0.15, 0.20, 0.3];
 112     SCROLLBAR_THUMB_COLOR: [f32; 4] = [0.60, 0.60, 0.65, 0.4];
 113     GRAPH_GRID_COLOR: [f32; 3] = [0.07, 0.07, 0.09];
 114     GRAPH_OPACITY: f32 = 0.95;
 115     GRAPH_NODE_OPACITY: f32 = 1.0;
 116     GRAPH_NODE_COLOR: [f32; 4] = NODE_IDLE;
 117     GRAPH_NODE_SELECTED_COLOR: [f32; 4] = NODE_SELECTED;
 118     GRAPH_NODE_DRAG_COLOR: [f32; 4] = NODE_DRAG;
 119     GRAPH_WIRE_COLOR: [f32; 4] = [0.1, 0.8, 0.4, 1.0];
 120     GRAPH_WIRE_HIGHLIGHT_COLOR: [f32; 4] = [0.0, 1.0, 0.9, 1.0];
 121     GRAPH_CONNECTOR_COLOR: [f32; 4] = [0.1, 0.8, 0.4, 1.0];
 122     GRAPH_CONNECTOR_HIGHLIGHT_COLOR: [f32; 4] = [0.0, 1.0, 0.9, 1.0];
 123     PARAM_BG_COLOR: [f32; 4] = PARAM_BG;
 124     PANE_COLOR_WHOLE: bool = false;
 125     PLATE_COLOR: Option<[f32; 4]> = Some([0.15, 0.15, 0.2, 0.95]);
 126     PLATE_BORDER_COLOR: Option<[f32; 4]> = Some([0.3, 0.3, 0.4, 1.0]);
 127     PLATE_BORDER_THICKNESS: f32 = 1.0;
 128     PLATE_BACKDROP_COMPRESSION: f32 = 0.0;
 129     PLATE_REFRACTION: f32 = 0.0;
 130     PLATE_BLUR: bool = false;
 131     FINISH_SPEC: f32 = FINISH_SPEC_DEFAULT;
 132     FINISH_SHININESS: f32 = FINISH_SHININESS_DEFAULT;
 133     FINISH_CURVATURE: f32 = FINISH_CURVATURE_DEFAULT;
 134     PLATE_FROST_RADIUS: f32 = crate::scene::material::Frost::DEFAULT_RADIUS;
 135 }
 136 
 137 /// Per-thread overrides for runtime colour writes, under `cfg(test)` only.
 138 ///
 139 /// Same defect as the font flake (cce-ui 1dc0ab1) and the style registry
 140 /// beside it: these are process-wide `RwLock` statics, so a test that pins a
 141 /// colour pins it for every test running alongside. It was live —
 142 /// `test_graph_style_configuration` sets a dozen of them and restores none.
 143 ///
 144 /// Keyed by the address of the static itself, so a slot needs no name
 145 /// repeated in two places and cannot be typo'd into a silent miss. Config
 146 /// loading is unaffected: `parse_and_set_colors` writes the statics directly
 147 /// and never goes through these setters, so the loaded palette stays the
 148 /// shared base every test thread reads.
 149 #[cfg(test)]
 150 mod test_overlay {
 151     use std::any::Any;
 152     use std::cell::RefCell;
 153     use std::collections::HashMap;
 154     thread_local! {
 155         static MAP: RefCell<HashMap<usize, Box<dyn Any>>> = RefCell::new(HashMap::new());
 156     }
 157     pub fn get<T: Clone + 'static>(key: usize) -> Option<T> {
 158         MAP.with(|m| m.borrow().get(&key).and_then(|b| b.downcast_ref::<T>()).cloned())
 159     }
 160     pub fn set<T: 'static>(key: usize, val: T) {
 161         MAP.with(|m| m.borrow_mut().insert(key, Box::new(val)));
 162     }
 163 }
 164 
 165 /// Read a style static, preferring this thread's test override.
 166 #[inline]
 167 fn style_read<T: Clone + 'static>(cell: &'static crate::style::StyleCell<T>) -> T {
 168     #[cfg(test)]
 169     if let Some(v) = test_overlay::get::<T>(cell as *const _ as usize) {
 170         return v;
 171     }
 172     cell.read().unwrap().clone()
 173 }
 174 
 175 /// Write a style static: per-thread under `cfg(test)`, process-wide otherwise.
 176 #[inline]
 177 fn style_write<T: Clone + 'static>(cell: &'static crate::style::StyleCell<T>, val: T) {
 178     #[cfg(test)]
 179     test_overlay::set(cell as *const _ as usize, val);
 180     #[cfg(not(test))]
 181     if let Ok(mut lock) = cell.write() {
 182         *lock = val;
 183     }
 184 }
 185 
 186 
 187 static PAGE_LOW_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PAGE_LOW_COLOR, |s| &mut s.color.PAGE_LOW_COLOR);
 188 static COLOR_BORDERS_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.COLOR_BORDERS_COLOR, |s| &mut s.color.COLOR_BORDERS_COLOR);
 189 static NODE_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.NODE_COLOR, |s| &mut s.color.NODE_COLOR);
 190 static NODE_SELECTED_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.NODE_SELECTED_COLOR, |s| &mut s.color.NODE_SELECTED_COLOR);
 191 static NODE_DRAG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.NODE_DRAG_COLOR, |s| &mut s.color.NODE_DRAG_COLOR);
 192 static SIDEBAR_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SIDEBAR_BG_COLOR, |s| &mut s.color.SIDEBAR_BG_COLOR);
 193 static HIGHLIGHT_PRIMARY_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.HIGHLIGHT_PRIMARY_COLOR, |s| &mut s.color.HIGHLIGHT_PRIMARY_COLOR);
 194 static MENUBAR_TAB_LABEL_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.MENUBAR_TAB_LABEL_COLOR, |s| &mut s.color.MENUBAR_TAB_LABEL_COLOR); // sRGB [230, 230, 242] linear
 195 static CONTROL_LABEL_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.CONTROL_LABEL_COLOR, |s| &mut s.color.CONTROL_LABEL_COLOR); // sRGB [204, 204, 212]
 196 static CONTROL_LABEL_COLOR_DETACHED: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.CONTROL_LABEL_COLOR_DETACHED, |s| &mut s.color.CONTROL_LABEL_COLOR_DETACHED); // sRGB [131, 131, 138]
 197 static CONTROL_LABEL_HOVER_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.CONTROL_LABEL_HOVER_COLOR, |s| &mut s.color.CONTROL_LABEL_HOVER_COLOR);
 198 static CONTROL_LABEL_FOCUS_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.CONTROL_LABEL_FOCUS_COLOR, |s| &mut s.color.CONTROL_LABEL_FOCUS_COLOR);
 199 static OPACITY: crate::style::StyleCell<Option<f32>> = crate::style::StyleCell::new(|s| &s.color.OPACITY, |s| &mut s.color.OPACITY);
 200 static ROOT_PLATE_OPACITY: crate::style::StyleCell<Option<f32>> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_OPACITY, |s| &mut s.color.ROOT_PLATE_OPACITY);
 201 static LIST_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LIST_BG_COLOR, |s| &mut s.color.LIST_BG_COLOR);
 202 static LIST_ENTRY_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LIST_ENTRY_BG_COLOR, |s| &mut s.color.LIST_ENTRY_BG_COLOR);
 203 static LIST_ENTRY_HIGHLIGHT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LIST_ENTRY_HIGHLIGHT_COLOR, |s| &mut s.color.LIST_ENTRY_HIGHLIGHT_COLOR);
 204 static LIST_FONT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LIST_FONT_COLOR, |s| &mut s.color.LIST_FONT_COLOR);
 205 static BREADCRUMB_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.BREADCRUMB_BG_COLOR, |s| &mut s.color.BREADCRUMB_BG_COLOR);
 206 static POPOVER_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.POPOVER_BG_COLOR, |s| &mut s.color.POPOVER_BG_COLOR);
 207 static PAGE_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PAGE_COLOR, |s| &mut s.color.PAGE_COLOR);
 208 static LAYER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LAYER_COLOR, |s| &mut s.color.LAYER_COLOR);
 209 static ROOT_PLATE_CORNER_RADIUS: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_CORNER_RADIUS, |s| &mut s.color.ROOT_PLATE_CORNER_RADIUS);
 210 
 211 // Transparent by default (alpha 0): a dropdown picks up the surface it sits
 212 // on, and its closed-state chrome is the flush inset trough alone — the
 213 // cce-files treatment, DE-wide. A configured `dropdown color=` opts a theme
 214 // back into a filled face (the paint path judges the RAW alpha).
 215 static DROPDOWN_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.DROPDOWN_BACKGROUND_COLOR, |s| &mut s.color.DROPDOWN_BACKGROUND_COLOR);
 216 
 217 static TEXTBOX_PLACEHOLDER_TEXT_COLOR: crate::style::StyleCell<[u8; 3]> = crate::style::StyleCell::new(|s| &s.color.TEXTBOX_PLACEHOLDER_TEXT_COLOR, |s| &mut s.color.TEXTBOX_PLACEHOLDER_TEXT_COLOR);
 218 static TEXTBOX_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TEXTBOX_BACKGROUND_COLOR, |s| &mut s.color.TEXTBOX_BACKGROUND_COLOR);
 219 static TEXTBOX_BACKGROUND_EDIT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TEXTBOX_BACKGROUND_EDIT_COLOR, |s| &mut s.color.TEXTBOX_BACKGROUND_EDIT_COLOR);
 220 
 221 static ROOT_PLATE_MENUBAR_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_MENUBAR_COLOR, |s| &mut s.color.ROOT_PLATE_MENUBAR_COLOR);
 222 static ROOT_PLATE_MENUBAR_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_MENUBAR_TEXT_COLOR, |s| &mut s.color.ROOT_PLATE_MENUBAR_TEXT_COLOR);
 223 static ROOT_PLATE_MENUBAR_BLUR: crate::style::StyleCell<bool> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_MENUBAR_BLUR, |s| &mut s.color.ROOT_PLATE_MENUBAR_BLUR);
 224 /// Tint strength of frosted menus/popovers over the blurred backdrop:
 225 /// 1.0 is fully opaque (frost invisible), lower shows more content through.
 226 static MENU_OPACITY: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.MENU_OPACITY, |s| &mut s.color.MENU_OPACITY);
 227 /// Backdrop compression of frosted menus/popovers (`style.surface.menu.
 228 /// compression`, 0..1): how hard the blurred content beneath a menu is
 229 /// pulled toward the menu's own key, so the menu holds its legibility over
 230 /// whatever it opens above. Menu-scoped, overriding the DE recipe's
 231 /// `plate.backdrop_compression` for popovers only; a menu is read while
 232 /// something else is going on beneath it, which a pane is not.
 233 static MENU_COMPRESSION: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.MENU_COMPRESSION, |s| &mut s.color.MENU_COMPRESSION);
 234 /// The colour a menu's face is tinted with (`style.surface.menu.color`),
 235 /// when the config names one; `None` follows the root plate colour
 236 /// (`page_low_color`), which is what every menu wore before the key
 237 /// existed. The alpha is ignored — `menu.opacity` is the tint strength —
 238 /// so a menu can be dark on a light window without the window's own
 239 /// plate going dark with it.
 240 static MENU_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.MENU_COLOR, |s| &mut s.color.MENU_COLOR);
 241 
 242 static ROOT_PLATE_STATUSBAR_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_STATUSBAR_COLOR, |s| &mut s.color.ROOT_PLATE_STATUSBAR_COLOR);
 243 static ROOT_PLATE_STATUSBAR_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_STATUSBAR_TEXT_COLOR, |s| &mut s.color.ROOT_PLATE_STATUSBAR_TEXT_COLOR);
 244 static ROOT_PLATE_STATUSBAR_BLUR: crate::style::StyleCell<bool> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_STATUSBAR_BLUR, |s| &mut s.color.ROOT_PLATE_STATUSBAR_BLUR);
 245 static BUTTON_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.BUTTON_BACKGROUND_COLOR, |s| &mut s.color.BUTTON_BACKGROUND_COLOR);
 246 static RAMP_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.RAMP_BACKGROUND_COLOR, |s| &mut s.color.RAMP_BACKGROUND_COLOR);
 247 static RAMP_BORDER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.RAMP_BORDER_COLOR, |s| &mut s.color.RAMP_BORDER_COLOR);
 248 static CONTROL_PANEL_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.CONTROL_PANEL_COLOR, |s| &mut s.color.CONTROL_PANEL_COLOR); // default #13151cff
 249 static CONTROL_PANEL_BORDER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.CONTROL_PANEL_BORDER_COLOR, |s| &mut s.color.CONTROL_PANEL_BORDER_COLOR); // default #292c37ff
 250 
 251 static PROGRESS_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PROGRESS_BG_COLOR, |s| &mut s.color.PROGRESS_BG_COLOR);
 252 static PROGRESS_FILL_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PROGRESS_FILL_COLOR, |s| &mut s.color.PROGRESS_FILL_COLOR);
 253 static SPINBOX_DISPLAY_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SPINBOX_DISPLAY_COLOR, |s| &mut s.color.SPINBOX_DISPLAY_COLOR);
 254 static SPINBOX_BUTTON_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SPINBOX_BUTTON_COLOR, |s| &mut s.color.SPINBOX_BUTTON_COLOR);
 255 static SPINBOX_BUTTON_HOVER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SPINBOX_BUTTON_HOVER_COLOR, |s| &mut s.color.SPINBOX_BUTTON_HOVER_COLOR);
 256 static SPINBOX_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SPINBOX_TEXT_COLOR, |s| &mut s.color.SPINBOX_TEXT_COLOR);
 257 
 258 static BUTTON_BORDER_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.BUTTON_BORDER_COLOR, |s| &mut s.color.BUTTON_BORDER_COLOR);
 259 
 260 static DROPDOWN_BORDER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.DROPDOWN_BORDER_COLOR, |s| &mut s.color.DROPDOWN_BORDER_COLOR);
 261 static DROPDOWN_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.DROPDOWN_TEXT_COLOR, |s| &mut s.color.DROPDOWN_TEXT_COLOR);
 262 
 263 static SLIDER_THUMB_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SLIDER_THUMB_COLOR, |s| &mut s.color.SLIDER_THUMB_COLOR);
 264 static SLIDER_THUMB_DRAG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SLIDER_THUMB_DRAG_COLOR, |s| &mut s.color.SLIDER_THUMB_DRAG_COLOR);
 265 
 266 static RANGE_SLIDER_THUMB_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.RANGE_SLIDER_THUMB_COLOR, |s| &mut s.color.RANGE_SLIDER_THUMB_COLOR);
 267 static RANGE_SLIDER_THUMB_DRAG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.RANGE_SLIDER_THUMB_DRAG_COLOR, |s| &mut s.color.RANGE_SLIDER_THUMB_DRAG_COLOR);
 268 
 269 static TREE_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_BACKGROUND_COLOR, |s| &mut s.color.TREE_BACKGROUND_COLOR);
 270 static TREE_BORDER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_BORDER_COLOR, |s| &mut s.color.TREE_BORDER_COLOR);
 271 static TREE_BORDER_HOVER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_BORDER_HOVER_COLOR, |s| &mut s.color.TREE_BORDER_HOVER_COLOR);
 272 static TREE_BORDER_FOCUS_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_BORDER_FOCUS_COLOR, |s| &mut s.color.TREE_BORDER_FOCUS_COLOR);
 273 static TREE_OPEN_SEARCH_KEY: crate::style::StyleCell<String> = crate::style::StyleCell::new(|s| &s.color.TREE_OPEN_SEARCH_KEY, |s| &mut s.color.TREE_OPEN_SEARCH_KEY);
 274 static LIST_OPEN_SEARCH_KEY: crate::style::StyleCell<String> = crate::style::StyleCell::new(|s| &s.color.LIST_OPEN_SEARCH_KEY, |s| &mut s.color.LIST_OPEN_SEARCH_KEY);
 275 static LIST_CLOSE_SEARCH_KEY: crate::style::StyleCell<String> = crate::style::StyleCell::new(|s| &s.color.LIST_CLOSE_SEARCH_KEY, |s| &mut s.color.LIST_CLOSE_SEARCH_KEY);
 276 
 277 static TREE_SECTION_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_SECTION_BG_COLOR, |s| &mut s.color.TREE_SECTION_BG_COLOR);
 278 static TREE_SECTION_BG_HOVER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_SECTION_BG_HOVER_COLOR, |s| &mut s.color.TREE_SECTION_BG_HOVER_COLOR);
 279 
 280 static TREE_LEAF_BG_EVEN_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_BG_EVEN_COLOR, |s| &mut s.color.TREE_LEAF_BG_EVEN_COLOR);
 281 static TREE_LEAF_BG_ODD_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_BG_ODD_COLOR, |s| &mut s.color.TREE_LEAF_BG_ODD_COLOR);
 282 static TREE_LEAF_BG_HOVER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_BG_HOVER_COLOR, |s| &mut s.color.TREE_LEAF_BG_HOVER_COLOR);
 283 static TREE_LEAF_BG_SELECTED_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_BG_SELECTED_COLOR, |s| &mut s.color.TREE_LEAF_BG_SELECTED_COLOR);
 284 
 285 static TREE_SECTION_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_SECTION_TEXT_COLOR, |s| &mut s.color.TREE_SECTION_TEXT_COLOR);
 286 static TREE_LEAF_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_TEXT_COLOR, |s| &mut s.color.TREE_LEAF_TEXT_COLOR);
 287 static TREE_LEAF_TEXT_SELECTED_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_TEXT_SELECTED_COLOR, |s| &mut s.color.TREE_LEAF_TEXT_SELECTED_COLOR);
 288 
 289 static TREE_TYPE_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_TYPE_TEXT_COLOR, |s| &mut s.color.TREE_TYPE_TEXT_COLOR);
 290 static TREE_VALUE_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_VALUE_TEXT_COLOR, |s| &mut s.color.TREE_VALUE_TEXT_COLOR);
 291 static TREE_SEPARATOR_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_SEPARATOR_COLOR, |s| &mut s.color.TREE_SEPARATOR_COLOR);
 292 
 293 static SCROLLBAR_TRACK_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SCROLLBAR_TRACK_COLOR, |s| &mut s.color.SCROLLBAR_TRACK_COLOR);
 294 static SCROLLBAR_THUMB_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SCROLLBAR_THUMB_COLOR, |s| &mut s.color.SCROLLBAR_THUMB_COLOR);
 295 
 296 static GRAPH_GRID_COLOR: crate::style::StyleCell<[f32; 3]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_GRID_COLOR, |s| &mut s.color.GRAPH_GRID_COLOR);
 297 static GRAPH_OPACITY: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.GRAPH_OPACITY, |s| &mut s.color.GRAPH_OPACITY);
 298 /// Opacity of the graph's NODE-domain content (node bodies, wires, connectors,
 299 /// node text) — `style.surface.graph.node.opacity`, deliberately independent of
 300 /// `GRAPH_OPACITY`, which fades only the pane surface (grid cells/gaps).
 301 static GRAPH_NODE_OPACITY: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.GRAPH_NODE_OPACITY, |s| &mut s.color.GRAPH_NODE_OPACITY);
 302 
 303 static GRAPH_NODE_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_NODE_COLOR, |s| &mut s.color.GRAPH_NODE_COLOR);
 304 static GRAPH_NODE_SELECTED_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_NODE_SELECTED_COLOR, |s| &mut s.color.GRAPH_NODE_SELECTED_COLOR);
 305 static GRAPH_NODE_DRAG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_NODE_DRAG_COLOR, |s| &mut s.color.GRAPH_NODE_DRAG_COLOR);
 306 
 307 static GRAPH_WIRE_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_WIRE_COLOR, |s| &mut s.color.GRAPH_WIRE_COLOR);
 308 static GRAPH_WIRE_HIGHLIGHT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_WIRE_HIGHLIGHT_COLOR, |s| &mut s.color.GRAPH_WIRE_HIGHLIGHT_COLOR);
 309 
 310 static GRAPH_CONNECTOR_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_CONNECTOR_COLOR, |s| &mut s.color.GRAPH_CONNECTOR_COLOR);
 311 static GRAPH_CONNECTOR_HIGHLIGHT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_CONNECTOR_HIGHLIGHT_COLOR, |s| &mut s.color.GRAPH_CONNECTOR_HIGHLIGHT_COLOR);
 312 
 313 pub fn button_background_color() -> [f32; 4] {
 314     style_read(&BUTTON_BACKGROUND_COLOR)
 315 }
 316 
 317 pub fn set_button_background_color(color: [f32; 4]) {
 318     style_write(&BUTTON_BACKGROUND_COLOR, color);
 319 }
 320 
 321 pub fn button_hover_color() -> [f32; 4] {
 322     let base = button_background_color();
 323     let mut oklab = linear_srgb_to_oklab([base[0], base[1], base[2]]);
 324     oklab[0] = (oklab[0] + 0.05).min(1.0); // increase lightness slightly
 325     let rgb = oklab_to_linear_srgb(oklab);
 326     [
 327         rgb[0].clamp(0.0, 1.0),
 328         rgb[1].clamp(0.0, 1.0),
 329         rgb[2].clamp(0.0, 1.0),
 330         (base[3] + 0.20).min(1.0),
 331     ]
 332 }
 333 
 334 pub fn button_press_color() -> [f32; 4] {
 335     let base = button_background_color();
 336     let mut oklab = linear_srgb_to_oklab([base[0], base[1], base[2]]);
 337     oklab[0] = (oklab[0] - 0.07).max(0.0); // decrease lightness
 338     let rgb = oklab_to_linear_srgb(oklab);
 339     [
 340         rgb[0].clamp(0.0, 1.0),
 341         rgb[1].clamp(0.0, 1.0),
 342         rgb[2].clamp(0.0, 1.0),
 343         (base[3] + 0.40).min(1.0),
 344     ]
 345 }
 346 
 347 pub fn node_color() -> [f32; 4] {
 348     load_colors_once();
 349     style_read(&NODE_COLOR)
 350 }
 351 
 352 pub fn set_node_color(color: [f32; 4]) {
 353     style_write(&NODE_COLOR, color);
 354 }
 355 
 356 pub fn node_selected_color() -> [f32; 4] {
 357     load_colors_once();
 358     style_read(&NODE_SELECTED_COLOR)
 359 }
 360 
 361 pub fn set_node_selected_color(color: [f32; 4]) {
 362     style_write(&NODE_SELECTED_COLOR, color);
 363 }
 364 
 365 pub fn node_drag_color() -> [f32; 4] {
 366     load_colors_once();
 367     style_read(&NODE_DRAG_COLOR)
 368 }
 369 
 370 pub fn set_node_drag_color(color: [f32; 4]) {
 371     style_write(&NODE_DRAG_COLOR, color);
 372 }
 373 
 374 pub fn textbox_placeholder_text_color() -> [u8; 3] {
 375     load_colors_once();
 376     style_read(&TEXTBOX_PLACEHOLDER_TEXT_COLOR)
 377 }
 378 
 379 pub fn set_textbox_placeholder_text_color(color: [u8; 3]) {
 380     style_write(&TEXTBOX_PLACEHOLDER_TEXT_COLOR, color);
 381 }
 382 
 383 pub fn textbox_background_color() -> [f32; 4] {
 384     load_colors_once();
 385     style_read(&TEXTBOX_BACKGROUND_COLOR)
 386 }
 387 
 388 pub fn set_textbox_background_color(color: [f32; 4]) {
 389     style_write(&TEXTBOX_BACKGROUND_COLOR, color);
 390 }
 391 
 392 pub fn textbox_background_edit_color() -> [f32; 4] {
 393     load_colors_once();
 394     style_read(&TEXTBOX_BACKGROUND_EDIT_COLOR)
 395 }
 396 
 397 pub fn set_textbox_background_edit_color(color: [f32; 4]) {
 398     style_write(&TEXTBOX_BACKGROUND_EDIT_COLOR, color);
 399 }
 400 
 401 pub fn graph_wire_color() -> [f32; 4] {
 402     load_colors_once();
 403     style_read(&GRAPH_WIRE_COLOR)
 404 }
 405 
 406 pub fn set_graph_wire_color(color: [f32; 4]) {
 407     style_write(&GRAPH_WIRE_COLOR, color);
 408 }
 409 
 410 pub fn graph_wire_highlight_color() -> [f32; 4] {
 411     load_colors_once();
 412     style_read(&GRAPH_WIRE_HIGHLIGHT_COLOR)
 413 }
 414 
 415 pub fn set_graph_wire_highlight_color(color: [f32; 4]) {
 416     style_write(&GRAPH_WIRE_HIGHLIGHT_COLOR, color);
 417 }
 418 
 419 pub fn graph_connector_color() -> [f32; 4] {
 420     load_colors_once();
 421     style_read(&GRAPH_CONNECTOR_COLOR)
 422 }
 423 
 424 pub fn set_graph_connector_color(color: [f32; 4]) {
 425     style_write(&GRAPH_CONNECTOR_COLOR, color);
 426 }
 427 
 428 pub fn graph_connector_highlight_color() -> [f32; 4] {
 429     load_colors_once();
 430     style_read(&GRAPH_CONNECTOR_HIGHLIGHT_COLOR)
 431 }
 432 
 433 pub fn set_graph_connector_highlight_color(color: [f32; 4]) {
 434     style_write(&GRAPH_CONNECTOR_HIGHLIGHT_COLOR, color);
 435 }
 436 
 437 pub fn graph_node_color() -> [f32; 4] {
 438     load_colors_once();
 439     style_read(&GRAPH_NODE_COLOR)
 440 }
 441 
 442 pub fn set_graph_node_color(color: [f32; 4]) {
 443     style_write(&GRAPH_NODE_COLOR, color);
 444 }
 445 
 446 pub fn graph_node_selected_color() -> [f32; 4] {
 447     load_colors_once();
 448     style_read(&GRAPH_NODE_SELECTED_COLOR)
 449 }
 450 
 451 pub fn set_graph_node_selected_color(color: [f32; 4]) {
 452     style_write(&GRAPH_NODE_SELECTED_COLOR, color);
 453 }
 454 
 455 pub fn graph_node_drag_color() -> [f32; 4] {
 456     load_colors_once();
 457     style_read(&GRAPH_NODE_DRAG_COLOR)
 458 }
 459 
 460 pub fn set_graph_node_drag_color(color: [f32; 4]) {
 461     style_write(&GRAPH_NODE_DRAG_COLOR, color);
 462 }
 463 
 464 /// The colour of a graph's grid lines (`style.surface.graph.grid_color`).
 465 pub fn graph_grid_color() -> [f32; 3] {
 466     load_colors_once();
 467     style_read(&GRAPH_GRID_COLOR)
 468 }
 469 
 470 pub fn set_graph_grid_color(color: [f32; 3]) {
 471     style_write(&GRAPH_GRID_COLOR, color);
 472 }
 473 
 474 pub fn graph_opacity() -> f32 {
 475     load_colors_once();
 476     style_read(&GRAPH_OPACITY)
 477 }
 478 
 479 pub fn graph_node_opacity() -> f32 {
 480     load_colors_once();
 481     style_read(&GRAPH_NODE_OPACITY)
 482 }
 483 
 484 pub fn set_graph_node_opacity(opacity: f32) {
 485     style_write(&GRAPH_NODE_OPACITY, opacity);
 486 }
 487 
 488 pub fn set_graph_opacity(opacity: f32) {
 489     style_write(&GRAPH_OPACITY, opacity);
 490 }
 491 
 492 pub fn page_low_color() -> [f32; 4] {
 493     load_colors_once();
 494     let mut color = *PAGE_LOW_COLOR.read().unwrap();
 495     if let Some(opacity) = read_root_plate_opacity_if_configured() {
 496         color[3] = opacity;
 497     }
 498     color
 499 }
 500 
 501 pub fn set_page_low_color(color: [f32; 4]) {
 502     style_write(&PAGE_LOW_COLOR, color);
 503 }
 504 
 505 pub fn page_color() -> [f32; 4] {
 506     load_colors_once();
 507     style_read(&PAGE_COLOR)
 508 }
 509 
 510 pub fn set_page_color(color: [f32; 4]) {
 511     style_write(&PAGE_COLOR, color);
 512 }
 513 
 514 pub fn layer_color() -> [f32; 4] {
 515     load_colors_once();
 516     style_read(&LAYER_COLOR)
 517 }
 518 
 519 pub fn set_layer_color(color: [f32; 4]) {
 520     style_write(&LAYER_COLOR, color);
 521 }
 522 
 523 
 524 pub fn color_borders_color() -> [f32; 4] {
 525     load_colors_once();
 526     style_read(&COLOR_BORDERS_COLOR)
 527 }
 528 
 529 pub fn set_color_borders_color(color: [f32; 4]) {
 530     style_write(&COLOR_BORDERS_COLOR, color);
 531 }
 532 
 533 pub const SLIDER_THUMB: [f32; 4] = [0.60, 0.60, 0.65, 1.0];
 534 pub const SLIDER_THUMB_DRAG: [f32; 4] = [0.80, 0.80, 0.85, 1.0];
 535 pub const PROGRESS_BG: [f32; 4] = [0.18, 0.18, 0.22, 1.0];
 536 pub const PROGRESS_FILL: [f32; 4] = [0.20, 0.50, 0.75, 1.0];
 537 
 538 pub const HIGHLIGHT_PRIMARY: [f32; 4] = [1.0, 1.0, 1.0, 0.12];
 539 pub const HIGHLIGHT_SECONDARY: [f32; 4] = [1.0, 1.0, 1.0, 0.06];
 540 
 541 pub const SPINBOX_BG: [f32; 4] = [0.18, 0.18, 0.22, 1.0];
 542 pub const SPINBOX_BUTTON: [f32; 4] = [0.25, 0.25, 0.32, 1.0];
 543 pub const SPINBOX_BUTTON_HOVER: [f32; 4] = [0.35, 0.35, 0.42, 1.0];
 544 pub const SPINBOX_DISPLAY: [f32; 4] = [0.12, 0.12, 0.16, 1.0];
 545 
 546 pub const CANVAS_BG: [f32; 4] = [0.05, 0.05, 0.10, 1.0];
 547 pub const VIEWPORT_BG: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
 548 pub const PARAM_BG: [f32; 4] = [0.10, 0.10, 0.14, 0.25];
 549 
 550 /// The params pane's plate tint (linear rgba) — [`PARAM_BG`] made live:
 551 /// `style.surface.param.color` in config overrides it, and apps can retint at
 552 /// runtime (the designer's Style section "Plate Color"). Alpha doubles as the
 553 /// frost strength under plate blur.
 554 static PARAM_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PARAM_BG_COLOR, |s| &mut s.color.PARAM_BG_COLOR);
 555 /// Whether the pane tint came in as `style.surface.plate.pane.color` — the
 556 /// spelling whose alpha IS the tint strength — rather than the legacy
 557 /// `style.surface.param.color`, which the top-level `plate_opacity` line
 558 /// still multiplies (`Material::pane_legacy`). Two spellings, one tint.
 559 static PANE_COLOR_WHOLE: crate::style::StyleCell<bool> = crate::style::StyleCell::new(|s| &s.color.PANE_COLOR_WHOLE, |s| &mut s.color.PANE_COLOR_WHOLE);
 560 
 561 pub fn param_bg_color() -> [f32; 4] {
 562     load_colors_once();
 563     style_read(&PARAM_BG_COLOR)
 564 }
 565 
 566 /// Whether the pane tint was spelled `style.surface.plate.pane.color`, in
 567 /// which case its alpha is the whole tint strength and `plate_opacity` does
 568 /// not multiply it. See `PANE_COLOR_WHOLE`.
 569 pub fn pane_color_is_whole() -> bool {
 570     load_colors_once();
 571     style_read(&PANE_COLOR_WHOLE)
 572 }
 573 
 574 pub fn set_param_bg_color(color: [f32; 4]) {
 575     style_write(&PARAM_BG_COLOR, color);
 576 }
 577 
 578 /// The params plate's final fill as the renderer consumes it: the pane rung's
 579 /// material ([`crate::scene::Material::pane`] — the tint at the global plate
 580 /// opacity, frosted under plate blur) encoded for a nested plate. The single
 581 /// source both `ParametersBg`'s own plate and any surface that wants to match
 582 /// it (the designer's node bodies) draw from, so they track a live retint /
 583 /// opacity / blur toggle together.
 584 pub fn param_plate_fill() -> [f32; 4] {
 585     use crate::scene::material::{Material, PlateRole};
 586     Material::pane().fill(PlateRole::Nested)
 587 }
 588 
 589 pub const PANEL_MENU_BG: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
 590 pub const PANEL_MENU_HOVER: [f32; 4] = [0.18, 0.18, 0.25, 1.0];
 591 pub const PANEL_MENU_FOCUSED: [f32; 4] = [0.08, 0.16, 0.28, 1.0];
 592 
 593 pub const SPLITTER_IDLE: [f32; 4] = [0.20, 0.20, 0.27, 1.0];
 594 pub const SPLITTER_HOVER: [f32; 4] = [0.40, 0.40, 0.50, 1.0];
 595 pub const SPLITTER_DRAG: [f32; 4] = [0.50, 0.50, 0.60, 1.0];
 596 
 597 pub const TEXT_FG: [f32; 4] = [0.80, 0.80, 0.85, 1.0];
 598 pub const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
 599 
 600 /// A canvas well's floor: the plate darkened, not a fill of its own, so every
 601 /// opening you look into — Trackpad, Slider2D, the bevel and ramp previews —
 602 /// is cut from the one material ([`crate::scene::paint::PaintCtx::well_floor`]).
 603 pub const WELL_FLOOR: [f32; 4] = [0.0, 0.0, 0.0, 0.18];
 604 /// [`WELL_FLOOR`] risen toward the plate: a clickable canvas's hover cue.
 605 pub const WELL_FLOOR_LIFTED: [f32; 4] = [0.0, 0.0, 0.0, 0.10];
 606 /// The hairline a well is framed with when relief is off, idle; see
 607 /// [`well_frame_color`].
 608 pub const WELL_FRAME: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
 609 /// [`WELL_FRAME`] under the pointer.
 610 pub const WELL_FRAME_HOVER: [f32; 4] = [0.25, 0.25, 0.35, 1.0];
 611 
 612 /// The frame a flat well is drawn in — the one hairline every well (text,
 613 /// keybind, colour and font fields, the canvases) wears when relief is off:
 614 /// neutral greys idle and hovered, the highlight accent while the well is
 615 /// active (editing, recording, pressed) — the colour the relief wells light
 616 /// their rims with, so the two styles share one focus cue. A flat well has no
 617 /// floor of its own any more than a relief one: the plate is the floor.
 618 pub fn well_frame_color(hovered: bool, active: bool) -> [f32; 4] {
 619     if active {
 620         let c = highlight_primary_color();
 621         [c[0], c[1], c[2], 1.0]
 622     } else if hovered {
 623         WELL_FRAME_HOVER
 624     } else {
 625         WELL_FRAME
 626     }
 627 }
 628 pub const TEXT_HEADER: [f32; 4] = [0.90, 0.90, 0.95, 1.0];
 629 pub const TEXT_ACCENT: [f32; 4] = [0.56, 0.83, 0.56, 1.0];
 630 
 631 pub fn read_opacity_if_configured() -> Option<f32> {
 632     load_colors_once();
 633     style_read(&OPACITY)
 634 }
 635 
 636 /// Tint strength for frosted menus/popovers (`/style/surface/menu/opacity`,
 637 /// default 0.8): the |alpha| of the blur-behind sentinel their plates carry.
 638 pub fn menu_opacity() -> f32 {
 639     load_colors_once();
 640     style_read(&MENU_OPACITY)
 641 }
 642 
 643 /// Backdrop compression of frosted menus/popovers
 644 /// (`/style/surface/menu/compression`, default 0.6) — see `MENU_COMPRESSION`.
 645 pub fn menu_compression() -> f32 {
 646     load_colors_once();
 647     style_read(&MENU_COMPRESSION)
 648 }
 649 
 650 pub fn set_menu_compression(c: f32) {
 651     style_write(&MENU_COMPRESSION, c.clamp(0.0, 1.0));
 652 }
 653 
 654 /// The colour a menu's face is tinted with: `style.surface.menu.color` when
 655 /// configured, else the root plate colour (`page_low_color`) — the face
 656 /// every menu wore before the key existed, so an unconfigured menu looks as
 657 /// it always did. Its alpha is not the tint strength; `menu_opacity` is
 658 /// (`Material::popover` reads both).
 659 pub fn menu_color() -> [f32; 4] {
 660     load_colors_once();
 661     style_read(&MENU_COLOR).unwrap_or_else(page_low_color)
 662 }
 663 
 664 pub fn set_menu_color(c: Option<[f32; 4]>) {
 665     style_write(&MENU_COLOR, c);
 666 }
 667 
 668 pub fn read_root_plate_opacity_if_configured() -> Option<f32> {
 669     load_colors_once();
 670     style_read(&ROOT_PLATE_OPACITY)
 671 }
 672 
 673 // The toggle's own palette (enabled/disabled/background) is RETIRED: a toggle
 674 // inherits the plate it sits on and marks state with light and relief, so
 675 // there is nothing left to tint. The `TOGGLE_*` consts above survive for the
 676 // graph's node geometry switch, which is a different control.
 677 
 678 pub fn list_bg_color() -> [f32; 4] {
 679     load_colors_once();
 680     style_read(&LIST_BG_COLOR)
 681 }
 682 
 683 pub fn set_list_bg_color(color: [f32; 4]) {
 684     if let Ok(mut lock) = LIST_BG_COLOR.write() {
 685         *lock = [color[0], color[1], color[2], 0.3];
 686     }
 687 }
 688 
 689 pub fn list_entry_bg_color() -> [f32; 4] {
 690     load_colors_once();
 691     style_read(&LIST_ENTRY_BG_COLOR)
 692 }
 693 
 694 pub fn set_list_entry_bg_color(color: [f32; 4]) {
 695     style_write(&LIST_ENTRY_BG_COLOR, color);
 696 }
 697 
 698 pub fn list_entry_highlight_color() -> [f32; 4] {
 699     load_colors_once();
 700     style_read(&LIST_ENTRY_HIGHLIGHT_COLOR)
 701 }
 702 
 703 pub fn set_list_entry_highlight_color(color: [f32; 4]) {
 704     style_write(&LIST_ENTRY_HIGHLIGHT_COLOR, color);
 705 }
 706 
 707 pub fn list_font_color() -> [f32; 4] {
 708     load_colors_once();
 709     style_read(&LIST_FONT_COLOR)
 710 }
 711 
 712 pub fn set_list_font_color(color: [f32; 4]) {
 713     style_write(&LIST_FONT_COLOR, color);
 714 }
 715 
 716 pub fn breadcrumb_bg_color() -> [f32; 4] {
 717     load_colors_once();
 718     style_read(&BREADCRUMB_BG_COLOR)
 719 }
 720 
 721 pub fn set_breadcrumb_bg_color(color: [f32; 4]) {
 722     if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
 723         *lock = [color[0], color[1], color[2], 1.0];
 724     }
 725 }
 726 
 727 pub fn popover_bg_color() -> [f32; 4] {
 728     load_colors_once();
 729     style_read(&POPOVER_BG_COLOR)
 730 }
 731 
 732 pub fn set_popover_bg_color(color: [f32; 4]) {
 733     if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
 734         *lock = [color[0], color[1], color[2], 1.0];
 735     }
 736 }
 737 
 738 /// Corner radius of the root plate (`style.surface.plate.root.corner_radius`).
 739 /// The window silhouette value — the compositor clips windows from the SHARED
 740 /// copy of this.
 741 pub fn root_plate_corner_radius() -> f32 {
 742     load_colors_once();
 743     style_read(&ROOT_PLATE_CORNER_RADIUS)
 744 }
 745 
 746 pub fn ramp_background_color() -> [f32; 4] {
 747     load_colors_once();
 748     style_read(&RAMP_BACKGROUND_COLOR)
 749 }
 750 
 751 pub fn ramp_border_color() -> [f32; 4] {
 752     load_colors_once();
 753     style_read(&RAMP_BORDER_COLOR)
 754 }
 755 
 756 pub fn control_panel_color() -> [f32; 4] {
 757     load_colors_once();
 758     style_read(&CONTROL_PANEL_COLOR)
 759 }
 760 
 761 pub fn set_control_panel_color(color: [f32; 4]) {
 762     style_write(&CONTROL_PANEL_COLOR, color);
 763 }
 764 
 765 pub fn control_panel_border_color() -> [f32; 4] {
 766     load_colors_once();
 767     style_read(&CONTROL_PANEL_BORDER_COLOR)
 768 }
 769 
 770 pub fn set_control_panel_border_color(color: [f32; 4]) {
 771     style_write(&CONTROL_PANEL_BORDER_COLOR, color);
 772 }
 773 
 774 pub fn progress_bg() -> [f32; 4] {
 775     load_colors_once();
 776     style_read(&PROGRESS_BG_COLOR)
 777 }
 778 
 779 pub fn set_progress_bg(color: [f32; 4]) {
 780     style_write(&PROGRESS_BG_COLOR, color);
 781 }
 782 
 783 pub fn progress_fill() -> [f32; 4] {
 784     load_colors_once();
 785     style_read(&PROGRESS_FILL_COLOR)
 786 }
 787 
 788 pub fn set_progress_fill(color: [f32; 4]) {
 789     style_write(&PROGRESS_FILL_COLOR, color);
 790 }
 791 
 792 pub fn button_border_color() -> Option<[f32; 4]> {
 793     load_colors_once();
 794     style_read(&BUTTON_BORDER_COLOR)
 795 }
 796 
 797 pub fn set_button_border_color(color: [f32; 4]) {
 798     if let Ok(mut lock) = BUTTON_BORDER_COLOR.write() {
 799         *lock = Some(color);
 800     }
 801 }
 802 
 803 pub fn dropdown_border_color() -> [f32; 4] {
 804     load_colors_once();
 805     style_read(&DROPDOWN_BORDER_COLOR)
 806 }
 807 
 808 pub fn set_dropdown_border_color(color: [f32; 4]) {
 809     style_write(&DROPDOWN_BORDER_COLOR, color);
 810 }
 811 
 812 pub fn dropdown_text_color() -> [f32; 4] {
 813     load_colors_once();
 814     style_read(&DROPDOWN_TEXT_COLOR)
 815 }
 816 
 817 pub fn set_dropdown_text_color(color: [f32; 4]) {
 818     style_write(&DROPDOWN_TEXT_COLOR, color);
 819 }
 820 
 821 
 822 pub fn slider_thumb() -> [f32; 4] {
 823     load_colors_once();
 824     style_read(&SLIDER_THUMB_COLOR)
 825 }
 826 
 827 pub fn set_slider_thumb(color: [f32; 4]) {
 828     style_write(&SLIDER_THUMB_COLOR, color);
 829 }
 830 
 831 pub fn slider_thumb_drag() -> [f32; 4] {
 832     load_colors_once();
 833     style_read(&SLIDER_THUMB_DRAG_COLOR)
 834 }
 835 
 836 pub fn set_slider_thumb_drag(color: [f32; 4]) {
 837     style_write(&SLIDER_THUMB_DRAG_COLOR, color);
 838 }
 839 
 840 pub fn rangeslider_thumb() -> [f32; 4] {
 841     load_colors_once();
 842     style_read(&RANGE_SLIDER_THUMB_COLOR)
 843 }
 844 
 845 pub fn set_rangeslider_thumb(color: [f32; 4]) {
 846     style_write(&RANGE_SLIDER_THUMB_COLOR, color);
 847 }
 848 
 849 pub fn rangeslider_thumb_drag() -> [f32; 4] {
 850     load_colors_once();
 851     style_read(&RANGE_SLIDER_THUMB_DRAG_COLOR)
 852 }
 853 
 854 pub fn set_rangeslider_thumb_drag(color: [f32; 4]) {
 855     style_write(&RANGE_SLIDER_THUMB_DRAG_COLOR, color);
 856 }
 857 
 858 pub fn spinbox_display() -> [f32; 4] {
 859     load_colors_once();
 860     style_read(&SPINBOX_DISPLAY_COLOR)
 861 }
 862 
 863 pub fn set_spinbox_display(color: [f32; 4]) {
 864     style_write(&SPINBOX_DISPLAY_COLOR, color);
 865 }
 866 
 867 pub fn spinbox_button() -> [f32; 4] {
 868     load_colors_once();
 869     style_read(&SPINBOX_BUTTON_COLOR)
 870 }
 871 
 872 pub fn set_spinbox_button(color: [f32; 4]) {
 873     style_write(&SPINBOX_BUTTON_COLOR, color);
 874 }
 875 
 876 pub fn spinbox_button_hover() -> [f32; 4] {
 877     load_colors_once();
 878     style_read(&SPINBOX_BUTTON_HOVER_COLOR)
 879 }
 880 
 881 pub fn set_spinbox_button_hover(color: [f32; 4]) {
 882     style_write(&SPINBOX_BUTTON_HOVER_COLOR, color);
 883 }
 884 
 885 pub fn spinbox_text_color() -> [f32; 4] {
 886     load_colors_once();
 887     style_read(&SPINBOX_TEXT_COLOR)
 888 }
 889 
 890 pub fn set_spinbox_text_color(color: [f32; 4]) {
 891     style_write(&SPINBOX_TEXT_COLOR, color);
 892 }
 893 
 894 pub fn set_root_plate_corner_radius(radius: f32) {
 895     style_write(&ROOT_PLATE_CORNER_RADIUS, radius);
 896 }
 897 
 898 #[derive(Debug, Clone, Copy)]
 899 pub struct Theme {
 900     pub surface_bg: [f32; 4],
 901     pub surface_border: [f32; 4],
 902     pub primary_accent: [f32; 4],
 903     pub press_overlay: [f32; 4],
 904     pub hover_overlay: [f32; 4],
 905 }
 906 
 907 pub fn active_theme() -> Theme {
 908     Theme {
 909         surface_bg: popover_bg_color(),
 910         surface_border: [0.25, 0.25, 0.35, 0.8],
 911         primary_accent: [0.20, 0.50, 0.75, 1.0],
 912         press_overlay: [1.0, 1.0, 1.0, 0.15],
 913         hover_overlay: [1.0, 1.0, 1.0, 0.08],
 914     }
 915 }
 916 
 917 pub fn active_window_mode() -> String {
 918     let app_id = crate::scale::app_id();
 919     if app_id.is_empty() {
 920         return "floating".to_string();
 921     }
 922 
 923     let config_path = crate::config::get_config_path();
 924     let content = match std::fs::read_to_string(&config_path) {
 925         Ok(c) => c,
 926         Err(_) => return "floating".to_string(),
 927     };
 928     let val = crate::config::parse_kdl_to_json(&content);
 929 
 930     if let Some(mode_rules) = val.get("mode_rule").and_then(|r| r.as_array()) {
 931         for rule in mode_rules {
 932             if rule.get("app_id").and_then(|id| id.as_str()) == Some(&app_id) {
 933                 if let Some(mode) = rule.get("mode").and_then(|m| m.as_str()) {
 934                     return mode.to_string();
 935                 }
 936             }
 937         }
 938     }
 939 
 940     let mut default_tile_mode = "cascade".to_string();
 941     if let Some(tag_layouts) = val.get("tag_layout").and_then(|l| l.as_array()) {
 942         for tl in tag_layouts {
 943             if tl.get("tag").and_then(|t| t.as_u64()) == Some(1) {
 944                 if let Some(m) = tl.get("mode").and_then(|m| m.as_str()) {
 945                     default_tile_mode = m.to_string();
 946                 }
 947             }
 948         }
 949     }
 950 
 951     if crate::scale::is_fullscreen() {
 952         return "fullscreen".to_string();
 953     }
 954     if crate::scale::is_maximized() {
 955         return default_tile_mode;
 956     }
 957 
 958     "floating".to_string()
 959 }
 960 
 961 /// Opacity of the root plate's fill (the alpha of the root-plate color).
 962 pub fn root_plate_opacity() -> f32 {
 963     page_low_color()[3]
 964 }
 965 
 966 pub fn tree_background_color() -> [f32; 4] {
 967     style_read(&TREE_BACKGROUND_COLOR)
 968 }
 969 pub fn tree_border_color() -> [f32; 4] {
 970     style_read(&TREE_BORDER_COLOR)
 971 }
 972 pub fn tree_border_hover_color() -> [f32; 4] {
 973     style_read(&TREE_BORDER_HOVER_COLOR)
 974 }
 975 pub fn tree_border_focus_color() -> [f32; 4] {
 976     style_read(&TREE_BORDER_FOCUS_COLOR)
 977 }
 978 
 979 pub fn tree_section_bg_color() -> [f32; 4] {
 980     style_read(&TREE_SECTION_BG_COLOR)
 981 }
 982 pub fn tree_section_bg_hover_color() -> [f32; 4] {
 983     style_read(&TREE_SECTION_BG_HOVER_COLOR)
 984 }
 985 
 986 pub fn tree_leaf_bg_even_color() -> [f32; 4] {
 987     style_read(&TREE_LEAF_BG_EVEN_COLOR)
 988 }
 989 pub fn tree_leaf_bg_odd_color() -> [f32; 4] {
 990     style_read(&TREE_LEAF_BG_ODD_COLOR)
 991 }
 992 pub fn tree_leaf_bg_hover_color() -> [f32; 4] {
 993     style_read(&TREE_LEAF_BG_HOVER_COLOR)
 994 }
 995 pub fn tree_leaf_bg_selected_color() -> [f32; 4] {
 996     style_read(&TREE_LEAF_BG_SELECTED_COLOR)
 997 }
 998 
 999 pub fn tree_section_text_color() -> [f32; 4] {
1000     style_read(&TREE_SECTION_TEXT_COLOR)
1001 }
1002 pub fn tree_leaf_text_color() -> [f32; 4] {
1003     style_read(&TREE_LEAF_TEXT_COLOR)
1004 }
1005 pub fn tree_leaf_text_selected_color() -> [f32; 4] {
1006     style_read(&TREE_LEAF_TEXT_SELECTED_COLOR)
1007 }
1008 
1009 pub fn tree_type_text_color() -> [f32; 4] {
1010     style_read(&TREE_TYPE_TEXT_COLOR)
1011 }
1012 pub fn tree_value_text_color() -> [f32; 4] {
1013     style_read(&TREE_VALUE_TEXT_COLOR)
1014 }
1015 pub fn tree_separator_color() -> [f32; 4] {
1016     style_read(&TREE_SEPARATOR_COLOR)
1017 }
1018 
1019 pub fn set_tree_background_color(c: [f32; 4]) {
1020     style_write(&TREE_BACKGROUND_COLOR, c);
1021 }
1022 pub fn set_tree_border_color(c: [f32; 4]) {
1023     style_write(&TREE_BORDER_COLOR, c);
1024 }
1025 pub fn set_tree_border_hover_color(c: [f32; 4]) {
1026     style_write(&TREE_BORDER_HOVER_COLOR, c);
1027 }
1028 pub fn set_tree_border_focus_color(c: [f32; 4]) {
1029     style_write(&TREE_BORDER_FOCUS_COLOR, c);
1030 }
1031 
1032 pub fn set_tree_section_bg_color(c: [f32; 4]) {
1033     style_write(&TREE_SECTION_BG_COLOR, c);
1034 }
1035 pub fn set_tree_section_bg_hover_color(c: [f32; 4]) {
1036     style_write(&TREE_SECTION_BG_HOVER_COLOR, c);
1037 }
1038 
1039 pub fn set_tree_leaf_bg_even_color(c: [f32; 4]) {
1040     style_write(&TREE_LEAF_BG_EVEN_COLOR, c);
1041 }
1042 pub fn set_tree_leaf_bg_odd_color(c: [f32; 4]) {
1043     style_write(&TREE_LEAF_BG_ODD_COLOR, c);
1044 }
1045 pub fn set_tree_leaf_bg_hover_color(c: [f32; 4]) {
1046     style_write(&TREE_LEAF_BG_HOVER_COLOR, c);
1047 }
1048 pub fn set_tree_leaf_bg_selected_color(c: [f32; 4]) {
1049     style_write(&TREE_LEAF_BG_SELECTED_COLOR, c);
1050 }
1051 
1052 pub fn set_tree_section_text_color(c: [f32; 4]) {
1053     style_write(&TREE_SECTION_TEXT_COLOR, c);
1054 }
1055 pub fn set_tree_leaf_text_color(c: [f32; 4]) {
1056     style_write(&TREE_LEAF_TEXT_COLOR, c);
1057 }
1058 pub fn set_tree_leaf_text_selected_color(c: [f32; 4]) {
1059     style_write(&TREE_LEAF_TEXT_SELECTED_COLOR, c);
1060 }
1061 
1062 pub fn set_tree_type_text_color(c: [f32; 4]) {
1063     style_write(&TREE_TYPE_TEXT_COLOR, c);
1064 }
1065 pub fn set_tree_value_text_color(c: [f32; 4]) {
1066     style_write(&TREE_VALUE_TEXT_COLOR, c);
1067 }
1068 pub fn set_tree_separator_color(c: [f32; 4]) {
1069     style_write(&TREE_SEPARATOR_COLOR, c);
1070 }
1071 
1072 pub fn scrollbar_track_color() -> [f32; 4] {
1073     style_read(&SCROLLBAR_TRACK_COLOR)
1074 }
1075 pub fn set_scrollbar_track_color(c: [f32; 4]) {
1076     style_write(&SCROLLBAR_TRACK_COLOR, c);
1077 }
1078 pub fn scrollbar_thumb_color() -> [f32; 4] {
1079     style_read(&SCROLLBAR_THUMB_COLOR)
1080 }
1081 pub fn set_scrollbar_thumb_color(c: [f32; 4]) {
1082     style_write(&SCROLLBAR_THUMB_COLOR, c);
1083 }
1084 
1085 pub fn root_plate_menubar_color() -> [f32; 4] {
1086     load_colors_once();
1087     style_read(&ROOT_PLATE_MENUBAR_COLOR)
1088 }
1089 
1090 pub fn set_root_plate_menubar_color(c: [f32; 4]) {
1091     style_write(&ROOT_PLATE_MENUBAR_COLOR, c);
1092 }
1093 
1094 pub fn root_plate_menubar_text_color() -> [f32; 4] {
1095     load_colors_once();
1096     style_read(&ROOT_PLATE_MENUBAR_TEXT_COLOR)
1097 }
1098 
1099 pub fn set_root_plate_menubar_text_color(c: [f32; 4]) {
1100     style_write(&ROOT_PLATE_MENUBAR_TEXT_COLOR, c);
1101 }
1102 
1103 pub fn root_plate_menubar_blur() -> bool {
1104     load_colors_once();
1105     style_read(&ROOT_PLATE_MENUBAR_BLUR)
1106 }
1107 
1108 pub fn set_root_plate_menubar_blur(b: bool) {
1109     style_write(&ROOT_PLATE_MENUBAR_BLUR, b);
1110 }
1111 
1112 pub fn root_plate_statusbar_color() -> [f32; 4] {
1113     load_colors_once();
1114     style_read(&ROOT_PLATE_STATUSBAR_COLOR)
1115 }
1116 
1117 pub fn set_root_plate_statusbar_color(c: [f32; 4]) {
1118     style_write(&ROOT_PLATE_STATUSBAR_COLOR, c);
1119 }
1120 
1121 pub fn root_plate_statusbar_text_color() -> [f32; 4] {
1122     load_colors_once();
1123     style_read(&ROOT_PLATE_STATUSBAR_TEXT_COLOR)
1124 }
1125 
1126 pub fn set_root_plate_statusbar_text_color(c: [f32; 4]) {
1127     style_write(&ROOT_PLATE_STATUSBAR_TEXT_COLOR, c);
1128 }
1129 
1130 pub fn root_plate_statusbar_blur() -> bool {
1131     load_colors_once();
1132     style_read(&ROOT_PLATE_STATUSBAR_BLUR)
1133 }
1134 
1135 pub fn set_root_plate_statusbar_blur(b: bool) {
1136     style_write(&ROOT_PLATE_STATUSBAR_BLUR, b);
1137 }
1138 
1139 static PLATE_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.PLATE_COLOR, |s| &mut s.color.PLATE_COLOR);
1140 static PLATE_BORDER_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.PLATE_BORDER_COLOR, |s| &mut s.color.PLATE_BORDER_COLOR);
1141 static PLATE_BORDER_THICKNESS: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.PLATE_BORDER_THICKNESS, |s| &mut s.color.PLATE_BORDER_THICKNESS);
1142 pub fn plate_color() -> Option<[f32; 4]> {
1143     load_colors_once();
1144     style_read(&PLATE_COLOR)
1145 }
1146 
1147 pub fn set_plate_color(c: Option<[f32; 4]>) {
1148     style_write(&PLATE_COLOR, c);
1149 }
1150 
1151 pub fn plate_border_color() -> Option<[f32; 4]> {
1152     load_colors_once();
1153     style_read(&PLATE_BORDER_COLOR)
1154 }
1155 
1156 pub fn set_plate_border_color(c: Option<[f32; 4]>) {
1157     style_write(&PLATE_BORDER_COLOR, c);
1158 }
1159 
1160 pub fn plate_border_thickness() -> f32 {
1161     load_colors_once();
1162     style_read(&PLATE_BORDER_THICKNESS)
1163 }
1164 
1165 pub fn set_plate_border_thickness(t: f32) {
1166     style_write(&PLATE_BORDER_THICKNESS, t);
1167 }
1168 
1169 /// Roll width of a beveled pane plate (the control_relief replacement for the
1170 /// flat border line), in logical px.
1171 ///
1172 /// **This is `style.surface.relief.width`** — the one roll width, the same
1173 /// number the root plate rolls over, every control wall runs, and
1174 /// `relief.edge.height` is a rise against. Until 2026-09-28 it was a second
1175 /// width of its own (`style.surface.plate.bevel_width`, default 6 against the
1176 /// relief's 9.3), so a `PlateSpec` pane plate and an `append_widget_plate`
1177 /// pane plate rolled over different widths in one window, the designer's
1178 /// panes could not be made to match its own window lip by editing one key,
1179 /// and the edge height was expressed against a width the panes did not use.
1180 /// The old key was an explicit override for the rest of that day and is
1181 /// RETIRED: a config still carrying it is reported by path
1182 /// (`retired_surface_keys`) and the key is not read. The getter survives
1183 /// as the name the plate painters call, so a caller need not know which
1184 /// registry key a roll is.
1185 pub fn plate_bevel_width() -> f32 {
1186     crate::layout::bevel_width()
1187 }
1188 
1189 /// How hard a frosted plate pulls its backdrop's LUMINANCE toward its own key
1190 /// before tinting: 0 = the backdrop passes through untouched, 1 = flat.
1191 ///
1192 /// This is the plate's legibility control, and it is NOT opacity. Blur
1193 /// destroys a backdrop's spatial detail but preserves its mean luminance, and
1194 /// text contrast is a mean-luminance property — so a frosted plate over
1195 /// something bright washes out however hard it is blurred, which is the whole
1196 /// of the "liquid glass" legibility problem. Compression remaps the backdrop's
1197 /// luminance toward the plate's own, symmetrically: a bright backdrop comes
1198 /// down and a DARK one comes up, so the plate stops swinging through the ink's
1199 /// luminance while its hue, chroma and movement still read.
1200 ///
1201 /// Default 0.0 — the behavior every existing config already has.
1202 static PLATE_BACKDROP_COMPRESSION: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.PLATE_BACKDROP_COMPRESSION, |s| &mut s.color.PLATE_BACKDROP_COMPRESSION);
1203 
1204 pub fn plate_backdrop_compression() -> f32 {
1205     load_colors_once();
1206     style_read(&PLATE_BACKDROP_COMPRESSION)
1207 }
1208 
1209 pub fn set_plate_backdrop_compression(c: f32) {
1210     style_write(&PLATE_BACKDROP_COMPRESSION, c.clamp(0.0, 1.0));
1211 }
1212 
1213 /// How far a frosted plate's roll bends what it samples, and how much clearer
1214 /// its rim is than its frosted body: 0 = a flat window, 1 = full.
1215 ///
1216 /// This buys no legibility and is not meant to — see
1217 /// [`plate_backdrop_compression`] for that. What it buys is the plate reading
1218 /// as an OBJECT: a curved edge displaces the view through it, so the
1219 /// silhouette stops being where the haze ends and becomes where a slab with a
1220 /// thickness begins. The two are complementary, and on a dark desktop
1221 /// especially: compression flattens the body toward the tint, which leaves the
1222 /// rim as the only place the material can still say what it is.
1223 ///
1224 /// Default 0.0 — no existing config changes appearance.
1225 static PLATE_REFRACTION: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.PLATE_REFRACTION, |s| &mut s.color.PLATE_REFRACTION);
1226 
1227 pub fn plate_refraction() -> f32 {
1228     load_colors_once();
1229     style_read(&PLATE_REFRACTION)
1230 }
1231 
1232 pub fn set_plate_refraction(r: f32) {
1233     style_write(&PLATE_REFRACTION, r.clamp(0.0, 1.0));
1234 }
1235 
1236 /// Whether the default plate material is frosted at all — the switch on
1237 /// `Frost::from_style`. Spelled `style.surface.plate.frost` (a block, the
1238 /// knobs inside it; see the loader). The older `style.surface.plate.blur`
1239 /// bool is retired and reported, not read.
1240 static PLATE_BLUR: crate::style::StyleCell<bool> = crate::style::StyleCell::new(|s| &s.color.PLATE_BLUR, |s| &mut s.color.PLATE_BLUR);
1241 
1242 /// The finish's three fixed terms — specular strength, shininess exponent,
1243 /// curvature/AO strength — as `scene::material::Finish::from_style` reads
1244 /// them. Until 2026-09-20 these were literals in the finish constructor
1245 /// (0.4 / 24 / 0.2); the getters exist so the DE's plastic can be edited and
1246 /// so a named material (RFC material, step 4) has somewhere to land. No config
1247 /// path yet: the defaults ARE the shipped look.
1248 static FINISH_SPEC: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.FINISH_SPEC, |s| &mut s.color.FINISH_SPEC);
1249 static FINISH_SHININESS: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.FINISH_SHININESS, |s| &mut s.color.FINISH_SHININESS);
1250 static FINISH_CURVATURE: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.FINISH_CURVATURE, |s| &mut s.color.FINISH_CURVATURE);
1251 /// The finish a config without `relief spec` / `shininess` / `curvature`
1252 /// gets: the literals the shader shipped with.
1253 pub(crate) const FINISH_SPEC_DEFAULT: f32 = 0.4;
1254 pub(crate) const FINISH_SHININESS_DEFAULT: f32 = 24.0;
1255 pub(crate) const FINISH_CURVATURE_DEFAULT: f32 = 0.2;
1256 
1257 /// The default material's blur radius (`style.surface.plate.frost.radius`,
1258 /// the kernel sigma in logical px) — [`crate::scene::Frost::DEFAULT_RADIUS`]
1259 /// unless config says otherwise.
1260 static PLATE_FROST_RADIUS: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.PLATE_FROST_RADIUS, |s| &mut s.color.PLATE_FROST_RADIUS);
1261 
1262 pub fn plate_frost_radius() -> f32 {
1263     load_colors_once();
1264     style_read(&PLATE_FROST_RADIUS)
1265 }
1266 pub fn set_plate_frost_radius(r: f32) {
1267     style_write(&PLATE_FROST_RADIUS, r.max(0.0));
1268 }
1269 
1270 pub fn finish_spec() -> f32 {
1271     style_read(&FINISH_SPEC)
1272 }
1273 pub fn set_finish_spec(v: f32) {
1274     style_write(&FINISH_SPEC, v.max(0.0));
1275 }
1276 pub fn finish_shininess() -> f32 {
1277     style_read(&FINISH_SHININESS)
1278 }
1279 pub fn set_finish_shininess(v: f32) {
1280     style_write(&FINISH_SHININESS, v.max(1.0));
1281 }
1282 pub fn finish_curvature() -> f32 {
1283     style_read(&FINISH_CURVATURE)
1284 }
1285 pub fn set_finish_curvature(v: f32) {
1286     style_write(&FINISH_CURVATURE, v.max(0.0));
1287 }
1288 
1289 pub fn plate_blur() -> bool {
1290     load_colors_once();
1291     style_read(&PLATE_BLUR)
1292 }
1293 
1294 pub fn set_plate_blur(b: bool) {
1295     style_write(&PLATE_BLUR, b);
1296 }
1297 
1298 pub fn control_label_color() -> [f32; 4] {
1299     style_read(&CONTROL_LABEL_COLOR)
1300 }
1301 
1302 pub fn control_label_color_u8() -> [u8; 3] {
1303     let c = control_label_color();
1304     [
1305         (linear_to_srgb(c[0]) * 255.0).round() as u8,
1306         (linear_to_srgb(c[1]) * 255.0).round() as u8,
1307         (linear_to_srgb(c[2]) * 255.0).round() as u8,
1308     ]
1309 }
1310 
1311 pub fn set_control_label_color(color: [f32; 4]) {
1312     style_write(&CONTROL_LABEL_COLOR, color);
1313 }
1314 
1315 pub fn control_label_hover_color() -> Option<[f32; 4]> {
1316     style_read(&CONTROL_LABEL_HOVER_COLOR)
1317 }
1318 
1319 pub fn control_label_focus_color() -> Option<[f32; 4]> {
1320     style_read(&CONTROL_LABEL_FOCUS_COLOR)
1321 }
1322 
1323 pub fn control_label_color_for_state(hovered: bool, focused: bool) -> [u8; 3] {
1324     let c = if focused {
1325         control_label_focus_color().unwrap_or_else(control_label_color)
1326     } else if hovered {
1327         control_label_hover_color().unwrap_or_else(control_label_color)
1328     } else {
1329         control_label_color()
1330     };
1331     [
1332         (linear_to_srgb(c[0]) * 255.0).round() as u8,
1333         (linear_to_srgb(c[1]) * 255.0).round() as u8,
1334         (linear_to_srgb(c[2]) * 255.0).round() as u8,
1335     ]
1336 }
1337 
1338 pub fn control_label_color_detached() -> [f32; 4] {
1339     style_read(&CONTROL_LABEL_COLOR_DETACHED)
1340 }
1341 
1342 pub fn control_label_color_detached_u8() -> [u8; 3] {
1343     let c = control_label_color_detached();
1344     [
1345         (linear_to_srgb(c[0]) * 255.0).round() as u8,
1346         (linear_to_srgb(c[1]) * 255.0).round() as u8,
1347         (linear_to_srgb(c[2]) * 255.0).round() as u8,
1348     ]
1349 }
1350 
1351 pub fn set_control_label_color_detached(color: [f32; 4]) {
1352     style_write(&CONTROL_LABEL_COLOR_DETACHED, color);
1353 }
1354 
1355 pub fn control_label_color_detached_for_state(hovered: bool, focused: bool) -> [u8; 3] {
1356     let c = if focused {
1357         control_label_focus_color().unwrap_or_else(control_label_color_detached)
1358     } else if hovered {
1359         control_label_hover_color().unwrap_or_else(control_label_color_detached)
1360     } else {
1361         control_label_color_detached()
1362     };
1363     [
1364         (linear_to_srgb(c[0]) * 255.0).round() as u8,
1365         (linear_to_srgb(c[1]) * 255.0).round() as u8,
1366         (linear_to_srgb(c[2]) * 255.0).round() as u8,
1367     ]
1368 }
1369 
1370 #[cfg(test)]
1371 mod color_tests {
1372     use super::*;
1373 
1374     #[test]
1375     fn test_parse_hex_rgba() {
1376         // 6-digit → alpha 1.0, raw sRGB
1377         assert_eq!(parse_hex_rgba("#ff0000"), Some([1.0, 0.0, 0.0, 1.0]));
1378         // 8-digit → explicit alpha
1379         assert_eq!(parse_hex_rgba("#00ff0080"), Some([0.0, 1.0, 0.0, 128.0 / 255.0]));
1380         // leading '#' optional; quotes/whitespace tolerated
1381         assert_eq!(parse_hex_rgba("\" ffffff \""), Some([1.0, 1.0, 1.0, 1.0]));
1382         assert_eq!(parse_hex_rgba("000000"), Some([0.0, 0.0, 0.0, 1.0]));
1383         // invalid
1384         assert_eq!(parse_hex_rgba("#fff"), None);
1385         assert_eq!(parse_hex_rgba("nothex"), None);
1386         assert_eq!(parse_hex_rgba(""), None);
1387         // rgb drops alpha; linear applies gamma to rgb only
1388         assert_eq!(parse_hex_rgb("#ff0000"), Some([1.0, 0.0, 0.0]));
1389         assert_eq!(parse_hex_rgba_linear("#000000ff"), Some([0.0, 0.0, 0.0, 1.0]));
1390         // byte primitive
1391         assert_eq!(parse_hex_bytes("#010203"), Some([1, 2, 3, 255]));
1392         assert_eq!(parse_hex_bytes("#01020304"), Some([1, 2, 3, 4]));
1393         assert_eq!(parse_hex_bytes("#fff"), None);
1394     }
1395 
1396     #[test]
1397     fn perceptual_fade_alpha_walks_lightness_not_luminance() {
1398         let overlay = [0.62, 0.70, 0.95];
1399         let backdrop = [0.028, 0.028, 0.041];
1400         let a = |t: f32| perceptual_fade_alpha(t, overlay, backdrop, 1.0);
1401 
1402         // Endpoints are the plain ones, and the ramp only ever falls.
1403         assert!((a(0.0) - 1.0).abs() < 1e-4);
1404         assert!(a(1.0).abs() < 1e-4);
1405         for i in 1..=20 {
1406             assert!(a(i as f32 / 20.0) <= a((i - 1) as f32 / 20.0));
1407         }
1408 
1409         // The correction runs BELOW the straight ramp — that ramp's excess brightness
1410         // through the middle is the bow the eye reads as non-linear.
1411         assert!(a(0.5) < 0.5 - 0.1, "midpoint {} should sit well under 0.5", a(0.5));
1412 
1413         // What it buys: composited lightness lands on a straight line. cbrt(luminance) is
1414         // the same proxy the implementation uses, checked here end to end through blending.
1415         let lum = |c: [f32; 3]| 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
1416         let l_at = |t: f32| {
1417             let a = a(t);
1418             let y: f32 = a * lum(overlay) + (1.0 - a) * lum(backdrop);
1419             y.cbrt()
1420         };
1421         let (top, bottom) = (l_at(0.0), l_at(1.0));
1422         for i in 0..=10 {
1423             let t = i as f32 / 10.0;
1424             let ideal = top + (bottom - top) * t;
1425             assert!((l_at(t) - ideal).abs() < 1e-3, "t={t}: {} vs {ideal}", l_at(t));
1426         }
1427 
1428         // No lightness contrast to shape: falls back to the straight ramp.
1429         let flat = perceptual_fade_alpha(0.5, overlay, overlay, 1.0);
1430         assert!((flat - 0.5).abs() < 1e-4);
1431     }
1432 
1433     #[test]
1434     fn test_print_active_config() {
1435         let path = crate::config::get_config_path();
1436         println!("ACTIVE CONFIG PATH: {:?}", path);
1437         if let Ok(content) = std::fs::read_to_string(&path) {
1438             println!("FILE READ OK! Length: {}", content.len());
1439             let val = crate::config::parse_kdl_to_json(&content);
1440             println!("PARSED JSON POINTER: {:?}", val.pointer("/style/control/dropdown/color"));
1441         } else {
1442             println!("FILE READ FAILED!");
1443         }
1444         println!("DROPDOWN COLOR GETTER: {:?}", dropdown_background_color());
1445         println!("LIST FONT COLOR GETTER: {:?}", list_font_color());
1446         println!("PLATE COLOR: {:?}", plate_color());
1447         println!("PLATE BORDER COLOR: {:?}", plate_border_color());
1448         println!("PLATE BORDER THICKNESS: {:?}", plate_border_thickness());
1449         println!("PLATE BLUR: {:?}", plate_blur());
1450     }
1451 }
1452 
1453 
1454 #[cfg(test)]
1455 mod tests {
1456     use super::*;
1457 
1458     /// The hex that pins a built-in colour is NOT its floats times 255.
1459     ///
1460     /// The style loader gamma-decodes every config hex; the constants in this
1461     /// file are already linear. So `PARAM_BG = [0.10, 0.10, 0.14]` is spelled
1462     /// `#595969` in config, and the naive `#1a1a24` decodes to a plate ten
1463     /// times darker — silently, both being valid config. Pinned here because
1464     /// it cost a measurement round: a sweep meant to hold the tint constant
1465     /// was quietly sweeping it, and the two halves disagreed by 3x.
1466     #[test]
1467     fn the_hex_that_pins_a_default_round_trips_through_the_gamma_decode() {
1468         let decoded = parse_hex_rgba_linear("#595969").expect("valid hex");
1469         // RGB only — a 6-digit hex carries no alpha, which the asserts at the
1470         // bottom cover as its own hazard.
1471         for (got, want) in decoded.iter().take(3).zip(PARAM_BG.iter().take(3)) {
1472             assert!(
1473                 (got - want).abs() < 0.005,
1474                 "#595969 decodes to {decoded:?}, PARAM_BG is {PARAM_BG:?}"
1475             );
1476         }
1477 
1478         // The naive spelling, and how far off it lands.
1479         let naive = parse_hex_rgba_linear("#1a1a24").expect("valid hex");
1480         assert!(
1481             naive[0] < PARAM_BG[0] / 5.0,
1482             "#1a1a24 was supposed to be nowhere near PARAM_BG, got {naive:?}"
1483         );
1484 
1485         // Alpha is NOT decoded, and a 6-digit hex means OPAQUE — dropping the
1486         // last byte off a translucent plate colour does not leave it alone.
1487         assert!((parse_hex_rgba_linear("#05050840").unwrap()[3] - 0.251).abs() < 0.002);
1488         assert_eq!(parse_hex_rgba_linear("#050508").unwrap()[3], 1.0);
1489     }
1490 
1491     /// Rim refraction defaults OFF and clamps, like its neighbour.
1492     /// The pane plates roll over the relief width — the one roll width —
1493     /// and nothing, a loaded `plate.bevel_width` included, moves them off it.
1494     #[test]
1495     fn the_pane_roll_is_the_relief_width() {
1496         let _lock = test_color_state_lock();
1497         crate::layout::lazy_init_style_registry();
1498         let _ = plate_blur();
1499         reload_colors("style {\n surface {\n plate bevel_width=(f64)12.0\n }\n}\n");
1500         assert_eq!(plate_bevel_width(), crate::layout::bevel_width(), "a retired key moves nothing");
1501         assert_eq!(
1502             retired_surface_keys(&crate::config::parse_kdl_to_json("style {\n surface {\n plate bevel_width=(f64)12.0\n }\n}\n")),
1503             vec!["style.surface.plate.bevel_width"]
1504         );
1505         reload_colors("");
1506     }
1507 
1508     /// The four flat frost keys, the `backdrop_compression` spelling inside
1509     /// a `frost` child, `plate.bevel_width`, `depth` (on the relief, or in a
1510     /// material's `finish`), the relief's flat geometry keys and the
1511     /// window_manager bevel spellings are reported by path; the blocks
1512     /// themselves and a material's `compression` / `light` are not.
1513     #[test]
1514     fn retired_surface_keys_are_named_by_path_and_the_block_is_not() {
1515         let clean: serde_json::Value = serde_json::json!({ "style": { "surface": {
1516             "plate": { "frost": { "radius": 5.5, "compression": 0.0, "refraction": 0.0 }, "color": "#6c6c7bf2" },
1517             "relief": { "light": 0.15, "wall": { "height": 1.0, "profile": "a" }, "edge": { "height": 2.0, "profile": "b" } },
1518             "material": { "glass": { "frost": { "compression": 0.6 }, "finish": { "light": 0.2 } } }
1519         } }, "window_manager": { "corner_shape": 4.5, "control_relief": true } });
1520         assert!(retired_surface_keys(&clean).is_empty());
1521         let old: serde_json::Value = serde_json::json!({ "style": { "surface": {
1522             "plate": { "blur": true, "radius": 1.5, "backdrop_compression": 0.85, "refraction": 0.3,
1523                        "bevel_width": 12.0, "frost": { "backdrop_compression": 0.2 } },
1524             "relief": { "depth": 0.15, "height": 1.0, "profile": "a", "edge_height": 2.0, "edge_profile": "b" },
1525             "material": { "glass": { "frost": { "backdrop_compression": 0.6 }, "finish": { "depth": 0.2 } } }
1526         } }, "window_manager": { "bevel_depth": 0.15, "bevel_width": 9.3, "bevel_shader": 0 } });
1527         let mut old = old;
1528         old["style"]["surface"]["graph"] = serde_json::json!({ "cell_color": "#545467", "gap_color": "#48485b", "grid_color": "#48485b", "uniform_background": false });
1529         assert_eq!(retired_surface_keys(&old), vec![
1530             "style.surface.plate.blur",
1531             "style.surface.plate.radius",
1532             "style.surface.plate.backdrop_compression",
1533             "style.surface.plate.refraction",
1534             "style.surface.plate.bevel_width",
1535             "style.surface.plate.frost.backdrop_compression",
1536             "style.surface.relief.depth",
1537             "style.surface.relief.height",
1538             "style.surface.relief.profile",
1539             "style.surface.relief.edge_height",
1540             "style.surface.relief.edge_profile",
1541             "window_manager.bevel_depth",
1542             "window_manager.bevel_width",
1543             "window_manager.bevel_shader",
1544             "style.surface.material.glass.frost.backdrop_compression",
1545             "style.surface.material.glass.finish.depth",
1546             "style.surface.graph.cell_color",
1547             "style.surface.graph.gap_color",
1548             "style.surface.graph.uniform_background",
1549         ]);
1550         assert!(retired_surface_keys(&serde_json::json!({})).is_empty());
1551     }
1552 
1553     #[test]
1554     fn plate_refraction_defaults_off_and_clamps() {
1555         assert_eq!(plate_refraction(), 0.0, "off unless a config asks");
1556         set_plate_refraction(0.6);
1557         assert_eq!(plate_refraction(), 0.6);
1558         set_plate_refraction(9.0);
1559         assert_eq!(plate_refraction(), 1.0);
1560         set_plate_refraction(-0.5);
1561         assert_eq!(plate_refraction(), 0.0);
1562     }
1563 
1564     /// The plate's backdrop compression defaults OFF and clamps.
1565     ///
1566     /// Off is load-bearing: it is the behaviour every config already in the
1567     /// wild has, and a toolkit-wide default that changed how every frosted
1568     /// surface in the DE looks would arrive unannounced in eighteen apps.
1569     /// Opting in is a per-app `style.surface.plate { frost compression=… }`.
1570     #[test]
1571     fn backdrop_compression_defaults_off_and_clamps() {
1572         assert_eq!(plate_backdrop_compression(), 0.0, "off unless a config asks");
1573 
1574         set_plate_backdrop_compression(0.85);
1575         assert_eq!(plate_backdrop_compression(), 0.85);
1576 
1577         // The shader clamps too, but a nonsense config value should not be
1578         // able to reach it and make the plate flat or inverted.
1579         set_plate_backdrop_compression(4.0);
1580         assert_eq!(plate_backdrop_compression(), 1.0);
1581         set_plate_backdrop_compression(-1.0);
1582         assert_eq!(plate_backdrop_compression(), 0.0);
1583     }
1584 
1585     /// A colour pinned by one test is invisible to a test beside it.
1586     ///
1587     /// The invariant that ends the parallel-flake class here. These statics
1588     /// are process-wide, and `test_graph_style_configuration` pins a dozen of
1589     /// them without restoring any; before `style_write` became per-thread
1590     /// under `cfg(test)`, every test running alongside it saw those values.
1591     #[test]
1592     fn a_pinned_colour_is_private_to_its_thread() {
1593         let base = node_color();
1594         let pinned = [0.123, 0.456, 0.789, 1.0];
1595         assert_ne!(base, pinned, "pick a value the config cannot already hold");
1596 
1597         set_node_color(pinned);
1598         assert_eq!(node_color(), pinned, "the pinning thread sees its own value");
1599 
1600         let elsewhere = std::thread::spawn(node_color).join().unwrap();
1601         assert_eq!(elsewhere, base, "a thread beside it must still see the shared base");
1602     }
1603 }