max / makenotwork
36 files changed,
+1118 insertions,
-687 deletions
| @@ -7059,7 +7059,7 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" | |||
| 7059 | 7059 | ||
| 7060 | 7060 | [[package]] | |
| 7061 | 7061 | name = "theme-common" | |
| 7062 | - | version = "0.4.0" | |
| 7062 | + | version = "0.5.0" | |
| 7063 | 7063 | dependencies = [ | |
| 7064 | 7064 | "serde", | |
| 7065 | 7065 | "toml", |
| @@ -7,10 +7,10 @@ | |||
| 7 | 7 | //! [`DEFAULT_THEME_ID`]. | |
| 8 | 8 | //! | |
| 9 | 9 | //! Themes are embedded at compile time so production needs no themes directory | |
| 10 | - | //! on disk. Each theme's primitive-layer CSS is rendered once via | |
| 11 | - | //! [`theme_common::to_css_vars`] and cached; page handlers inject the rendered | |
| 12 | - | //! string into the page `<head>`, where it overrides the default `:root` | |
| 13 | - | //! primitives from `static/style.css` (the semantic layer derives from there, | |
| 10 | + | //! on disk. Each theme is resolved to its intent tokens and rendered once via | |
| 11 | + | //! [`theme_common::intent_css_vars`] and cached; page handlers inject the | |
| 12 | + | //! rendered string into the page `<head>`, where it overrides the default | |
| 13 | + | //! intent `:root` from `static/style.css` (the brand aliases derive from there, | |
| 14 | 14 | //! so the whole page re-themes). This is the single TOML -> CSS mapping shared | |
| 15 | 15 | //! with GoingsOn and audiofiles. | |
| 16 | 16 | ||
| @@ -18,7 +18,7 @@ use std::collections::BTreeMap; | |||
| 18 | 18 | use std::sync::LazyLock; | |
| 19 | 19 | ||
| 20 | 20 | use include_dir::{Dir, include_dir}; | |
| 21 | - | use theme_common::{ThemeMeta, parse_theme_str, to_css_vars}; | |
| 21 | + | use theme_common::{ThemeMeta, intent_css_vars, parse_theme_str, resolve}; | |
| 22 | 22 | ||
| 23 | 23 | /// The bundled themes, embedded from the shared crate at compile time. | |
| 24 | 24 | static THEMES_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../shared/themes"); | |
| @@ -28,10 +28,10 @@ static THEMES_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../shared/themes" | |||
| 28 | 28 | pub const DEFAULT_THEME_ID: &str = "makenotwork"; | |
| 29 | 29 | ||
| 30 | 30 | /// A built-in theme: its metadata (for the picker) and its pre-rendered | |
| 31 | - | /// primitive-layer CSS (for `<head>` injection). | |
| 31 | + | /// intent-layer CSS (for `<head>` injection). | |
| 32 | 32 | pub struct ThemeEntry { | |
| 33 | 33 | pub meta: ThemeMeta, | |
| 34 | - | /// `:root { … }` block of the 14 primitive tokens, ready to inline. | |
| 34 | + | /// `:root { … }` block of resolved intent tokens, ready to inline. | |
| 35 | 35 | pub css: String, | |
| 36 | 36 | } | |
| 37 | 37 | ||
| @@ -51,8 +51,9 @@ static REGISTRY: LazyLock<BTreeMap<String, ThemeEntry>> = LazyLock::new(|| { | |||
| 51 | 51 | }; | |
| 52 | 52 | match parse_theme_str(id, content, false) { | |
| 53 | 53 | Ok(theme) => { | |
| 54 | - | let css = to_css_vars(&theme); | |
| 55 | - | map.insert(id.to_string(), ThemeEntry { meta: theme.meta, css }); | |
| 54 | + | let tokens = resolve(&theme); | |
| 55 | + | let css = intent_css_vars(&tokens); | |
| 56 | + | map.insert(id.to_string(), ThemeEntry { meta: tokens.meta, css }); | |
| 56 | 57 | } | |
| 57 | 58 | Err(e) => { | |
| 58 | 59 | // A malformed bundled theme is a build-content bug, not a | |
| @@ -148,13 +149,15 @@ mod tests { | |||
| 148 | 149 | } | |
| 149 | 150 | ||
| 150 | 151 | #[test] | |
| 151 | - | fn default_css_carries_primitives_losslessly() { | |
| 152 | + | fn default_css_carries_intent_tokens() { | |
| 152 | 153 | let css = theme_css(None); | |
| 153 | - | // The values style.css's semantic layer derives from. | |
| 154 | - | assert!(css.contains("--bg-primary: #ede8e1;")); | |
| 155 | - | assert!(css.contains("--fg-primary: #3d3530;")); | |
| 156 | - | assert!(css.contains("--accent-blue: #6c5ce7;")); | |
| 157 | - | assert!(css.contains("--border-default: #d0cbb8;")); | |
| 154 | + | // Intent vocabulary the page's brand aliases derive from. | |
| 155 | + | assert!(css.contains("--surface-page: #ede8e1;")); | |
| 156 | + | assert!(css.contains("--content: #3d3530;")); | |
| 157 | + | assert!(css.contains("--action: #6c5ce7;")); | |
| 158 | + | assert!(css.contains("--border: #d0cbb8;")); | |
| 159 | + | // A derived interactive state is present too. | |
| 160 | + | assert!(css.contains("--selection: ")); | |
| 158 | 161 | } | |
| 159 | 162 | ||
| 160 | 163 | #[test] |
| @@ -136,63 +136,70 @@ | |||
| 136 | 136 | --font-body: "Lato", sans-serif; | |
| 137 | 137 | ||
| 138 | 138 | /* -------------------------------------------------------------------- | |
| 139 | - | PRIMITIVE LAYER — theme-common palette (themeable) | |
| 140 | - | ||
| 141 | - | The 14 shared tokens emitted by theme_common::to_css_vars(): 4 bg, | |
| 142 | - | 3 fg, 6 accent, 1 border. These are the ONLY literal source for | |
| 143 | - | themeable color. A built-in or creator theme swaps this block; the | |
| 144 | - | semantic layer below derives from it, so re-theming cascades for free. | |
| 145 | - | Values reproduce shared/themes/makenotwork.toml exactly. Names match | |
| 146 | - | GoingsOn + audiofiles, so one theme ports across every product. | |
| 139 | + | INTENT LAYER — the shared theme vocabulary (themeable) | |
| 140 | + | ||
| 141 | + | These are the intent tokens emitted by theme_common::intent_css_vars(): | |
| 142 | + | colors declared by ROLE (surface/content/action/status/line/category), | |
| 143 | + | plus the derived interactive states (hover/active/selection/row-stripe/ | |
| 144 | + | contrast) the crate computes once for every product. The values below | |
| 145 | + | are the platform default (shared/themes/makenotwork.toml resolved) so | |
| 146 | + | non-creator pages render without injection; creator profile/project/item | |
| 147 | + | pages inject a <style> that supersedes this block with the chosen theme. | |
| 148 | + | Generated — keep in sync with `cargo run --example dump_intents makenotwork`. | |
| 147 | 149 | -------------------------------------------------------------------- */ | |
| 148 | - | --bg-primary: #ede8e1; | |
| 149 | - | --bg-secondary: #f4f0eb; | |
| 150 | - | --bg-tertiary: #ddd7c5; | |
| 151 | - | --bg-surface: #f5f0eb; | |
| 152 | - | --fg-primary: #3d3530; | |
| 153 | - | --fg-secondary: #5c554f; | |
| 154 | - | --fg-muted: #8a8480; | |
| 155 | - | --accent-red: #c0392b; | |
| 156 | - | --accent-green: #27ae60; | |
| 157 | - | --accent-blue: #6c5ce7; | |
| 158 | - | --accent-yellow: #f59e0b; | |
| 159 | - | --accent-purple: #a29bfe; | |
| 160 | - | --accent-cyan: #17a2b8; | |
| 161 | - | --border-default: #d0cbb8; | |
| 150 | + | --surface-page: #ede8e1; | |
| 151 | + | --surface-raised: #f5f0eb; | |
| 152 | + | --surface-sunken: #ddd7c5; | |
| 153 | + | --surface-overlay: #f4f0eb; | |
| 154 | + | --content: #3d3530; | |
| 155 | + | --content-secondary: #5c554f; | |
| 156 | + | --content-muted: #8a8480; | |
| 157 | + | --content-on-action: #ffffff; | |
| 158 | + | --action: #6c5ce7; | |
| 159 | + | --action-hover: #7b6ce9; | |
| 160 | + | --action-active: #6153d0; | |
| 161 | + | --danger: #c0392b; | |
| 162 | + | --success: #27ae60; | |
| 163 | + | --warning: #f59e0b; | |
| 164 | + | --info: #17a2b8; | |
| 165 | + | --danger-surface: #e8d3cb; | |
| 166 | + | --success-surface: #d5e1d2; | |
| 167 | + | --warning-surface: #eedfc7; | |
| 168 | + | --info-surface: #d3e0dc; | |
| 169 | + | --border: #d0cbb8; | |
| 170 | + | --border-strong: #bbb7a6; | |
| 171 | + | --focus-ring: #6c5ce7; | |
| 172 | + | --selection: #c6bee3; | |
| 173 | + | --row-stripe: #efeae4; | |
| 174 | + | --hover-surface: #ddd7c5; | |
| 175 | + | --category-one: #c0392b; | |
| 176 | + | --category-two: #27ae60; | |
| 177 | + | --category-three: #6c5ce7; | |
| 178 | + | --category-four: #f59e0b; | |
| 179 | + | --category-five: #a29bfe; | |
| 180 | + | --category-six: #17a2b8; | |
| 162 | 181 | ||
| 163 | 182 | /* -------------------------------------------------------------------- | |
| 164 | - | SEMANTIC LAYER — MNW roles derived from the primitives above. | |
| 183 | + | BRAND ALIASES — MNW's historical vocabulary over the intent layer. | |
| 165 | 184 | ||
| 166 | - | Components reference THESE names, never the raw palette. Each | |
| 167 | - | derivation currently resolves to its historical hex, so the swap to | |
| 168 | - | the primitive layer is visually lossless; change a theme and these | |
| 169 | - | follow. Brand vocabulary (--detail, --highlight, --light-background) | |
| 170 | - | is kept as derived aliases so 300+ existing call sites stay valid. | |
| 185 | + | Kept so 300+ existing call sites (--detail, --highlight, --surface-*) | |
| 186 | + | stay valid. Names that already match an intent token (--border, | |
| 187 | + | --danger, --success, --focus-ring) consume it directly above. | |
| 171 | 188 | -------------------------------------------------------------------- */ | |
| 172 | - | /* Surfaces + ink */ | |
| 173 | - | --background: var(--bg-primary); | |
| 174 | - | --light-background: var(--bg-secondary); | |
| 175 | - | --secondary-bg: var(--bg-secondary); | |
| 176 | - | --surface: var(--bg-surface); | |
| 177 | - | --surface-raised: var(--bg-surface); | |
| 178 | - | --surface-muted: var(--bg-tertiary); | |
| 179 | - | --detail: var(--fg-primary); | |
| 180 | - | --text: var(--fg-primary); | |
| 181 | - | --text-muted: var(--fg-muted); | |
| 182 | - | --highlight: var(--accent-blue); | |
| 183 | - | --accent: var(--accent-blue); | |
| 184 | - | --border: var(--border-default); | |
| 185 | - | --surface-border: var(--border-default); | |
| 186 | - | ||
| 187 | - | /* Status roles */ | |
| 188 | - | --success: var(--accent-green); | |
| 189 | - | --danger: var(--accent-red); | |
| 190 | - | --focus-ring: var(--accent-blue); | |
| 191 | - | ||
| 192 | - | /* Compatibility aliases (git browser, older inline styles) */ | |
| 193 | - | --accent-color: var(--accent-blue); | |
| 194 | - | --border-color: var(--border-default); | |
| 195 | - | --background-color: var(--bg-primary); | |
| 189 | + | --background: var(--surface-page); | |
| 190 | + | --background-color: var(--surface-page); | |
| 191 | + | --light-background: var(--surface-overlay); | |
| 192 | + | --secondary-bg: var(--surface-overlay); | |
| 193 | + | --surface: var(--surface-raised); | |
| 194 | + | --surface-muted: var(--surface-sunken); | |
| 195 | + | --detail: var(--content); | |
| 196 | + | --text: var(--content); | |
| 197 | + | --text-muted: var(--content-muted); | |
| 198 | + | --highlight: var(--action); | |
| 199 | + | --accent: var(--action); | |
| 200 | + | --accent-color: var(--action); | |
| 201 | + | --surface-border: var(--border); | |
| 202 | + | --border-color: var(--border); | |
| 196 | 203 | ||
| 197 | 204 | /* -------------------------------------------------------------------- | |
| 198 | 205 | APP-SIDE LITERALS — intentionally NOT in the shared palette. | |
| @@ -4214,7 +4221,7 @@ form button:active { | |||
| 4214 | 4221 | } | |
| 4215 | 4222 | ||
| 4216 | 4223 | .layer-chip-pass { background: #e6f0dc; border-color: #c4d8a8; color: #3a5a2a; } | |
| 4217 | - | .layer-chip-skip { background: var(--bg-page); border-color: var(--border); color: var(--text-muted); } | |
| 4224 | + | .layer-chip-skip { background: var(--surface-page); border-color: var(--border); color: var(--text-muted); } | |
| 4218 | 4225 | .layer-chip-fail { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); font-weight: 600; } | |
| 4219 | 4226 | .layer-chip-error { background: var(--warning-bg); border-color: var(--warning-border); color: var(--warning); font-weight: 600; } | |
| 4220 | 4227 |
| @@ -273,7 +273,7 @@ dependencies = [ | |||
| 273 | 273 | ||
| 274 | 274 | [[package]] | |
| 275 | 275 | name = "theme-common" | |
| 276 | - | version = "0.4.0" | |
| 276 | + | version = "0.5.0" | |
| 277 | 277 | dependencies = [ | |
| 278 | 278 | "serde", | |
| 279 | 279 | "tempfile", |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | [package] | |
| 2 | 2 | name = "theme-common" | |
| 3 | - | version = "0.4.0" | |
| 3 | + | version = "0.5.0" | |
| 4 | 4 | edition = "2024" | |
| 5 | 5 | ||
| 6 | 6 | [dependencies] |
| @@ -0,0 +1,8 @@ | |||
| 1 | + | fn main() { | |
| 2 | + | let id = std::env::args().nth(1).unwrap_or_else(|| "makenotwork".into()); | |
| 3 | + | let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join("themes"); | |
| 4 | + | let t = theme_common::load_semantic(&[(dir, false)], &id).unwrap(); | |
| 5 | + | for (k, v) in &t.intents { | |
| 6 | + | println!(" --{k}: {v};"); | |
| 7 | + | } | |
| 8 | + | } |
| @@ -1,32 +1,46 @@ | |||
| 1 | - | //! Shared theme loading logic for TOML-based theme files. | |
| 1 | + | //! Shared theme loading + intent resolution for TOML-based theme files. | |
| 2 | 2 | //! | |
| 3 | - | //! Used by GoingsOn, Balanced Breakfast (Tauri apps), and audiofiles (egui). | |
| 4 | - | //! audiofiles embeds themes at compile time but uses `ThemeMeta`, `parse_meta`, | |
| 5 | - | //! and `extract_colors` from this crate. | |
| 3 | + | //! Used by GoingsOn, Balanced Breakfast (Tauri apps), audiofiles (egui), and the | |
| 4 | + | //! MNW web server. Themes are authored by **intent** ("human design"): colors are | |
| 5 | + | //! declared by role (surface / content / action / status / line / category), not | |
| 6 | + | //! by hue. This crate is the single place that resolves an authored theme into a | |
| 7 | + | //! full set of intent tokens — including the derived interactive states | |
| 8 | + | //! (hover/active/selection/row-stripe/contrast) that each app used to recompute | |
| 9 | + | //! itself — and emits them as CSS variables or RGB tuples. | |
| 6 | 10 | //! | |
| 7 | - | //! Theme files are TOML with this structure: | |
| 11 | + | //! Theme file shape: | |
| 8 | 12 | //! ```text | |
| 9 | 13 | //! [meta] | |
| 10 | - | //! name = "Theme Name" | |
| 11 | - | //! variant = "dark" # or "light" | |
| 14 | + | //! name = "Nord" | |
| 15 | + | //! variant = "dark" # or "light" | |
| 12 | 16 | //! | |
| 13 | - | //! [background] | |
| 14 | - | //! primary = "#1e1e2e" | |
| 17 | + | //! [surface] # container backgrounds by role/elevation | |
| 18 | + | //! page = "#2e3440"; raised = "#3b4252"; sunken = "#434c5e"; overlay = "#3b4252" | |
| 15 | 19 | //! | |
| 16 | - | //! [foreground] | |
| 17 | - | //! primary = "#cdd6f4" | |
| 20 | + | //! [content] # text/ink by emphasis | |
| 21 | + | //! primary = "#d8dee9"; secondary = "#e5e9f0"; muted = "#616e88" | |
| 18 | 22 | //! | |
| 19 | - | //! [accent] | |
| 20 | - | //! primary = "#89b4fa" | |
| 23 | + | //! [action] # interactive / brand color | |
| 24 | + | //! primary = "#81a1c1" | |
| 21 | 25 | //! | |
| 22 | - | //! [border] | |
| 23 | - | //! primary = "#45475a" | |
| 26 | + | //! [status] # state semantics | |
| 27 | + | //! danger = "#bf616a"; success = "#a3be8c"; warning = "#ebcb8b"; info = "#88c0d0" | |
| 28 | + | //! | |
| 29 | + | //! [line] | |
| 30 | + | //! border = "#4c566a" | |
| 31 | + | //! | |
| 32 | + | //! [category] # distinct decorative colors for tags/badges/charts | |
| 33 | + | //! one = "#bf616a"; two = "#a3be8c"; three = "#81a1c1" | |
| 34 | + | //! four = "#ebcb8b"; five = "#b48ead"; six = "#88c0d0" | |
| 24 | 35 | //! ``` | |
| 25 | 36 | ||
| 26 | 37 | use serde::Serialize; | |
| 27 | - | use std::collections::HashMap; | |
| 38 | + | use std::collections::{BTreeMap, HashMap}; | |
| 28 | 39 | use std::path::{Path, PathBuf}; | |
| 29 | 40 | ||
| 41 | + | /// The color sections an authored theme may declare. | |
| 42 | + | pub const COLOR_SECTIONS: &[&str] = &["surface", "content", "action", "status", "line", "category"]; | |
| 43 | + | ||
| 30 | 44 | /// Theme metadata parsed from the `[meta]` section. | |
| 31 | 45 | #[derive(Debug, Clone, Serialize)] | |
| 32 | 46 | #[serde(rename_all = "camelCase")] | |
| @@ -37,7 +51,8 @@ pub struct ThemeMeta { | |||
| 37 | 51 | pub is_custom: bool, | |
| 38 | 52 | } | |
| 39 | 53 | ||
| 40 | - | /// A fully loaded theme: metadata plus flattened color map. | |
| 54 | + | /// A loaded theme: metadata plus the authored colors, flattened to dotted keys | |
| 55 | + | /// (e.g. `"surface.page"`, `"status.danger"`, `"category.one"`). | |
| 41 | 56 | #[derive(Debug, Serialize)] | |
| 42 | 57 | #[serde(rename_all = "camelCase")] | |
| 43 | 58 | pub struct ThemeColors { | |
| @@ -45,6 +60,210 @@ pub struct ThemeColors { | |||
| 45 | 60 | pub colors: HashMap<String, String>, | |
| 46 | 61 | } | |
| 47 | 62 | ||
| 63 | + | // ============================================================================ | |
| 64 | + | // Color math (single source of truth for the four derivation ops) | |
| 65 | + | // ============================================================================ | |
| 66 | + | ||
| 67 | + | /// An sRGB color. Hex round-trips losslessly. | |
| 68 | + | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
| 69 | + | pub struct Rgb { | |
| 70 | + | pub r: u8, | |
| 71 | + | pub g: u8, | |
| 72 | + | pub b: u8, | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | impl Rgb { | |
| 76 | + | /// Parse `#rgb` or `#rrggbb` (case-insensitive). Returns `None` otherwise. | |
| 77 | + | pub fn from_hex(s: &str) -> Option<Rgb> { | |
| 78 | + | let h = s.strip_prefix('#')?; | |
| 79 | + | let (r, g, b) = match h.len() { | |
| 80 | + | 6 => ( | |
| 81 | + | u8::from_str_radix(&h[0..2], 16).ok()?, | |
| 82 | + | u8::from_str_radix(&h[2..4], 16).ok()?, | |
| 83 | + | u8::from_str_radix(&h[4..6], 16).ok()?, | |
| 84 | + | ), | |
| 85 | + | 3 => { | |
| 86 | + | let d = |c: &str| u8::from_str_radix(c, 16).ok().map(|v| v * 17); | |
| 87 | + | (d(&h[0..1])?, d(&h[1..2])?, d(&h[2..3])?) | |
| 88 | + | } | |
| 89 | + | _ => return None, | |
| 90 | + | }; | |
| 91 | + | Some(Rgb { r, g, b }) | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | /// Lowercase `#rrggbb`. | |
| 95 | + | pub fn to_hex(self) -> String { | |
| 96 | + | format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b) | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | pub fn tuple(self) -> (u8, u8, u8) { | |
| 100 | + | (self.r, self.g, self.b) | |
| 101 | + | } | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | /// ITU-R BT.601 relative luminance in [0,1]. | |
| 105 | + | fn luminance(c: Rgb) -> f32 { | |
| 106 | + | (0.299 * c.r as f32 + 0.587 * c.g as f32 + 0.114 * c.b as f32) / 255.0 | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | /// Black or white, whichever contrasts with `bg`. Matches BB `contrastColor` | |
| 110 | + | /// and AF `contrast_color` (luminance > 0.5 → black). | |
| 111 | + | pub fn contrast_color(bg: Rgb) -> Rgb { | |
| 112 | + | if luminance(bg) > 0.5 { | |
| 113 | + | Rgb { r: 0, g: 0, b: 0 } | |
| 114 | + | } else { | |
| 115 | + | Rgb { r: 255, g: 255, b: 255 } | |
| 116 | + | } | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | /// Lighten each channel toward white by `pct` percent. Matches BB `lighten`. | |
| 120 | + | pub fn lighten(c: Rgb, pct: f32) -> Rgb { | |
| 121 | + | let f = |ch: u8| (ch as f32 + (255.0 - ch as f32) * (pct / 100.0)).round().clamp(0.0, 255.0) as u8; | |
| 122 | + | Rgb { r: f(c.r), g: f(c.g), b: f(c.b) } | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | /// Darken each channel toward black by `pct` percent. Matches BB `darken`. | |
| 126 | + | pub fn darken(c: Rgb, pct: f32) -> Rgb { | |
| 127 | + | let f = |ch: u8| (ch as f32 * (1.0 - pct / 100.0)).round().clamp(0.0, 255.0) as u8; | |
| 128 | + | Rgb { r: f(c.r), g: f(c.g), b: f(c.b) } | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | /// Linear blend from `a` to `b` by `t` in [0,1], per channel. Matches AF `lerp_color`. | |
| 132 | + | pub fn lerp(a: Rgb, b: Rgb, t: f32) -> Rgb { | |
| 133 | + | let f = |x: u8, y: u8| (x as f32 + (y as f32 - x as f32) * t).round().clamp(0.0, 255.0) as u8; | |
| 134 | + | Rgb { r: f(a.r, b.r), g: f(a.g, b.g), b: f(a.b, b.b) } | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | // ============================================================================ | |
| 138 | + | // Intent resolution | |
| 139 | + | // ============================================================================ | |
| 140 | + | ||
| 141 | + | /// Authored base intents: (TOML dotted source key, canonical token key). | |
| 142 | + | /// These are read straight from the theme; the token key is the CSS-var stem | |
| 143 | + | /// (`--{token}`) and the `rgb()` lookup key. | |
| 144 | + | pub const BASE_INTENTS: &[(&str, &str)] = &[ | |
| 145 | + | ("surface.page", "surface-page"), | |
| 146 | + | ("surface.raised", "surface-raised"), | |
| 147 | + | ("surface.sunken", "surface-sunken"), | |
| 148 | + | ("surface.overlay", "surface-overlay"), | |
| 149 | + | ("content.primary", "content"), | |
| 150 | + | ("content.secondary", "content-secondary"), | |
| 151 | + | ("content.muted", "content-muted"), | |
| 152 | + | ("action.primary", "action"), | |
| 153 | + | ("status.danger", "danger"), | |
| 154 | + | ("status.success", "success"), | |
| 155 | + | ("status.warning", "warning"), | |
| 156 | + | ("status.info", "info"), | |
| 157 | + | ("line.border", "border"), | |
| 158 | + | ("category.one", "category-one"), | |
| 159 | + | ("category.two", "category-two"), | |
| 160 | + | ("category.three", "category-three"), | |
| 161 | + | ("category.four", "category-four"), | |
| 162 | + | ("category.five", "category-five"), | |
| 163 | + | ("category.six", "category-six"), | |
| 164 | + | ]; | |
| 165 | + | ||
| 166 | + | /// A fully resolved intent layer: every token key → concrete `#rrggbb`. | |
| 167 | + | /// Includes both authored base intents and the computed derived intents. | |
| 168 | + | #[derive(Debug, Clone, Serialize)] | |
| 169 | + | #[serde(rename_all = "camelCase")] | |
| 170 | + | pub struct SemanticTokens { | |
| 171 | + | pub meta: ThemeMeta, | |
| 172 | + | /// token-key → resolved hex. Stable, deterministic ordering. | |
| 173 | + | pub intents: BTreeMap<String, String>, | |
| 174 | + | } | |
| 175 | + | ||
| 176 | + | impl SemanticTokens { | |
| 177 | + | /// Resolved hex for a token key, if present. | |
| 178 | + | pub fn hex(&self, key: &str) -> Option<&str> { | |
| 179 | + | self.intents.get(key).map(String::as_str) | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | /// Resolved RGB tuple for a token key (for egui / native consumers). | |
| 183 | + | pub fn rgb(&self, key: &str) -> Option<(u8, u8, u8)> { | |
| 184 | + | self.intents.get(key).and_then(|h| Rgb::from_hex(h)).map(Rgb::tuple) | |
| 185 | + | } | |
| 186 | + | } | |
| 187 | + | ||
| 188 | + | /// Resolve an authored theme into the full intent token set. | |
| 189 | + | /// | |
| 190 | + | /// 1. Copy each present base intent from the authored colors. | |
| 191 | + | /// 2. Compute the derived interactive states from the base intents, using the | |
| 192 | + | /// same math the apps used to apply individually (so output is identical). | |
| 193 | + | /// Each derived token is emitted only when its source intents exist, mirroring | |
| 194 | + | /// the skip-missing behavior of the rest of the crate. | |
| 195 | + | pub fn resolve(theme: &ThemeColors) -> SemanticTokens { | |
| 196 | + | let mut intents: BTreeMap<String, String> = BTreeMap::new(); | |
| 197 | + | ||
| 198 | + | // 1. Base intents (authored). | |
| 199 | + | for (src, token) in BASE_INTENTS { | |
| 200 | + | if let Some(v) = theme.colors.get(*src) { | |
| 201 | + | intents.insert((*token).to_string(), v.clone()); | |
| 202 | + | } | |
| 203 | + | } | |
| 204 | + | ||
| 205 | + | // Helper: parse an already-resolved token to Rgb. | |
| 206 | + | let get = |m: &BTreeMap<String, String>, k: &str| m.get(k).and_then(|h| Rgb::from_hex(h)); | |
| 207 | + | ||
| 208 | + | // 2. Derived intents. | |
| 209 | + | let mut derived: Vec<(String, Rgb)> = Vec::new(); | |
| 210 | + | if let Some(action) = get(&intents, "action") { | |
| 211 | + | derived.push(("action-hover".into(), lighten(action, 10.0))); | |
| 212 | + | derived.push(("action-active".into(), darken(action, 10.0))); | |
| 213 | + | derived.push(("content-on-action".into(), contrast_color(action))); | |
| 214 | + | derived.push(("focus-ring".into(), action)); | |
| 215 | + | if let Some(page) = get(&intents, "surface-page") { | |
| 216 | + | derived.push(("selection".into(), lerp(page, action, 0.3))); | |
| 217 | + | } | |
| 218 | + | } | |
| 219 | + | if let Some(page) = get(&intents, "surface-page") { | |
| 220 | + | if let Some(overlay) = get(&intents, "surface-overlay") { | |
| 221 | + | derived.push(("row-stripe".into(), lerp(page, overlay, 0.3))); | |
| 222 | + | } | |
| 223 | + | for status in ["danger", "success", "warning", "info"] { | |
| 224 | + | if let Some(c) = get(&intents, status) { | |
| 225 | + | derived.push((format!("{status}-surface"), lerp(page, c, 0.12))); | |
| 226 | + | } | |
| 227 | + | } | |
| 228 | + | } | |
| 229 | + | if let Some(sunken) = get(&intents, "surface-sunken") { | |
| 230 | + | derived.push(("hover-surface".into(), sunken)); | |
| 231 | + | } | |
| 232 | + | if let Some(border) = get(&intents, "border") { | |
| 233 | + | derived.push(("border-strong".into(), darken(border, 10.0))); | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | for (token, rgb) in derived { | |
| 237 | + | intents.insert(token, rgb.to_hex()); | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | SemanticTokens { meta: theme.meta.clone(), intents } | |
| 241 | + | } | |
| 242 | + | ||
| 243 | + | /// Emit the resolved intent layer as CSS declarations (no selector), one | |
| 244 | + | /// ` --token: #hex;` line each, in deterministic (BTreeMap) order. | |
| 245 | + | pub fn intent_css_declarations(tokens: &SemanticTokens) -> String { | |
| 246 | + | let mut out = String::new(); | |
| 247 | + | for (token, hex) in &tokens.intents { | |
| 248 | + | out.push_str(" --"); | |
| 249 | + | out.push_str(token); | |
| 250 | + | out.push_str(": "); | |
| 251 | + | out.push_str(hex); | |
| 252 | + | out.push_str(";\n"); | |
| 253 | + | } | |
| 254 | + | out | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | /// Emit the resolved intent layer as a `:root { … }` block — the single TOML → | |
| 258 | + | /// CSS mapping every web surface injects. | |
| 259 | + | pub fn intent_css_vars(tokens: &SemanticTokens) -> String { | |
| 260 | + | format!(":root {{\n{}}}\n", intent_css_declarations(tokens)) | |
| 261 | + | } | |
| 262 | + | ||
| 263 | + | // ============================================================================ | |
| 264 | + | // Loading / parsing | |
| 265 | + | // ============================================================================ | |
| 266 | + | ||
| 48 | 267 | /// Validate a theme ID contains only safe characters (alphanumeric, hyphens, underscores). | |
| 49 | 268 | pub fn validate_theme_id(id: &str) -> Result<(), String> { | |
| 50 | 269 | if !id | |
| @@ -56,7 +275,7 @@ pub fn validate_theme_id(id: &str) -> Result<(), String> { | |||
| 56 | 275 | Ok(()) | |
| 57 | 276 | } | |
| 58 | 277 | ||
| 59 | - | /// Parse the `[meta]` section from a TOML table into `ThemeMeta`. | |
| 278 | + | /// Parse the `[meta]` section into `ThemeMeta`. | |
| 60 | 279 | /// | |
| 61 | 280 | /// Falls back to the file ID as the name and `"dark"` as the variant. | |
| 62 | 281 | pub fn parse_meta(id: &str, table: &toml::Table, is_custom: bool) -> ThemeMeta { | |
| @@ -72,19 +291,14 @@ pub fn parse_meta(id: &str, table: &toml::Table, is_custom: bool) -> ThemeMeta { | |||
| 72 | 291 | .unwrap_or("dark") | |
| 73 | 292 | .to_string(); | |
| 74 | 293 | ||
| 75 | - | ThemeMeta { | |
| 76 | - | id: id.to_string(), | |
| 77 | - | name, | |
| 78 | - | variant, | |
| 79 | - | is_custom, | |
| 80 | - | } | |
| 294 | + | ThemeMeta { id: id.to_string(), name, variant, is_custom } | |
| 81 | 295 | } | |
| 82 | 296 | ||
| 83 | - | /// Extract color sections (background, foreground, accent, border) from a TOML | |
| 84 | - | /// table into a flat `HashMap` with keys like `"background.primary"`. | |
| 297 | + | /// Extract the intent color sections into a flat `HashMap` with dotted keys | |
| 298 | + | /// like `"surface.page"`, `"status.danger"`, `"category.one"`. | |
| 85 | 299 | pub fn extract_colors(table: &toml::Table) -> HashMap<String, String> { | |
| 86 | 300 | let mut colors = HashMap::new(); | |
| 87 | - | for section in &["background", "foreground", "accent", "border"] { | |
| 301 | + | for section in COLOR_SECTIONS { | |
| 88 | 302 | if let Some(sect) = table.get(*section).and_then(|s| s.as_table()) { | |
| 89 | 303 | for (key, val) in sect { | |
| 90 | 304 | if let Some(color) = val.as_str() { | |
| @@ -161,11 +375,7 @@ pub fn find_theme_path(dirs: &[(PathBuf, bool)], id: &str) -> Option<(PathBuf, b | |||
| 161 | 375 | } | |
| 162 | 376 | ||
| 163 | 377 | /// Parse a complete theme (metadata + colors) from raw TOML content, with no | |
| 164 | - | /// filesystem access. | |
| 165 | - | /// | |
| 166 | - | /// For callers that embed themes at compile time (e.g. the MNW server bundles | |
| 167 | - | /// `shared/themes/*.toml` into the binary) rather than reading a directory. | |
| 168 | - | /// Keeps TOML parsing inside this crate so consumers need only `theme_common`. | |
| 378 | + | /// filesystem access. For callers that embed themes at compile time. | |
| 169 | 379 | pub fn parse_theme_str(id: &str, content: &str, is_custom: bool) -> Result<ThemeColors, String> { | |
| 170 | 380 | validate_theme_id(id)?; | |
| 171 | 381 | let table: toml::Table = content | |
| @@ -196,11 +406,15 @@ pub fn load_theme(dirs: &[(PathBuf, bool)], id: &str) -> Result<ThemeColors, Str | |||
| 196 | 406 | Ok(ThemeColors { meta, colors }) | |
| 197 | 407 | } | |
| 198 | 408 | ||
| 409 | + | /// Load a theme and resolve it to the full intent token set in one step. | |
| 410 | + | pub fn load_semantic(dirs: &[(PathBuf, bool)], id: &str) -> Result<SemanticTokens, String> { | |
| 411 | + | Ok(resolve(&load_theme(dirs, id)?)) | |
| 412 | + | } | |
| 413 | + | ||
| 199 | 414 | /// Import a theme TOML file into the custom themes directory. | |
| 200 | 415 | /// | |
| 201 | - | /// Validates that the file is parseable TOML with the expected color sections, | |
| 202 | - | /// then copies it to `custom_dir/{id}.toml` where `id` is the file stem. | |
| 203 | - | /// Creates `custom_dir` if it doesn't exist. Returns the theme metadata. | |
| 416 | + | /// Validates that the file is parseable TOML with at least one intent color | |
| 417 | + | /// section, then copies it to `custom_dir/{id}.toml`. Returns the theme metadata. | |
| 204 | 418 | pub fn import_theme(source_path: &Path, custom_dir: &Path) -> Result<ThemeMeta, String> { | |
| 205 | 419 | let content = std::fs::read_to_string(source_path) | |
| 206 | 420 | .map_err(|e| format!("Failed to read {}: {}", source_path.display(), e))?; | |
| @@ -209,12 +423,14 @@ pub fn import_theme(source_path: &Path, custom_dir: &Path) -> Result<ThemeMeta, | |||
| 209 | 423 | .parse() | |
| 210 | 424 | .map_err(|e| format!("Invalid TOML: {}", e))?; | |
| 211 | 425 | ||
| 212 | - | // Verify it has at least one color section | |
| 213 | - | let has_colors = ["background", "foreground", "accent", "border"] | |
| 426 | + | let has_colors = COLOR_SECTIONS | |
| 214 | 427 | .iter() | |
| 215 | 428 | .any(|s| table.get(*s).and_then(|v| v.as_table()).is_some()); | |
| 216 | 429 | if !has_colors { | |
| 217 | - | return Err("Theme file must have at least one color section (background, foreground, accent, or border)".to_string()); | |
| 430 | + | return Err(format!( | |
| 431 | + | "Theme file must have at least one color section ({})", | |
| 432 | + | COLOR_SECTIONS.join(", ") | |
| 433 | + | )); | |
| 218 | 434 | } | |
| 219 | 435 | ||
| 220 | 436 | let id = source_path | |
| @@ -237,8 +453,7 @@ pub fn import_theme(source_path: &Path, custom_dir: &Path) -> Result<ThemeMeta, | |||
| 237 | 453 | /// Delete a custom theme by ID. | |
| 238 | 454 | /// | |
| 239 | 455 | /// Only operates on `custom_dir` — bundled themes are not deletable through | |
| 240 | - | /// this entry point. Returns `Err` if the ID is invalid, the file does not | |
| 241 | - | /// exist, or the underlying remove fails. | |
| 456 | + | /// this entry point. | |
| 242 | 457 | pub fn delete_theme(custom_dir: &Path, id: &str) -> Result<(), String> { | |
| 243 | 458 | validate_theme_id(id)?; | |
| 244 | 459 | ||
| @@ -251,31 +466,32 @@ pub fn delete_theme(custom_dir: &Path, id: &str) -> Result<(), String> { | |||
| 251 | 466 | .map_err(|e| format!("Failed to delete {}: {}", path.display(), e)) | |
| 252 | 467 | } | |
| 253 | 468 | ||
| 254 | - | /// A four-color palette for preview chips, swatches, etc. | |
| 255 | - | /// | |
| 256 | - | /// Smaller than `ThemeColors`: only the `primary` value from each of the four | |
| 257 | - | /// canonical sections, so callers rendering a list of theme thumbnails don't | |
| 258 | - | /// need to allocate a full `HashMap` per row. | |
| 469 | + | /// A four-color preview for theme thumbnails: the representative swatch from | |
| 470 | + | /// each of the principal roles. | |
| 259 | 471 | #[derive(Debug, Clone, Serialize)] | |
| 260 | 472 | #[serde(rename_all = "camelCase")] | |
| 261 | 473 | pub struct ThemePreview { | |
| 262 | 474 | pub meta: ThemeMeta, | |
| 475 | + | /// Page background (`surface.page`). | |
| 263 | 476 | pub background: Option<String>, | |
| 477 | + | /// Body text (`content.primary`). | |
| 264 | 478 | pub foreground: Option<String>, | |
| 479 | + | /// Brand/interactive color (`action.primary`). | |
| 265 | 480 | pub accent: Option<String>, | |
| 481 | + | /// Divider/outline color (`line.border`). | |
| 266 | 482 | pub border: Option<String>, | |
| 267 | 483 | } | |
| 268 | 484 | ||
| 269 | - | fn primary_color(table: &toml::Table, section: &str) -> Option<String> { | |
| 485 | + | fn color_at(table: &toml::Table, section: &str, key: &str) -> Option<String> { | |
| 270 | 486 | table | |
| 271 | 487 | .get(section) | |
| 272 | 488 | .and_then(|s| s.as_table()) | |
| 273 | - | .and_then(|s| s.get("primary")) | |
| 489 | + | .and_then(|s| s.get(key)) | |
| 274 | 490 | .and_then(|v| v.as_str()) | |
| 275 | 491 | .map(|s| s.to_string()) | |
| 276 | 492 | } | |
| 277 | 493 | ||
| 278 | - | /// Load just the primary colors for a theme — for UI previews / thumbnails. | |
| 494 | + | /// Load just the preview swatches for a theme — for UI thumbnails. | |
| 279 | 495 | pub fn load_theme_preview(dirs: &[(PathBuf, bool)], id: &str) -> Result<ThemePreview, String> { | |
| 280 | 496 | validate_theme_id(id)?; | |
| 281 | 497 | ||
| @@ -291,17 +507,14 @@ pub fn load_theme_preview(dirs: &[(PathBuf, bool)], id: &str) -> Result<ThemePre | |||
| 291 | 507 | ||
| 292 | 508 | Ok(ThemePreview { | |
| 293 | 509 | meta: parse_meta(id, &table, is_custom), | |
| 294 | - | background: primary_color(&table, "background"), | |
| 295 | - | foreground: primary_color(&table, "foreground"), | |
| 296 | - | accent: primary_color(&table, "accent"), | |
| 297 | - | border: primary_color(&table, "border"), | |
| 510 | + | background: color_at(&table, "surface", "page"), | |
| 511 | + | foreground: color_at(&table, "content", "primary"), | |
| 512 | + | accent: color_at(&table, "action", "primary"), | |
| 513 | + | border: color_at(&table, "line", "border"), | |
| 298 | 514 | }) | |
| 299 | 515 | } | |
| 300 | 516 | ||
| 301 | 517 | /// Export a theme to a user-chosen path. | |
| 302 | - | /// | |
| 303 | - | /// Finds the theme by ID in the given directories and copies the TOML file | |
| 304 | - | /// to `dest_path`. | |
| 305 | 518 | pub fn export_theme(dirs: &[(PathBuf, bool)], id: &str, dest_path: &Path) -> Result<(), String> { | |
| 306 | 519 | validate_theme_id(id)?; | |
| 307 | 520 | ||
| @@ -314,67 +527,10 @@ pub fn export_theme(dirs: &[(PathBuf, bool)], id: &str, dest_path: &Path) -> Res | |||
| 314 | 527 | Ok(()) | |
| 315 | 528 | } | |
| 316 | 529 | ||
| 317 | - | /// The canonical TOML-key -> CSS-custom-property mapping for the primitive layer. | |
| 318 | - | /// | |
| 319 | - | /// This is the single source of truth for how a theme-common palette becomes CSS | |
| 320 | - | /// variables. Every web surface (MNW server-rendered pages, and eventually | |
| 321 | - | /// GoingsOn — which today hand-maintains an equivalent `COLOR_MAP` in JS) emits | |
| 322 | - | /// the *same* variable names from this table, so adding a token is one edit here. | |
| 323 | - | /// | |
| 324 | - | /// The 14 primitive tokens: 4 backgrounds, 3 foregrounds, 6 accents, 1 border. | |
| 325 | - | /// Apps derive their semantic roles (`--surface`, `--danger`, diff/health, …) | |
| 326 | - | /// from these in their own base CSS; the crate stays a pure palette. | |
| 327 | - | pub const PRIMITIVE_VARS: &[(&str, &str)] = &[ | |
| 328 | - | ("background.primary", "--bg-primary"), | |
| 329 | - | ("background.secondary", "--bg-secondary"), | |
| 330 | - | ("background.tertiary", "--bg-tertiary"), | |
| 331 | - | ("background.surface", "--bg-surface"), | |
| 332 | - | ("foreground.primary", "--fg-primary"), | |
| 333 | - | ("foreground.secondary", "--fg-secondary"), | |
| 334 | - | ("foreground.muted", "--fg-muted"), | |
| 335 | - | ("accent.red", "--accent-red"), | |
| 336 | - | ("accent.green", "--accent-green"), | |
| 337 | - | ("accent.blue", "--accent-blue"), | |
| 338 | - | ("accent.yellow", "--accent-yellow"), | |
| 339 | - | ("accent.purple", "--accent-purple"), | |
| 340 | - | ("accent.cyan", "--accent-cyan"), | |
| 341 | - | ("border.default", "--border-default"), | |
| 342 | - | ]; | |
| 343 | - | ||
| 344 | - | /// Emit the primitive layer as CSS declarations (no selector wrapper). | |
| 345 | - | /// | |
| 346 | - | /// One ` --name: value;` line per token that the theme actually defines, in the | |
| 347 | - | /// canonical [`PRIMITIVE_VARS`] order. Missing tokens are skipped rather than | |
| 348 | - | /// emitted empty, mirroring how the desktop apps apply only present keys. The | |
| 349 | - | /// caller chooses the selector — use [`to_css_vars`] for a ready `:root` block, | |
| 350 | - | /// or wrap these declarations in any scope (e.g. a creator canvas class). | |
| 351 | - | pub fn to_css_declarations(theme: &ThemeColors) -> String { | |
| 352 | - | let mut out = String::new(); | |
| 353 | - | for (toml_key, css_var) in PRIMITIVE_VARS { | |
| 354 | - | if let Some(value) = theme.colors.get(*toml_key) { | |
| 355 | - | out.push_str(" "); | |
| 356 | - | out.push_str(css_var); | |
| 357 | - | out.push_str(": "); | |
| 358 | - | out.push_str(value); | |
| 359 | - | out.push_str(";\n"); | |
| 360 | - | } | |
| 361 | - | } | |
| 362 | - | out | |
| 363 | - | } | |
| 364 | - | ||
| 365 | - | /// Emit the primitive layer as a CSS `:root { … }` block. | |
| 366 | - | /// | |
| 367 | - | /// This is the single TOML -> CSS mapping for the web. Inject the result into a | |
| 368 | - | /// page `<head>` (or serve it as a stylesheet) to apply a theme's primitive | |
| 369 | - | /// palette; the app's semantic layer derives from these variables. | |
| 370 | - | pub fn to_css_vars(theme: &ThemeColors) -> String { | |
| 371 | - | format!(":root {{\n{}}}\n", to_css_declarations(theme)) | |
| 372 | - | } | |
| 373 | - | ||
| 374 | 530 | /// Construct a dev fallback theme directory path. | |
| 375 | 531 | /// | |
| 376 | 532 | /// Given a `CARGO_MANIFEST_DIR`, walks up `levels` parent directories and | |
| 377 | - | /// appends `"themes"`. Returns the path if the directory exists. | |
| 533 | + | /// appends `MNW/shared/themes`. Returns the path if the directory exists. | |
| 378 | 534 | pub fn dev_themes_dir(manifest_dir: &Path, levels: usize) -> Option<PathBuf> { | |
| 379 | 535 | let mut path = manifest_dir.to_path_buf(); | |
| 380 | 536 | for _ in 0..levels { | |
| @@ -393,6 +549,8 @@ mod tests { | |||
| 393 | 549 | use super::*; | |
| 394 | 550 | use std::fs; | |
| 395 | 551 |
Lines truncated
| @@ -7,24 +7,33 @@ | |||
| 7 | 7 | name = "audiofiles" | |
| 8 | 8 | variant = "light" | |
| 9 | 9 | ||
| 10 | - | [background] | |
| 11 | - | primary = "#CCDAD1" # Ash Grey — main canvas | |
| 12 | - | secondary = "#B4C5BB" # mid sage — headers / secondary panels | |
| 13 | - | tertiary = "#9CAEA9" # Ash Grey deep — selected / inset | |
| 14 | - | surface = "#DAE3DC" # lighter sage — raised cards/rows | |
| 10 | + | [surface] | |
| 11 | + | page = "#CCDAD1" | |
| 12 | + | raised = "#DAE3DC" | |
| 13 | + | sunken = "#9CAEA9" | |
| 14 | + | overlay = "#B4C5BB" | |
| 15 | 15 | ||
| 16 | - | [foreground] | |
| 17 | - | primary = "#38302E" # Deep Mocha — body text | |
| 18 | - | secondary = "#6F6866" # Dim Grey | |
| 19 | - | muted = "#788585" # Grey Olive — hints / disabled | |
| 16 | + | [content] | |
| 17 | + | primary = "#38302E" | |
| 18 | + | secondary = "#6F6866" | |
| 19 | + | muted = "#788585" | |
| 20 | 20 | ||
| 21 | - | [accent] | |
| 22 | - | red = "#B05F4E" # muted terracotta | |
| 23 | - | green = "#6F8A5C" # muted olive | |
| 24 | - | blue = "#5E7C8E" # muted slate | |
| 25 | - | yellow = "#C19A53" # muted ochre | |
| 26 | - | purple = "#836A80" # muted mauve | |
| 27 | - | cyan = "#5F8C82" # muted teal | |
| 21 | + | [action] | |
| 22 | + | primary = "#5E7C8E" | |
| 28 | 23 | ||
| 29 | - | [border] | |
| 30 | - | default = "#9CAEA9" | |
| 24 | + | [status] | |
| 25 | + | danger = "#B05F4E" | |
| 26 | + | success = "#6F8A5C" | |
| 27 | + | warning = "#C19A53" | |
| 28 | + | info = "#5F8C82" | |
| 29 | + | ||
| 30 | + | [line] | |
| 31 | + | border = "#9CAEA9" | |
| 32 | + | ||
| 33 | + | [category] | |
| 34 | + | one = "#B05F4E" | |
| 35 | + | two = "#6F8A5C" | |
| 36 | + | three = "#5E7C8E" | |
| 37 | + | four = "#C19A53" | |
| 38 | + | five = "#836A80" | |
| 39 | + | six = "#5F8C82" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Ayu Light" | |
| 6 | 6 | variant = "light" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#e7eaed" | |
| 10 | - | secondary = "#dde1e5" | |
| 11 | - | tertiary = "#d0d4d8" | |
| 12 | - | surface = "#f2f4f6" | |
| 8 | + | [surface] | |
| 9 | + | page = "#e7eaed" | |
| 10 | + | raised = "#f2f4f6" | |
| 11 | + | sunken = "#d0d4d8" | |
| 12 | + | overlay = "#dde1e5" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#5c6166" | |
| 14 | + | [content] | |
| 15 | + | primary = "#5c6166" | |
| 16 | 16 | secondary = "#6b7580" | |
| 17 | - | muted = "#8b9199" | |
| 17 | + | muted = "#8b9199" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#f07171" | |
| 21 | - | green = "#86b300" | |
| 22 | - | blue = "#399ee6" | |
| 23 | - | yellow = "#ffaa33" | |
| 24 | - | purple = "#a37acc" | |
| 25 | - | cyan = "#55b4d4" | |
| 19 | + | [action] | |
| 20 | + | primary = "#399ee6" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#828c9a" | |
| 22 | + | [status] | |
| 23 | + | danger = "#f07171" | |
| 24 | + | success = "#86b300" | |
| 25 | + | warning = "#ffaa33" | |
| 26 | + | info = "#55b4d4" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#828c9a" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#f07171" | |
| 33 | + | two = "#86b300" | |
| 34 | + | three = "#399ee6" | |
| 35 | + | four = "#ffaa33" | |
| 36 | + | five = "#a37acc" | |
| 37 | + | six = "#55b4d4" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Ayu Mirage" | |
| 6 | 6 | variant = "dark" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#1f2430" | |
| 10 | - | secondary = "#191e29" | |
| 11 | - | tertiary = "#272d38" | |
| 12 | - | surface = "#242936" | |
| 8 | + | [surface] | |
| 9 | + | page = "#1f2430" | |
| 10 | + | raised = "#242936" | |
| 11 | + | sunken = "#272d38" | |
| 12 | + | overlay = "#191e29" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#cccac2" | |
| 14 | + | [content] | |
| 15 | + | primary = "#cccac2" | |
| 16 | 16 | secondary = "#b8b4aa" | |
| 17 | - | muted = "#707a8c" | |
| 17 | + | muted = "#707a8c" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#f28779" | |
| 21 | - | green = "#bae67e" | |
| 22 | - | blue = "#5ccfe6" | |
| 23 | - | yellow = "#ffd580" | |
| 24 | - | purple = "#d4bfff" | |
| 25 | - | cyan = "#95e6cb" | |
| 19 | + | [action] | |
| 20 | + | primary = "#5ccfe6" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#33415e" | |
| 22 | + | [status] | |
| 23 | + | danger = "#f28779" | |
| 24 | + | success = "#bae67e" | |
| 25 | + | warning = "#ffd580" | |
| 26 | + | info = "#95e6cb" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#33415e" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#f28779" | |
| 33 | + | two = "#bae67e" | |
| 34 | + | three = "#5ccfe6" | |
| 35 | + | four = "#ffd580" | |
| 36 | + | five = "#d4bfff" | |
| 37 | + | six = "#95e6cb" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Carbonfox" | |
| 6 | 6 | variant = "dark" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#161616" | |
| 10 | - | secondary = "#101010" | |
| 11 | - | tertiary = "#1e1e1e" | |
| 12 | - | surface = "#252525" | |
| 8 | + | [surface] | |
| 9 | + | page = "#161616" | |
| 10 | + | raised = "#252525" | |
| 11 | + | sunken = "#1e1e1e" | |
| 12 | + | overlay = "#101010" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#f2f4f8" | |
| 14 | + | [content] | |
| 15 | + | primary = "#f2f4f8" | |
| 16 | 16 | secondary = "#dde1e6" | |
| 17 | - | muted = "#878d96" | |
| 17 | + | muted = "#878d96" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#ee5396" | |
| 21 | - | green = "#25be6a" | |
| 22 | - | blue = "#78a9ff" | |
| 23 | - | yellow = "#08bdba" | |
| 24 | - | purple = "#be95ff" | |
| 25 | - | cyan = "#33b1ff" | |
| 19 | + | [action] | |
| 20 | + | primary = "#78a9ff" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#393939" | |
| 22 | + | [status] | |
| 23 | + | danger = "#ee5396" | |
| 24 | + | success = "#25be6a" | |
| 25 | + | warning = "#08bdba" | |
| 26 | + | info = "#33b1ff" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#393939" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#ee5396" | |
| 33 | + | two = "#25be6a" | |
| 34 | + | three = "#78a9ff" | |
| 35 | + | four = "#08bdba" | |
| 36 | + | five = "#be95ff" | |
| 37 | + | six = "#33b1ff" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Catppuccin Frappé" | |
| 6 | 6 | variant = "dark" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#303446" | |
| 10 | - | secondary = "#292c3c" | |
| 11 | - | tertiary = "#414559" | |
| 12 | - | surface = "#232634" | |
| 8 | + | [surface] | |
| 9 | + | page = "#303446" | |
| 10 | + | raised = "#232634" | |
| 11 | + | sunken = "#414559" | |
| 12 | + | overlay = "#292c3c" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#c6d0f5" | |
| 14 | + | [content] | |
| 15 | + | primary = "#c6d0f5" | |
| 16 | 16 | secondary = "#b5bfe2" | |
| 17 | - | muted = "#a5adce" | |
| 17 | + | muted = "#a5adce" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#e78284" | |
| 21 | - | green = "#a6d189" | |
| 22 | - | blue = "#8caaee" | |
| 23 | - | yellow = "#e5c890" | |
| 24 | - | purple = "#ca9ee6" | |
| 25 | - | cyan = "#81c8be" | |
| 19 | + | [action] | |
| 20 | + | primary = "#8caaee" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#626880" | |
| 22 | + | [status] | |
| 23 | + | danger = "#e78284" | |
| 24 | + | success = "#a6d189" | |
| 25 | + | warning = "#e5c890" | |
| 26 | + | info = "#81c8be" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#626880" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#e78284" | |
| 33 | + | two = "#a6d189" | |
| 34 | + | three = "#8caaee" | |
| 35 | + | four = "#e5c890" | |
| 36 | + | five = "#ca9ee6" | |
| 37 | + | six = "#81c8be" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Catppuccin Latte" | |
| 6 | 6 | variant = "light" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#e6e9ef" | |
| 10 | - | secondary = "#dce0e8" | |
| 11 | - | tertiary = "#ccd0da" | |
| 12 | - | surface = "#eff1f5" | |
| 8 | + | [surface] | |
| 9 | + | page = "#e6e9ef" | |
| 10 | + | raised = "#eff1f5" | |
| 11 | + | sunken = "#ccd0da" | |
| 12 | + | overlay = "#dce0e8" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#4c4f69" | |
| 14 | + | [content] | |
| 15 | + | primary = "#4c4f69" | |
| 16 | 16 | secondary = "#5c5f77" | |
| 17 | - | muted = "#6c6f82" | |
| 17 | + | muted = "#6c6f82" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#d20f39" | |
| 21 | - | green = "#40a02b" | |
| 22 | - | blue = "#1e66f5" | |
| 23 | - | yellow = "#df8e1d" | |
| 24 | - | purple = "#8839ef" | |
| 25 | - | cyan = "#04a5e5" | |
| 19 | + | [action] | |
| 20 | + | primary = "#1e66f5" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#bcc0cc" | |
| 22 | + | [status] | |
| 23 | + | danger = "#d20f39" | |
| 24 | + | success = "#40a02b" | |
| 25 | + | warning = "#df8e1d" | |
| 26 | + | info = "#04a5e5" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#bcc0cc" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#d20f39" | |
| 33 | + | two = "#40a02b" | |
| 34 | + | three = "#1e66f5" | |
| 35 | + | four = "#df8e1d" | |
| 36 | + | five = "#8839ef" | |
| 37 | + | six = "#04a5e5" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Catppuccin Macchiato" | |
| 6 | 6 | variant = "dark" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#24273a" | |
| 10 | - | secondary = "#1e2030" | |
| 11 | - | tertiary = "#363a4f" | |
| 12 | - | surface = "#181926" | |
| 8 | + | [surface] | |
| 9 | + | page = "#24273a" | |
| 10 | + | raised = "#181926" | |
| 11 | + | sunken = "#363a4f" | |
| 12 | + | overlay = "#1e2030" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#cad3f5" | |
| 14 | + | [content] | |
| 15 | + | primary = "#cad3f5" | |
| 16 | 16 | secondary = "#b8c0e0" | |
| 17 | - | muted = "#a5adcb" | |
| 17 | + | muted = "#a5adcb" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#ed8796" | |
| 21 | - | green = "#a6da95" | |
| 22 | - | blue = "#8aadf4" | |
| 23 | - | yellow = "#eed49f" | |
| 24 | - | purple = "#c6a0f6" | |
| 25 | - | cyan = "#8bd5ca" | |
| 19 | + | [action] | |
| 20 | + | primary = "#8aadf4" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#5b6078" | |
| 22 | + | [status] | |
| 23 | + | danger = "#ed8796" | |
| 24 | + | success = "#a6da95" | |
| 25 | + | warning = "#eed49f" | |
| 26 | + | info = "#8bd5ca" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#5b6078" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#ed8796" | |
| 33 | + | two = "#a6da95" | |
| 34 | + | three = "#8aadf4" | |
| 35 | + | four = "#eed49f" | |
| 36 | + | five = "#c6a0f6" | |
| 37 | + | six = "#8bd5ca" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Catppuccin Mocha" | |
| 6 | 6 | variant = "dark" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#181825" | |
| 10 | - | secondary = "#11111b" | |
| 11 | - | tertiary = "#313244" | |
| 12 | - | surface = "#1e1e2e" | |
| 8 | + | [surface] | |
| 9 | + | page = "#181825" | |
| 10 | + | raised = "#1e1e2e" | |
| 11 | + | sunken = "#313244" | |
| 12 | + | overlay = "#11111b" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#cdd6f4" | |
| 14 | + | [content] | |
| 15 | + | primary = "#cdd6f4" | |
| 16 | 16 | secondary = "#bac2de" | |
| 17 | - | muted = "#9399b2" | |
| 17 | + | muted = "#9399b2" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#f38ba8" | |
| 21 | - | green = "#a6e3a1" | |
| 22 | - | blue = "#89b4fa" | |
| 23 | - | yellow = "#f9e2af" | |
| 24 | - | purple = "#cba6f7" | |
| 25 | - | cyan = "#89dceb" | |
| 19 | + | [action] | |
| 20 | + | primary = "#89b4fa" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#45475a" | |
| 22 | + | [status] | |
| 23 | + | danger = "#f38ba8" | |
| 24 | + | success = "#a6e3a1" | |
| 25 | + | warning = "#f9e2af" | |
| 26 | + | info = "#89dceb" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#45475a" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#f38ba8" | |
| 33 | + | two = "#a6e3a1" | |
| 34 | + | three = "#89b4fa" | |
| 35 | + | four = "#f9e2af" | |
| 36 | + | five = "#cba6f7" | |
| 37 | + | six = "#89dceb" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Dawnfox" | |
| 6 | 6 | variant = "light" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#faf4ed" | |
| 10 | - | secondary = "#ebe5df" | |
| 11 | - | tertiary = "#ebe0df" | |
| 12 | - | surface = "#f5efe8" | |
| 8 | + | [surface] | |
| 9 | + | page = "#faf4ed" | |
| 10 | + | raised = "#f5efe8" | |
| 11 | + | sunken = "#ebe0df" | |
| 12 | + | overlay = "#ebe5df" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#575279" | |
| 14 | + | [content] | |
| 15 | + | primary = "#575279" | |
| 16 | 16 | secondary = "#625c87" | |
| 17 | - | muted = "#a8a3b3" | |
| 17 | + | muted = "#a8a3b3" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#b4637a" | |
| 21 | - | green = "#618774" | |
| 22 | - | blue = "#286983" | |
| 23 | - | yellow = "#ea9d34" | |
| 24 | - | purple = "#907aa9" | |
| 25 | - | cyan = "#56949f" | |
| 19 | + | [action] | |
| 20 | + | primary = "#286983" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#bdbfc9" | |
| 22 | + | [status] | |
| 23 | + | danger = "#b4637a" | |
| 24 | + | success = "#618774" | |
| 25 | + | warning = "#ea9d34" | |
| 26 | + | info = "#56949f" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#bdbfc9" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#b4637a" | |
| 33 | + | two = "#618774" | |
| 34 | + | three = "#286983" | |
| 35 | + | four = "#ea9d34" | |
| 36 | + | five = "#907aa9" | |
| 37 | + | six = "#56949f" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Dracula" | |
| 6 | 6 | variant = "dark" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#222430" | |
| 10 | - | secondary = "#191A21" | |
| 11 | - | tertiary = "#44475a" | |
| 12 | - | surface = "#282A36" | |
| 8 | + | [surface] | |
| 9 | + | page = "#222430" | |
| 10 | + | raised = "#282A36" | |
| 11 | + | sunken = "#44475a" | |
| 12 | + | overlay = "#191A21" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#f8f8f2" | |
| 14 | + | [content] | |
| 15 | + | primary = "#f8f8f2" | |
| 16 | 16 | secondary = "#f8f8f2" | |
| 17 | - | muted = "#6272A4" | |
| 17 | + | muted = "#6272A4" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#ff5555" | |
| 21 | - | green = "#50fa7b" | |
| 22 | - | blue = "#8be9fd" | |
| 23 | - | yellow = "#f1fa8c" | |
| 24 | - | purple = "#BD93F9" | |
| 25 | - | cyan = "#8be9fd" | |
| 19 | + | [action] | |
| 20 | + | primary = "#8be9fd" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#44475a" | |
| 22 | + | [status] | |
| 23 | + | danger = "#ff5555" | |
| 24 | + | success = "#50fa7b" | |
| 25 | + | warning = "#f1fa8c" | |
| 26 | + | info = "#8be9fd" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#44475a" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#ff5555" | |
| 33 | + | two = "#50fa7b" | |
| 34 | + | three = "#8be9fd" | |
| 35 | + | four = "#f1fa8c" | |
| 36 | + | five = "#BD93F9" | |
| 37 | + | six = "#8be9fd" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Everforest" | |
| 6 | 6 | variant = "dark" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#2d353b" | |
| 10 | - | secondary = "#272e33" | |
| 11 | - | tertiary = "#3d484d" | |
| 12 | - | surface = "#343f44" | |
| 8 | + | [surface] | |
| 9 | + | page = "#2d353b" | |
| 10 | + | raised = "#343f44" | |
| 11 | + | sunken = "#3d484d" | |
| 12 | + | overlay = "#272e33" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#d3c6aa" | |
| 14 | + | [content] | |
| 15 | + | primary = "#d3c6aa" | |
| 16 | 16 | secondary = "#9da9a0" | |
| 17 | - | muted = "#7a8478" | |
| 17 | + | muted = "#7a8478" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#e67e80" | |
| 21 | - | green = "#a7c080" | |
| 22 | - | blue = "#7fbbb3" | |
| 23 | - | yellow = "#dbbc7f" | |
| 24 | - | purple = "#d699b6" | |
| 25 | - | cyan = "#83c092" | |
| 19 | + | [action] | |
| 20 | + | primary = "#7fbbb3" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#3d484d" | |
| 22 | + | [status] | |
| 23 | + | danger = "#e67e80" | |
| 24 | + | success = "#a7c080" | |
| 25 | + | warning = "#dbbc7f" | |
| 26 | + | info = "#83c092" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#3d484d" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#e67e80" | |
| 33 | + | two = "#a7c080" | |
| 34 | + | three = "#7fbbb3" | |
| 35 | + | four = "#dbbc7f" | |
| 36 | + | five = "#d699b6" | |
| 37 | + | six = "#83c092" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "Flatwhite" | |
| 6 | 6 | variant = "light" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#f1ece4" | |
| 10 | - | secondary = "#e4ddd2" | |
| 11 | - | tertiary = "#dcd3c6" | |
| 12 | - | surface = "#f7f3ee" | |
| 8 | + | [surface] | |
| 9 | + | page = "#f1ece4" | |
| 10 | + | raised = "#f7f3ee" | |
| 11 | + | sunken = "#dcd3c6" | |
| 12 | + | overlay = "#e4ddd2" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#605a52" | |
| 14 | + | [content] | |
| 15 | + | primary = "#605a52" | |
| 16 | 16 | secondary = "#786d5e" | |
| 17 | - | muted = "#9a8b78" | |
| 17 | + | muted = "#9a8b78" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#ff1414" | |
| 21 | - | green = "#2db448" | |
| 22 | - | blue = "#4c5361" | |
| 23 | - | yellow = "#f2a60d" | |
| 24 | - | purple = "#614c61" | |
| 25 | - | cyan = "#465953" | |
| 19 | + | [action] | |
| 20 | + | primary = "#4c5361" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#93836c" | |
| 22 | + | [status] | |
| 23 | + | danger = "#ff1414" | |
| 24 | + | success = "#2db448" | |
| 25 | + | warning = "#f2a60d" | |
| 26 | + | info = "#465953" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#93836c" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#ff1414" | |
| 33 | + | two = "#2db448" | |
| 34 | + | three = "#4c5361" | |
| 35 | + | four = "#f2a60d" | |
| 36 | + | five = "#614c61" | |
| 37 | + | six = "#465953" |
| @@ -5,24 +5,33 @@ | |||
| 5 | 5 | name = "GoingsOn" | |
| 6 | 6 | variant = "light" | |
| 7 | 7 | ||
| 8 | - | [background] | |
| 9 | - | primary = "#E0E4FA" | |
| 10 | - | secondary = "#CDD3F0" | |
| 11 | - | tertiary = "#BAC2E6" | |
| 12 | - | surface = "#FFFFFF" | |
| 8 | + | [surface] | |
| 9 | + | page = "#E0E4FA" | |
| 10 | + | raised = "#FFFFFF" | |
| 11 | + | sunken = "#BAC2E6" | |
| 12 | + | overlay = "#CDD3F0" | |
| 13 | 13 | ||
| 14 | - | [foreground] | |
| 15 | - | primary = "#000000" | |
| 14 | + | [content] | |
| 15 | + | primary = "#000000" | |
| 16 | 16 | secondary = "#2D2D2D" | |
| 17 | - | muted = "#6B6B6B" | |
| 17 | + | muted = "#6B6B6B" | |
| 18 | 18 | ||
| 19 | - | [accent] | |
| 20 | - | red = "#DC3545" | |
| 21 | - | green = "#5CB85C" | |
| 22 | - | blue = "#6196FF" | |
| 23 | - | yellow = "#F7D154" | |
| 24 | - | purple = "#7B68EE" | |
| 25 | - | cyan = "#17A2B8" | |
| 19 | + | [action] | |
| 20 | + | primary = "#6196FF" | |
| 26 | 21 | ||
| 27 | - | [border] | |
| 28 | - | default = "#000000" | |
| 22 | + | [status] | |
| 23 | + | danger = "#DC3545" | |
| 24 | + | success = "#5CB85C" | |
| 25 | + | warning = "#F7D154" | |
| 26 | + | info = "#17A2B8" | |
| 27 | + | ||
| 28 | + | [line] | |
| 29 | + | border = "#000000" | |
| 30 | + | ||
| 31 | + | [category] | |
| 32 | + | one = "#DC3545" | |
| 33 | + | two = "#5CB85C" | |
| 34 | + | three = "#6196FF" | |
| 35 | + | four = "#F7D154" | |
| 36 | + | five = "#7B68EE" | |
| 37 | + | six = "#17A2B8" |