diff --git a/Cargo.lock b/Cargo.lock index 1f7d3af..63d7693 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -536,6 +536,7 @@ version = "0.9.2" dependencies = [ "anyhow", "base64 0.23.0", + "colony-ui", "dirs", "ed25519-dalek", "flate2", @@ -559,6 +560,16 @@ dependencies = [ "zip", ] +[[package]] +name = "colony-ui" +version = "0.1.0" +source = "git+https://github.com/Project-Colony/Project-Colony-Resources?tag=v0.1.0#108ee8010997e69e06929ae84e112a4d8b90ae80" +dependencies = [ + "dirs", + "iced", + "serde_json", +] + [[package]] name = "combine" version = "4.6.7" @@ -3510,6 +3521,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ + "indexmap", "itoa", "memchr", "serde", diff --git a/Cargo.toml b/Cargo.toml index 8f02689..2100904 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,11 @@ rust-version = "1.80" [dependencies] anyhow = "1.0.100" +# The shared theme, palettes, accents, labels and widgets, generated from the +# design tokens in Project-Colony-Resources. Pinned to a tag rather than +# tracking main: a change upstream cannot break this build without a deliberate +# bump here. +colony-ui = { git = "https://github.com/Project-Colony/Project-Colony-Resources", tag = "v0.1.0" } iced = { version = "0.14.0", features = ["tokio", "markdown", "advanced", "image-without-codecs"] } shell-words = "1.1.1" serde = { version = "1.0.228", features = ["derive"] } diff --git a/docs/architecture.md b/docs/architecture.md index 7d5833f..907a2cd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -76,16 +76,17 @@ All async operations (API calls, downloads, scanning) return a `Task` t | Data | Location | Duration | |------|----------|----------| -| Colony repos (cache) | `~/.cache/colony/repos_cache.json` | Offline fallback | -| Scanned apps (cache) | `~/.cache/colony/scan_cache.json` | Session | -| Repo docs (cache) | `~/.cache/colony/docs//` | Offline fallback | -| Preferences | `~/.config/colony/preferences.json` | Permanent | -| Favorites | `~/.config/colony/favorites.json` | Permanent | -| OAuth token | OS Keychain / `~/.config/colony/github_token.json` | Permanent | +| Colony repos (cache) | `~/.cache/Colony/Colony/repos_cache.json` | Offline fallback | +| Scanned apps (cache) | `~/.cache/Colony/Colony/scan_cache.json` | Session | +| Repo docs (cache) | `~/.cache/Colony/Colony/repo-docs//` | Offline fallback | +| App icons (cache) | `~/.cache/Colony/Colony/repo-icons//icon.png` | Offline fallback | +| Preferences | `~/.config/Colony/Colony/preferences/preferences.json` | Permanent | +| Favorites | `~/.config/Colony/Colony/preferences/favorites.json` | Permanent | +| OAuth token | OS Keychain / `~/.config/Colony/Colony/auth/github_token.json` | Permanent | | Installed versions | `~/.local/share/Colony/apps//.colony_version` | Permanent | | Resolved asset | `~/.local/share/Colony/apps//.colony_asset` | Permanent | | Colony binaries | `~/.local/share/Colony/apps//` | Permanent | -| Self-update staging | `~/.local/share/Colony/update-staging/` | Temporary | +| Self-update staging | `~/.cache/Colony/Colony/update-staging/` | Temporary | ## GitHub API diff --git a/docs/faq.md b/docs/faq.md index 6cd7a45..96644fe 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -56,7 +56,7 @@ Open the app in Colony → detail view → **Uninstall**. Or delete the app's di ### How do I uninstall Colony itself? -AUR: `sudo pacman -R colony-bin` (or `colony-git`). Manual install: delete the binary plus `~/.config/colony/`, `~/.cache/colony/`, and `~/.local/share/Colony/`. +AUR: `sudo pacman -R colony-bin` (or `colony-git`). Manual install: delete the binary plus `~/.config/Colony/`, `~/.cache/Colony/`, and `~/.local/share/Colony/`. --- @@ -72,7 +72,7 @@ You get a 5000 req/h limit and Colony can read private repos you have access to ### Where is the token stored? -In your OS keychain (GNOME keyring, KWallet, macOS Keychain, Windows Credential Manager) via the [keyring](https://crates.io/crates/keyring) crate. If no keychain is available (headless Linux, CI) it falls back to `~/.config/colony/github_token.json` with `chmod 600`. +In your OS keychain (GNOME keyring, KWallet, macOS Keychain, Windows Credential Manager) via the [keyring](https://crates.io/crates/keyring) crate. If no keychain is available (headless Linux, CI) it falls back to `~/.config/Colony/Colony/auth/github_token.json` with `chmod 600`. ### How do I disconnect? @@ -93,7 +93,7 @@ Most common causes: 5. The repo is not under the `Project-Colony` org (Colony only scans that org). 6. GitHub rate limit hit — connect your account. -Cache file to inspect: `~/.cache/colony/repos_cache.json`. +Cache file to inspect: `~/.cache/Colony/Colony/repos_cache.json`. ### Does Colony support private repos? @@ -119,20 +119,26 @@ If your app is installed but not in any of these (portable binary without `.desk ### Where does Colony store its stuff? -| What | Path | -|----------------------------|---------------------------------------------------------| -| Preferences | `~/.config/colony/preferences.json` | -| Favorites | `~/.config/colony/favorites.json` | -| GitHub token (fallback) | `~/.config/colony/github_token.json` (`chmod 600`) | -| Installed app binaries | `~/.local/share/Colony/apps//` | -| Installed version marker | `~/.local/share/Colony/apps//.colony_version` | -| Resolved asset filename | `~/.local/share/Colony/apps//.colony_asset` | -| Repo list cache | `~/.cache/colony/repos_cache.json` | -| Manifest docs cache | `~/.cache/colony/docs//` | -| System app scan cache | `~/.cache/colony/scan_cache.json` | -| Self-update staging | `~/.local/share/Colony/update-staging/` | - -Purging the `~/.cache/colony/` directory forces a full re-scan and re-fetch. +| What | Path (Linux) | +|----------------------------|----------------------------------------------------------| +| Preferences | `~/.config/Colony/Colony/preferences/preferences.json` | +| Favorites | `~/.config/Colony/Colony/preferences/favorites.json` | +| GitHub token (fallback) | `~/.config/Colony/Colony/auth/github_token.json` (`600`) | +| Installed app binaries | `~/.local/share/Colony/apps//` | +| Installed version marker | `~/.local/share/Colony/apps//.colony_version` | +| Resolved asset filename | `~/.local/share/Colony/apps//.colony_asset` | +| Repo list cache | `~/.cache/Colony/Colony/repos_cache.json` | +| Manifest docs cache | `~/.cache/Colony/Colony/repo-docs//` | +| App icon cache | `~/.cache/Colony/Colony/repo-icons//icon.png` | +| System app scan cache | `~/.cache/Colony/Colony/scan_cache.json` | +| Self-update staging | `~/.cache/Colony/Colony/update-staging/` | + +On Windows all three roots are `%LOCALAPPDATA%\Colony\`, and on macOS config +and data share `~/Library/Application Support/Colony/` while the cache sits in +`~/Library/Caches/Colony/`. The layout is defined once in +[Project-Colony-Resources](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/design/filesystem.md). + +Purging the `~/.cache/Colony/` directory forces a full re-scan and re-fetch. ### Can I edit `preferences.json` by hand? @@ -140,7 +146,7 @@ Yes. Colony re-reads it on launch. The file is JSON with field names matching wh ### Can I override the scan directories? -Yes. Edit `~/.config/colony/preferences.json` and add custom paths under the platform-appropriate scan settings. The Settings UI also exposes this for Linux. +Yes. Edit `~/.config/Colony/Colony/preferences/preferences.json` and add custom paths under the platform-appropriate scan settings. The Settings UI also exposes this for Linux. ### I changed a theme and don't see it — why? @@ -161,8 +167,8 @@ colony 2>&1 | tee colony.log Common causes: - Missing Linux runtime libs (GTK, dbus, xdo) — on AUR the deps are pulled automatically. For manual downloads, install them via your package manager. -- Corrupted cache — `rm -rf ~/.cache/colony` and relaunch. -- Corrupted preferences — `mv ~/.config/colony/preferences.json ~/.config/colony/preferences.json.bak` and relaunch to regenerate defaults. +- Corrupted cache — `rm -rf ~/.cache/Colony` and relaunch. +- Corrupted preferences — `mv ~/.config/Colony/Colony/preferences/preferences.json{,.bak}` and relaunch to regenerate defaults. ### Download stuck / very slow diff --git a/docs/tutorial.md b/docs/tutorial.md index 92dc8f2..35fb3b3 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -151,5 +151,5 @@ upgraded alongside the rest of your system. [colony-spec.md](colony-spec.md) for the full manifest reference. - **Rate limit hit?** Connect a GitHub account (section 6). - **Download fails?** Check your network. Colony retries automatically; logs - go to `~/.cache/colony/` if you need to dig in. + go to `~/.cache/Colony/Colony/` if you need to dig in. - **More questions?** See the [FAQ](faq.md). diff --git a/src/download.rs b/src/download.rs index 04f384d..ba32269 100644 --- a/src/download.rs +++ b/src/download.rs @@ -10,7 +10,7 @@ use std::path::PathBuf; use std::time::Duration; use crate::github::{APP_VERSION, CONNECT_TIMEOUT, GITHUB_ACCOUNT, LAUNCHER_OWNER, LAUNCHER_REPO}; -use crate::persistence::colony_data_dir; +use crate::persistence::colony_cache_dir; /// Build the HTTP client used for large asset downloads (longer read timeout /// than the API client). @@ -555,7 +555,8 @@ pub async fn download_launcher_asset( filename: String, progress_tx: Option)>>, ) -> Result { - let temp_dir = colony_data_dir()?.join("update-staging"); + // Transient by definition: staging lives in the cache. + let temp_dir = colony_cache_dir()?.join("update-staging"); std::fs::create_dir_all(&temp_dir)?; let dest_path = temp_dir.join(&filename); diff --git a/src/i18n/en.rs b/src/i18n/en.rs index b6efead..2f0bef2 100644 --- a/src/i18n/en.rs +++ b/src/i18n/en.rs @@ -376,77 +376,9 @@ pub(super) fn insert_all(strings: &mut HashMap) { "Overall application appearance.".into(), ); strings.insert("settings_theme_dark".into(), "Dark".into()); - // Theme families - strings.insert("settings_theme_catppuccin".into(), "Catppuccin".into()); - strings.insert("settings_theme_catppuccin_latte".into(), "Latte".into()); - strings.insert("settings_theme_catppuccin_frappe".into(), "Frappé".into()); - strings.insert( - "settings_theme_catppuccin_macchiato".into(), - "Macchiato".into(), - ); - strings.insert("settings_theme_catppuccin_mocha".into(), "Mocha".into()); - strings.insert("settings_theme_gruvbox".into(), "Gruvbox".into()); - strings.insert("settings_theme_light".into(), "Light mode".into()); - strings.insert("settings_theme_dark_mode".into(), "Dark mode".into()); - strings.insert("settings_theme_everblush".into(), "Everblush".into()); - strings.insert("settings_theme_kanagawa".into(), "Kanagawa".into()); - strings.insert( - "settings_theme_kanagawa_journal".into(), - "Journal mode".into(), - ); - // New theme families - strings.insert("settings_theme_nord".into(), "Nord".into()); - strings.insert("settings_theme_dracula".into(), "Dracula".into()); - strings.insert("settings_theme_solarized".into(), "Solarized".into()); - strings.insert("settings_theme_tokyonight".into(), "Tokyo Night".into()); - strings.insert("settings_theme_tokyonight_night".into(), "Night".into()); - strings.insert("settings_theme_tokyonight_day".into(), "Day".into()); - strings.insert("settings_theme_rosepine".into(), "Rosé Pine".into()); - strings.insert("settings_theme_rosepine_main".into(), "Main".into()); - strings.insert("settings_theme_rosepine_moon".into(), "Moon".into()); - strings.insert("settings_theme_rosepine_dawn".into(), "Dawn".into()); - strings.insert("settings_theme_onedark".into(), "One Dark".into()); - strings.insert("settings_theme_monokai".into(), "Monokai Pro".into()); - strings.insert("settings_theme_monokai_pro".into(), "Pro".into()); - strings.insert("settings_theme_monokai_classic".into(), "Classic".into()); - strings.insert("settings_theme_monokai_spectrum".into(), "Spectrum".into()); - strings.insert("settings_theme_ayu".into(), "Ayu".into()); - strings.insert("settings_theme_ayu_mirage".into(), "Mirage".into()); - strings.insert("settings_theme_everforest".into(), "Everforest".into()); - strings.insert("settings_theme_material".into(), "Material".into()); - strings.insert("settings_theme_material_oceanic".into(), "Oceanic".into()); - strings.insert( - "settings_theme_material_palenight".into(), - "Palenight".into(), - ); - strings.insert( - "settings_theme_material_deepocean".into(), - "Deep Ocean".into(), - ); - strings.insert("settings_theme_flexoki".into(), "Flexoki".into()); - strings.insert("settings_theme_nightfox".into(), "Nightfox".into()); - strings.insert("settings_theme_nightfox_nightfox".into(), "Nightfox".into()); - strings.insert("settings_theme_nightfox_dawnfox".into(), "Dawnfox".into()); - strings.insert("settings_theme_sonokai".into(), "Sonokai".into()); - strings.insert("settings_theme_sonokai_default".into(), "Default".into()); - strings.insert("settings_theme_oxocarbon".into(), "Oxocarbon".into()); - strings.insert("settings_theme_nightowl".into(), "Night Owl".into()); - strings.insert("settings_theme_iceberg".into(), "Iceberg".into()); - strings.insert("settings_theme_horizon".into(), "Horizon".into()); - strings.insert("settings_theme_melange".into(), "Melange".into()); - strings.insert("settings_theme_synthwave".into(), "Synthwave '84".into()); - strings.insert("settings_theme_modus".into(), "Modus".into()); - strings.insert("settings_theme_modus_operandi".into(), "Operandi".into()); - strings.insert("settings_theme_modus_vivendi".into(), "Vivendi".into()); - strings.insert( - "settings_theme_stellar_blade".into(), - "Stellar Blade".into(), - ); - strings.insert("settings_theme_stellar_blade_eve".into(), "EVE".into()); - strings.insert("settings_theme_stellar_blade_tachy".into(), "Tachy".into()); - strings.insert("settings_theme_stellar_blade_lily".into(), "Lily".into()); - strings.insert("settings_theme_stellar_blade_enya".into(), "Enya".into()); - strings.insert("settings_theme_stellar_blade_kaya".into(), "Kaya".into()); + // The theme family and variant names are NOT here: they come from + // colony-ui, generated from the design tokens. A family added upstream + // must not need a line in this file - see `Locale::new`. // Colors & accents strings.insert("settings_section_colors".into(), "Colors & accents".into()); strings.insert( @@ -458,14 +390,6 @@ pub(super) fn insert_all(strings: &mut HashMap) { "settings_accent_color_desc".into(), "Color used for interactive elements.".into(), ); - strings.insert("settings_accent_red".into(), "Red".into()); - strings.insert("settings_accent_orange".into(), "Orange".into()); - strings.insert("settings_accent_yellow".into(), "Yellow".into()); - strings.insert("settings_accent_green".into(), "Green".into()); - strings.insert("settings_accent_blue".into(), "Blue".into()); - strings.insert("settings_accent_indigo".into(), "Indigo".into()); - strings.insert("settings_accent_violet".into(), "Violet".into()); - strings.insert("settings_accent_amber".into(), "Amber".into()); strings.insert( "settings_auto_accent".into(), "Auto accent from background".into(), diff --git a/src/i18n/fr.rs b/src/i18n/fr.rs index 85263fe..b2fd5ff 100644 --- a/src/i18n/fr.rs +++ b/src/i18n/fr.rs @@ -417,77 +417,9 @@ pub(super) fn insert_all(strings: &mut HashMap) { "Apparence globale de l'application.".into(), ); strings.insert("settings_theme_dark".into(), "Sombre".into()); - // Theme families - strings.insert("settings_theme_catppuccin".into(), "Catppuccin".into()); - strings.insert("settings_theme_catppuccin_latte".into(), "Latte".into()); - strings.insert("settings_theme_catppuccin_frappe".into(), "Frappé".into()); - strings.insert( - "settings_theme_catppuccin_macchiato".into(), - "Macchiato".into(), - ); - strings.insert("settings_theme_catppuccin_mocha".into(), "Mocha".into()); - strings.insert("settings_theme_gruvbox".into(), "Gruvbox".into()); - strings.insert("settings_theme_light".into(), "Mode clair".into()); - strings.insert("settings_theme_dark_mode".into(), "Mode sombre".into()); - strings.insert("settings_theme_everblush".into(), "Everblush".into()); - strings.insert("settings_theme_kanagawa".into(), "Kanagawa".into()); - strings.insert( - "settings_theme_kanagawa_journal".into(), - "Mode journal".into(), - ); - // New theme families - strings.insert("settings_theme_nord".into(), "Nord".into()); - strings.insert("settings_theme_dracula".into(), "Dracula".into()); - strings.insert("settings_theme_solarized".into(), "Solarized".into()); - strings.insert("settings_theme_tokyonight".into(), "Tokyo Night".into()); - strings.insert("settings_theme_tokyonight_night".into(), "Nuit".into()); - strings.insert("settings_theme_tokyonight_day".into(), "Jour".into()); - strings.insert("settings_theme_rosepine".into(), "Rosé Pine".into()); - strings.insert("settings_theme_rosepine_main".into(), "Principal".into()); - strings.insert("settings_theme_rosepine_moon".into(), "Lune".into()); - strings.insert("settings_theme_rosepine_dawn".into(), "Aurore".into()); - strings.insert("settings_theme_onedark".into(), "One Dark".into()); - strings.insert("settings_theme_monokai".into(), "Monokai Pro".into()); - strings.insert("settings_theme_monokai_pro".into(), "Pro".into()); - strings.insert("settings_theme_monokai_classic".into(), "Classic".into()); - strings.insert("settings_theme_monokai_spectrum".into(), "Spectrum".into()); - strings.insert("settings_theme_ayu".into(), "Ayu".into()); - strings.insert("settings_theme_ayu_mirage".into(), "Mirage".into()); - strings.insert("settings_theme_everforest".into(), "Everforest".into()); - strings.insert("settings_theme_material".into(), "Material".into()); - strings.insert("settings_theme_material_oceanic".into(), "Oceanic".into()); - strings.insert( - "settings_theme_material_palenight".into(), - "Palenight".into(), - ); - strings.insert( - "settings_theme_material_deepocean".into(), - "Deep Ocean".into(), - ); - strings.insert("settings_theme_flexoki".into(), "Flexoki".into()); - strings.insert("settings_theme_nightfox".into(), "Nightfox".into()); - strings.insert("settings_theme_nightfox_nightfox".into(), "Nightfox".into()); - strings.insert("settings_theme_nightfox_dawnfox".into(), "Dawnfox".into()); - strings.insert("settings_theme_sonokai".into(), "Sonokai".into()); - strings.insert("settings_theme_sonokai_default".into(), "Défaut".into()); - strings.insert("settings_theme_oxocarbon".into(), "Oxocarbon".into()); - strings.insert("settings_theme_nightowl".into(), "Night Owl".into()); - strings.insert("settings_theme_iceberg".into(), "Iceberg".into()); - strings.insert("settings_theme_horizon".into(), "Horizon".into()); - strings.insert("settings_theme_melange".into(), "Mélange".into()); - strings.insert("settings_theme_synthwave".into(), "Synthwave '84".into()); - strings.insert("settings_theme_modus".into(), "Modus".into()); - strings.insert("settings_theme_modus_operandi".into(), "Operandi".into()); - strings.insert("settings_theme_modus_vivendi".into(), "Vivendi".into()); - strings.insert( - "settings_theme_stellar_blade".into(), - "Stellar Blade".into(), - ); - strings.insert("settings_theme_stellar_blade_eve".into(), "EVE".into()); - strings.insert("settings_theme_stellar_blade_tachy".into(), "Tachy".into()); - strings.insert("settings_theme_stellar_blade_lily".into(), "Lily".into()); - strings.insert("settings_theme_stellar_blade_enya".into(), "Enya".into()); - strings.insert("settings_theme_stellar_blade_kaya".into(), "Kaya".into()); + // The theme family and variant names are NOT here: they come from + // colony-ui, generated from the design tokens. A family added upstream + // must not need a line in this file - see `Locale::new`. // Colors & accents strings.insert( "settings_section_colors".into(), @@ -502,14 +434,6 @@ pub(super) fn insert_all(strings: &mut HashMap) { "settings_accent_color_desc".into(), "Couleur utilisée pour les éléments interactifs.".into(), ); - strings.insert("settings_accent_red".into(), "Rouge".into()); - strings.insert("settings_accent_orange".into(), "Orange".into()); - strings.insert("settings_accent_yellow".into(), "Jaune".into()); - strings.insert("settings_accent_green".into(), "Vert".into()); - strings.insert("settings_accent_blue".into(), "Bleu".into()); - strings.insert("settings_accent_indigo".into(), "Indigo".into()); - strings.insert("settings_accent_violet".into(), "Violet".into()); - strings.insert("settings_accent_amber".into(), "Ambre".into()); strings.insert( "settings_auto_accent".into(), "Accent automatique selon le fond".into(), diff --git a/src/i18n/mod.rs b/src/i18n/mod.rs index 815f7c9..636900d 100644 --- a/src/i18n/mod.rs +++ b/src/i18n/mod.rs @@ -15,6 +15,26 @@ impl Locale { fn new(lang: &str) -> Self { let mut strings = HashMap::new(); + // The shared vocabulary FIRST: theme families, theme variants and accent + // names, generated from the design tokens in Project-Colony-Resources. + // + // They used to be copied into `fr.rs` and `en.rs` by hand, which made + // the picker's promise false: `THEME_FAMILIES` renders whatever the + // catalog holds, so a family added upstream reached the screen with no + // code change here - and then displayed its raw key, because no hand + // written line existed to name it. Seeding from the crate closes that. + // + // Colony's own strings load second and would win a collision, but + // `no_shared_key_is_redefined_locally` fails if one ever exists: an + // override is how the drift this deletion removed would come back. + let shared = match lang { + "fr" => colony_ui::i18n::Locale::Fr, + _ => colony_ui::i18n::Locale::En, + }; + strings.extend( + colony_ui::i18n::all(shared).map(|(k, v)| (k.to_string(), v.to_string())), + ); + match lang { "fr" => fr::insert_all(&mut strings), _ => en::insert_all(&mut strings), @@ -50,6 +70,10 @@ pub fn set_language(lang: &str) { "en" }; tracing::info!("Locale: {lang}"); + // The shared widgets call `colony_ui::i18n::t` directly - they cannot reach + // Colony's table - so the crate's own active locale has to move in step or + // the theme picker stays English while the rest of the page turns French. + colony_ui::i18n::set_locale(colony_ui::i18n::Locale::from_tag(lang)); if let Ok(mut locale) = LOCALE.write() { *locale = Some(Locale::new(lang)); } @@ -134,6 +158,19 @@ fn detect_language() -> String { mod tests { use super::*; + /// The active locale is process-wide, so a test that SETS it and then reads + /// it back cannot run beside another that sets it too - the second write + /// lands between the first test's write and its assertion. Every test that + /// calls `set_language` takes this first. + /// + /// The lock is poisoned by a failing test, which would cascade into a second + /// misleading failure, so the guard is taken through `unwrap_or_else`. + static LOCALE: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + fn locale_guard() -> std::sync::MutexGuard<'static, ()> { + LOCALE.lock().unwrap_or_else(|e| e.into_inner()) + } + #[test] fn english_locale_has_keys() { let locale = Locale::new("en"); @@ -177,8 +214,110 @@ mod tests { ); } + /// Colony's own locale files must not carry a string colony-ui already + /// ships. They did - all 62 of them, in both locales - and the copies had + /// not drifted *yet*, which is the only reason the deletion was safe. + /// + /// `Locale::new` loads Colony's table second, so a re-added line would win + /// silently and put the drift back. This is what makes that loud. + #[test] + fn no_shared_key_is_redefined_locally() { + for (lang, insert_all) in [ + ("fr", fr::insert_all as fn(&mut HashMap)), + ("en", en::insert_all as fn(&mut HashMap)), + ] { + let mut own = HashMap::new(); + insert_all(&mut own); + + let shared = colony_ui::i18n::Locale::from_tag(lang); + let clashes: Vec<&str> = colony_ui::i18n::all(shared) + .map(|(k, _)| k) + .filter(|k| own.contains_key(*k)) + .collect(); + + assert!( + clashes.is_empty(), + "{lang}.rs redefines strings colony-ui already ships: {clashes:?}. \ + Delete them - the shared table is seeded first in Locale::new." + ); + } + } + + /// The point of the deletion: the names still reach the screen, localized, + /// through exactly the same `t()` every call site already uses. + #[test] + fn shared_theme_and_accent_labels_still_resolve() { + let fr = Locale::new("fr"); + let en = Locale::new("en"); + + // Translated. + assert_eq!(fr.strings.get("settings_accent_red").unwrap(), "Rouge"); + assert_eq!(en.strings.get("settings_accent_red").unwrap(), "Red"); + assert_eq!(fr.strings.get("settings_theme_light").unwrap(), "Mode clair"); + + // A proper noun, identical in both - and never hand-typed again. + assert_eq!(fr.strings.get("settings_theme_gruvbox").unwrap(), "Gruvbox"); + assert_eq!(en.strings.get("settings_theme_gruvbox").unwrap(), "Gruvbox"); + } + + /// A family added upstream must reach the picker NAMED. Before the seeding + /// it reached it as its raw key, because naming it took a hand-written line + /// in a file the upstream change never touched. + #[test] + fn every_catalog_entry_has_a_name_in_both_locales() { + let fr = Locale::new("fr"); + let en = Locale::new("en"); + + for family in colony_ui::THEME_FAMILIES { + for key in std::iter::once(family.label_key) + .chain(family.variants.iter().map(|v| v.label_key)) + { + for (lang, locale) in [("fr", &fr), ("en", &en)] { + let name = locale.strings.get(key); + assert!( + name.is_some_and(|n| n != key), + "{lang}: theme catalog key {key} would render as itself" + ); + } + } + } + } + + /// Same for the accents, whose order is load-bearing (each app's identity + /// tint is a hash bucketed into this list). + #[test] + fn every_accent_has_a_name_in_both_locales() { + let fr = Locale::new("fr"); + let en = Locale::new("en"); + for accent in colony_ui::ACCENT_OVERRIDES { + for (lang, locale) in [("fr", &fr), ("en", &en)] { + assert!( + locale.strings.contains_key(accent.label_key), + "{lang}: accent {} has no name", accent.key + ); + } + } + } + + /// `set_language` has to move BOTH tables. The shared widgets never touch + /// Colony's, so if this regresses the theme picker silently stays English. + #[test] + fn set_language_moves_the_shared_locale_too() { + let _guard = locale_guard(); + + set_language("fr"); + assert_eq!(colony_ui::i18n::locale(), colony_ui::i18n::Locale::Fr); + assert_eq!(colony_ui::i18n::t("settings_accent_red"), "Rouge"); + + set_language("en"); + assert_eq!(colony_ui::i18n::locale(), colony_ui::i18n::Locale::En); + assert_eq!(colony_ui::i18n::t("settings_accent_red"), "Red"); + } + #[test] fn t_fmt_substitution() { + let _guard = locale_guard(); + // Initialize with English for test set_language("en"); let result = t_fmt("apps_found", &[("count", "42")]); diff --git a/src/main.rs b/src/main.rs index ab03270..5eb497f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,10 @@ pub fn main() -> iced::Result { ) .init(); + // Earlier versions wrote to different directories; move them before + // anything reads a path. No-op once done, and on a fresh install. + crate::persistence::migrate_legacy_paths(); + // Honor the saved language preference over environment locale detection, // and reopen at the last persisted window size (clamped to sanity). let prefs = crate::persistence::load_preferences(); diff --git a/src/persistence.rs b/src/persistence.rs index 10afdc4..a64139c 100644 --- a/src/persistence.rs +++ b/src/persistence.rs @@ -4,18 +4,112 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::github::{current_platform_key, ColonyRepo}; -/// Central data directory for all Colony files: `~/.config/Colony/Colony/` +/// Colony's own name in the shared `Colony//` tree. +const PROGRAM: &str = "Colony"; + +/// Central config directory for Colony's own files. +/// +/// The layout — which root on which platform — is defined once in colony-ui; +/// see `design/filesystem.md` in Project-Colony-Resources. On Linux this is +/// `~/.config/Colony/Colony/`. pub fn colony_data_dir() -> Result { - let base = dirs::config_dir() - .ok_or_else(|| anyhow::anyhow!("No config directory"))? - .join("Colony") - .join("Colony"); - std::fs::create_dir_all(&base)?; - Ok(base) + Ok(colony_ui::paths::config_dir(PROGRAM)?) +} + +/// Regenerable state: the repo listing and the scan results. +pub(crate) fn colony_cache_dir() -> Result { + Ok(colony_ui::paths::cache_dir(PROGRAM)?) +} + +/// Move state written by earlier versions to where the shared layout puts it. +/// +/// Must run before anything reads a path — `main` calls it first. +/// +/// Deliberately conservative: it only moves when the old location exists and +/// the new one does not, and it **never deletes the source**. A user who ends +/// up with a copy in both places has lost nothing; a user whose preferences +/// were deleted by a half-finished migration has. +pub fn migrate_legacy_paths() { + // Windows used to resolve config to Roaming (dirs::config_dir), and the + // layout is Local. Identical on Linux and macOS, so this is a no-op there. + if let (Some(legacy_root), Ok(current)) = ( + dirs::config_dir(), + colony_ui::paths::locate::config_dir(PROGRAM), + ) { + relocate( + &legacy_root.join("Colony").join(PROGRAM), + ¤t, + "config directory", + ); + } + + // Everything regenerable used to live inside the config directory. All of + // it is re-fetched when missing, so a failure here costs a round trip to + // GitHub and nothing more. + if let (Ok(config), Ok(cache)) = ( + colony_ui::paths::locate::config_dir(PROGRAM), + colony_ui::paths::locate::cache_dir(PROGRAM), + ) { + relocate(&config.join("cache"), &cache, "cache"); + for sub in ["repo-docs", "repo-icons", "update-staging"] { + relocate(&config.join(sub), &cache.join(sub), sub); + } + } +} + +/// Move `from` to `to`, once, without ever destroying `from`. +fn relocate(from: &Path, to: &Path, what: &str) { + if from == to || !from.is_dir() || to.exists() { + return; + } + let Some(parent) = to.parent() else { return }; + if let Err(e) = std::fs::create_dir_all(parent) { + tracing::warn!("cannot prepare {} for the {what}: {e}", parent.display()); + return; + } + + // A rename is atomic and cheap, but fails across filesystems (EXDEV) — and + // ~/.config and ~/.cache are not guaranteed to be on the same one. + if std::fs::rename(from, to).is_ok() { + tracing::info!("moved the {what} to {}", to.display()); + return; + } + match copy_tree(from, to) { + Ok(()) => tracing::info!( + "copied the {what} to {}; the old copy at {} is left in place and can be deleted by hand", + to.display(), + from.display() + ), + Err(e) => { + // Leave no half-copied directory behind: the next start would see + // `to` existing and skip the migration, stranding the real data. + let _ = std::fs::remove_dir_all(to); + tracing::error!( + "could not move the {what} from {}: {e}. Nothing was lost — the old location still holds it — but Colony will start with an empty one.", + from.display() + ); + } + } +} + +/// Recursive copy. Files and directories only; anything else is skipped. +fn copy_tree(from: &Path, to: &Path) -> std::io::Result<()> { + std::fs::create_dir_all(to)?; + for entry in std::fs::read_dir(from)? { + let entry = entry?; + let target = to.join(entry.file_name()); + let kind = entry.file_type()?; + if kind.is_dir() { + copy_tree(&entry.path(), &target)?; + } else if kind.is_file() { + std::fs::copy(entry.path(), &target)?; + } + } + Ok(()) } /// Join a repo name onto `base` as a single directory component. @@ -36,9 +130,10 @@ pub(crate) fn colony_app_dir(repo_name: &str) -> Result { join_repo_component(colony_apps_dir()?, repo_name) } -/// Directory for cached repo documentation files: `~/.config/Colony/Colony/repo-docs/{repo_name}/` +/// Cached repo documentation: `/repo-docs/{repo_name}/`. Re-fetched from +/// GitHub when missing, so it is cache rather than config. fn repo_docs_dir(repo_name: &str) -> Result { - let base = join_repo_component(colony_data_dir()?.join("repo-docs"), repo_name)?; + let base = join_repo_component(colony_cache_dir()?.join("repo-docs"), repo_name)?; std::fs::create_dir_all(&base)?; Ok(base) } @@ -56,9 +151,10 @@ pub fn read_repo_doc(repo_name: &str, filename: &str) -> Option { std::fs::read_to_string(dir.join(filename)).ok() } -/// Directory for the cached per-repo app icon: `~/.config/Colony/Colony/repo-icons/{repo_name}/` +/// Cached per-repo app icon: `/repo-icons/{repo_name}/`. Re-downloaded +/// when missing, so it is cache rather than config. fn repo_icon_dir(repo_name: &str) -> Result { - let base = join_repo_component(colony_data_dir()?.join("repo-icons"), repo_name)?; + let base = join_repo_component(colony_cache_dir()?.join("repo-icons"), repo_name)?; std::fs::create_dir_all(&base)?; Ok(base) } @@ -76,11 +172,13 @@ pub fn load_repo_icon(repo_name: &str) -> Option> { std::fs::read(dir.join("icon.png")).ok() } -/// Return the Colony apps directory: `/Colony/apps/` +/// The shared install root: `/Colony/apps/`. +/// +/// Deliberately a sibling of Colony's own directory rather than a child — +/// installed programs belong to the ecosystem, not to the launcher. Does not +/// create the directory; callers that write do that themselves. pub fn colony_apps_dir() -> Result { - let base = dirs::data_local_dir() - .ok_or_else(|| anyhow::anyhow!("Cannot determine local data directory"))?; - Ok(base.join("Colony").join("apps")) + Ok(colony_ui::paths::locate::apps_dir()?) } /// Check if a Colony app is installed for the current platform. @@ -199,9 +297,7 @@ pub fn load_installed_asset(repo_name: &str) -> Option { } fn repos_cache_path() -> Result { - let cache_dir = colony_data_dir()?.join("cache"); - std::fs::create_dir_all(&cache_dir)?; - Ok(cache_dir.join("repos_cache.json")) + Ok(colony_cache_dir()?.join("repos_cache.json")) } /// Save Colony repos to local cache for offline use. @@ -300,9 +396,7 @@ pub fn save_preferences(prefs: &UserPreferences) -> Result<()> { } fn scan_cache_path() -> Result { - let cache_dir = colony_data_dir()?.join("cache"); - std::fs::create_dir_all(&cache_dir)?; - Ok(cache_dir.join("scan_cache.json")) + Ok(colony_cache_dir()?.join("scan_cache.json")) } /// Cached scan entry. @@ -414,7 +508,7 @@ fn desktop_entry_filename(repo_name: &str) -> Result { /// management from Settings > Storage; installs and preferences are NOT /// touched. Returns the number of cache directories removed. pub fn clear_store_caches() -> usize { - let Ok(base) = colony_data_dir() else { + let Ok(base) = colony_cache_dir() else { return 0; }; let mut removed = 0; @@ -437,7 +531,7 @@ pub fn clear_store_caches() -> usize { /// transient absence must not purge anything). Uninstalling a still-listed /// app deliberately keeps its caches - they render the catalog entry. pub fn prune_orphaned_repo_caches(live_repo_names: &[String]) { - let Ok(base) = colony_data_dir() else { + let Ok(base) = colony_cache_dir() else { return; }; for parent in ["repo-docs", "repo-icons"] { @@ -549,3 +643,147 @@ mod tests { assert_eq!(loaded.first_launch_done, Some(true)); } } + +#[cfg(test)] +mod path_migration_tests { + use super::*; + + fn scratch(name: &str) -> PathBuf { + let dir = + std::env::temp_dir().join(format!("colony_migrate_{name}_{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).expect("scratch dir"); + dir + } + + fn seed(dir: &Path, rel: &str, contents: &str) { + let path = dir.join(rel); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(path, contents).unwrap(); + } + + #[test] + fn moves_a_legacy_directory_and_keeps_its_contents() { + let root = scratch("moves"); + let (from, to) = (root.join("old"), root.join("new")); + seed( + &from, + "preferences/preferences.json", + "{\"theme\":\"gruvbox\"}", + ); + seed(&from, "auth/github_token.json", "token"); + + relocate(&from, &to, "config directory"); + + assert!(!from.exists(), "the source should have been renamed away"); + assert_eq!( + std::fs::read_to_string(to.join("preferences/preferences.json")).unwrap(), + "{\"theme\":\"gruvbox\"}" + ); + assert!(to.join("auth/github_token.json").exists()); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn never_clobbers_an_existing_destination() { + let root = scratch("clobber"); + let (from, to) = (root.join("old"), root.join("new")); + seed(&from, "preferences.json", "old"); + seed(&to, "preferences.json", "current"); + + relocate(&from, &to, "config directory"); + + // The current data wins and the old copy is left untouched, not merged. + assert_eq!( + std::fs::read_to_string(to.join("preferences.json")).unwrap(), + "current" + ); + assert_eq!( + std::fs::read_to_string(from.join("preferences.json")).unwrap(), + "old" + ); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn does_nothing_on_a_fresh_install_or_a_second_run() { + let root = scratch("noop"); + let (from, to) = (root.join("absent"), root.join("new")); + + relocate(&from, &to, "config directory"); + + assert!(!to.exists(), "nothing to migrate should create nothing"); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn a_source_equal_to_the_destination_is_left_alone() { + // This is the Linux case for the config directory: the old and new + // resolvers return the same path, so the migration must be inert. + let root = scratch("same"); + let dir = root.join("config"); + seed(&dir, "preferences.json", "kept"); + + relocate(&dir, &dir, "config directory"); + + assert_eq!( + std::fs::read_to_string(dir.join("preferences.json")).unwrap(), + "kept" + ); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn copy_tree_reproduces_nested_contents() { + let root = scratch("copy"); + let (from, to) = (root.join("src"), root.join("dst")); + seed(&from, "a.json", "a"); + seed(&from, "deep/b.json", "b"); + seed(&from, "deep/deeper/c.json", "c"); + + copy_tree(&from, &to).expect("copy"); + + for (rel, want) in [ + ("a.json", "a"), + ("deep/b.json", "b"), + ("deep/deeper/c.json", "c"), + ] { + assert_eq!( + std::fs::read_to_string(to.join(rel)).unwrap(), + want, + "{rel}" + ); + } + // The source survives a copy — that is the whole point of the fallback. + assert!(from.join("a.json").exists()); + let _ = std::fs::remove_dir_all(&root); + } + + #[test] + fn colony_lives_under_the_shared_tree() { + let config = colony_ui::paths::locate::config_dir(PROGRAM).unwrap(); + assert!( + config.ends_with("Colony/Colony") || config.ends_with("Colony\\Colony"), + "{config:?}" + ); + + // apps/ is a SIBLING of Colony's own data directory, not a child of + // it: uninstalling the launcher must not look like it takes the + // installed programs with it. + let apps = colony_apps_dir().unwrap(); + let data = colony_ui::paths::locate::data_dir(PROGRAM).unwrap(); + assert!( + apps.ends_with("Colony/apps") || apps.ends_with("Colony\\apps"), + "{apps:?}" + ); + assert!( + !apps.starts_with(&data), + "installed programs must not live inside Colony's own directory" + ); + assert_eq!( + apps.parent(), + data.parent(), + "apps/ and Colony/ share a parent" + ); + } +} diff --git a/src/state.rs b/src/state.rs index 3ad2c85..40bd370 100644 --- a/src/state.rs +++ b/src/state.rs @@ -416,6 +416,21 @@ impl App { (base as f32 * self.font_scale()).round() } + /// What the shared colony-ui widgets need to know about this program's text. + /// + /// A widget in that crate cannot reach `App`, so the three fonts and the + /// combined scale are handed over explicitly. `Typography::sz` is + /// `(base * scale).round()` - the same arithmetic as [`Self::sz`], which is + /// what keeps a shared widget the same size as the rows around it. + pub fn typo(&self) -> colony_ui::Typography { + colony_ui::Typography { + scale: self.font_scale(), + regular: self.app_font(), + medium: self.app_font_with_weight(Weight::Medium), + bold: self.app_font_with_weight(Weight::Bold), + } + } + /// Duration of the sidebar slide animation. pub const SIDEBAR_ANIM_MS: f32 = 200.0; diff --git a/src/ui/settings.rs b/src/ui/settings.rs index 7f74883..79461b5 100644 --- a/src/ui/settings.rs +++ b/src/ui/settings.rs @@ -7,9 +7,7 @@ use crate::i18n; use crate::message::Message; use crate::state::App; use crate::ui::theme::Palette; - -/// A theme variant: (variant_key, i18n_label_key, bg_hex, accent_hex). -type ThemeVariant = (&'static str, &'static str, u32, u32); +use colony_ui::widgets; /// Settings category names (keys for i18n). const SETTINGS_CATEGORIES: &[&str] = &[ @@ -809,583 +807,38 @@ impl App { } // ── Theme sub-section ── + /// The theme picker, drawn by colony-ui straight from the generated + /// catalog. This used to be 135 lines here, character for character what + /// the crate now draws - the crate's copy was ported FROM this one. fn view_theme_section(&self) -> Element<'_, Message> { - let font = self.app_font(); - let medium = self.app_font_with_weight(Weight::Medium); - - // Theme families: (key, i18n_label_key, variants) - // Each variant: (variant_key, i18n_label_key, bg_hex, accent_hex) - // Theme families: (key, i18n_label_key, icon, variants) - // Each variant: (variant_key, i18n_label_key, bg_hex, accent_hex) - // Icons use Nerd Font codepoints for themed themes - let theme_families: Vec<(&str, &str, &str, Vec)> = vec![ - // ── Existing themes ── - ( - "catppuccin", - "settings_theme_catppuccin", - "\u{f0f4}", - vec![ - // coffee - ( - "latte", - "settings_theme_catppuccin_latte", - 0xeff1f5, - 0x1e66f5, - ), - ( - "frappe", - "settings_theme_catppuccin_frappe", - 0x303446, - 0x8caaee, - ), - ( - "macchiato", - "settings_theme_catppuccin_macchiato", - 0x24273a, - 0x8aadf4, - ), - ( - "mocha", - "settings_theme_catppuccin_mocha", - 0x1e1e2e, - 0x89b4fa, - ), - ], - ), - ( - "gruvbox", - "settings_theme_gruvbox", - "", - vec![ - ("light", "settings_theme_light", 0xfbf1c7, 0x458588), - ("dark", "settings_theme_dark_mode", 0x282828, 0x83a598), - ], - ), - ( - "everblush", - "settings_theme_everblush", - "\u{f06c}", - vec![ - // leaf - ("light", "settings_theme_light", 0xe8eded, 0x3a88c0), - ("dark", "settings_theme_dark_mode", 0x141b1e, 0x67b0e8), - ], - ), - ( - "kanagawa", - "settings_theme_kanagawa", - "\u{f073e}", - vec![ - // wave (torii) - ("light", "settings_theme_light", 0xf2ecbc, 0x4d699b), - ("dark", "settings_theme_dark_mode", 0x1F1F28, 0x7E9CD8), - ( - "journal", - "settings_theme_kanagawa_journal", - 0xd5cea3, - 0x7a6840, - ), - ], - ), - // ── New themes ── - ( - "nord", - "settings_theme_nord", - "\u{f2dc}", - vec![ - // snowflake - ("dark", "settings_theme_dark_mode", 0x2E3440, 0x88C0D0), - ("light", "settings_theme_light", 0xECEFF4, 0x5E81AC), - ], - ), - ( - "dracula", - "settings_theme_dracula", - "\u{f6e2}", - vec![ - // ghost - ("dark", "settings_theme_dark_mode", 0x282A36, 0xBD93F9), - ("light", "settings_theme_light", 0xFFFBEB, 0x7C5FC2), - ], - ), - ( - "solarized", - "settings_theme_solarized", - "\u{f185}", - vec![ - // sun - ("dark", "settings_theme_dark_mode", 0x002B36, 0x268BD2), - ("light", "settings_theme_light", 0xFDF6E3, 0x268BD2), - ], - ), - ( - "tokyonight", - "settings_theme_tokyonight", - "\u{f0219}", - vec![ - // city - ( - "night", - "settings_theme_tokyonight_night", - 0x1A1B26, - 0x7AA2F7, - ), - ("day", "settings_theme_tokyonight_day", 0xE1E2E7, 0x2E7DE9), - ], - ), - ( - "rosepine", - "settings_theme_rosepine", - "\u{f46d}", - vec![ - // rose/flower - ("main", "settings_theme_rosepine_main", 0x191724, 0x9CCFD8), - ("moon", "settings_theme_rosepine_moon", 0x232136, 0x9CCFD8), - ("dawn", "settings_theme_rosepine_dawn", 0xFAF4ED, 0x56949F), - ], - ), - ( - "onedark", - "settings_theme_onedark", - "", - vec![ - ("dark", "settings_theme_dark_mode", 0x282C34, 0x61AFEF), - ("light", "settings_theme_light", 0xFAFAFA, 0x4078F2), - ], - ), - ( - "monokai", - "settings_theme_monokai", - "\u{f121}", - vec![ - // code - ("pro", "settings_theme_monokai_pro", 0x2D2A2E, 0x78DCE8), - ( - "classic", - "settings_theme_monokai_classic", - 0x272822, - 0x66D9EF, - ), - ( - "spectrum", - "settings_theme_monokai_spectrum", - 0x222222, - 0x5AD4E6, - ), - ], - ), - ( - "ayu", - "settings_theme_ayu", - "\u{f06c0}", - vec![ - // sunrise - ("dark", "settings_theme_dark_mode", 0x0B0E14, 0xE6B450), - ("mirage", "settings_theme_ayu_mirage", 0x1F2430, 0xFFCC66), - ("light", "settings_theme_light", 0xFAFAFA, 0xFF9940), - ], - ), - ( - "everforest", - "settings_theme_everforest", - "\u{f1bb}", - vec![ - // tree - ("dark", "settings_theme_dark_mode", 0x2D353B, 0x7FBBB3), - ("light", "settings_theme_light", 0xFDF6E3, 0x3A94C5), - ], - ), - ( - "material", - "settings_theme_material", - "\u{f0509}", - vec![ - // material-design - ( - "oceanic", - "settings_theme_material_oceanic", - 0x263238, - 0x89DDFF, - ), - ( - "palenight", - "settings_theme_material_palenight", - 0x292D3E, - 0xC792EA, - ), - ( - "deepocean", - "settings_theme_material_deepocean", - 0x0F111A, - 0x84FFFF, - ), - ], - ), - ( - "flexoki", - "settings_theme_flexoki", - "\u{f02d}", - vec![ - // book - ("dark", "settings_theme_dark_mode", 0x100F0F, 0x4385BE), - ("light", "settings_theme_light", 0xFFFCF0, 0x205EA6), - ], - ), - ( - "nightfox", - "settings_theme_nightfox", - "\u{f0139}", - vec![ - // fox - ( - "nightfox", - "settings_theme_nightfox_nightfox", - 0x192330, - 0x719CD6, - ), - ( - "dawnfox", - "settings_theme_nightfox_dawnfox", - 0xFAF4ED, - 0x286983, - ), - ], - ), - ( - "sonokai", - "settings_theme_sonokai", - "", - vec![( - "default", - "settings_theme_sonokai_default", - 0x2C2E34, - 0x76CCE0, - )], - ), - ( - "oxocarbon", - "settings_theme_oxocarbon", - "\u{f0620}", - vec![ - // molecule - ("dark", "settings_theme_dark_mode", 0x161616, 0x78A9FF), - ("light", "settings_theme_light", 0xFFFFFF, 0x0F62FE), - ], - ), - ( - "nightowl", - "settings_theme_nightowl", - "\u{f19e}", - vec![ - // owl (moon) - ("dark", "settings_theme_dark_mode", 0x011627, 0x82AAFF), - ("light", "settings_theme_light", 0xFBFBFB, 0x4876D6), - ], - ), - ( - "iceberg", - "settings_theme_iceberg", - "\u{f2dc}", - vec![ - // snowflake - ("dark", "settings_theme_dark_mode", 0x161821, 0x84A0C6), - ("light", "settings_theme_light", 0xE8E9EC, 0x2D539E), - ], - ), - ( - "horizon", - "settings_theme_horizon", - "\u{f06c0}", - vec![ - // sunrise - ("dark", "settings_theme_dark_mode", 0x1C1E26, 0x26BBD9), - ], - ), - ( - "melange", - "settings_theme_melange", - "\u{f0f4}", - vec![ - // coffee - ("dark", "settings_theme_dark_mode", 0x292522, 0xA3A9CE), - ("light", "settings_theme_light", 0xF4F0ED, 0x5E6DAB), - ], - ), - ( - "synthwave", - "settings_theme_synthwave", - "\u{f001}", - vec![ - // music - ("dark", "settings_theme_dark_mode", 0x262335, 0xFF7EDB), - ], - ), - ( - "modus", - "settings_theme_modus", - "\u{f06e}", - vec![ - // eye (accessibility) - ( - "operandi", - "settings_theme_modus_operandi", - 0xFFFFFF, - 0x0031A9, - ), - ( - "vivendi", - "settings_theme_modus_vivendi", - 0x000000, - 0x2FAFFF, - ), - ], - ), - // ── Fan-made character sets ── - ( - "stellar_blade", - "settings_theme_stellar_blade", - "\u{f04e5}", - vec![ - // sword - ( - "eve", - "settings_theme_stellar_blade_eve", - 0xECEEEC, - 0x21A04D, - ), - ( - "tachy", - "settings_theme_stellar_blade_tachy", - 0x10121A, - 0xFF7C1E, - ), - ( - "lily", - "settings_theme_stellar_blade_lily", - 0x111010, - 0xFFD23E, - ), - ( - "enya", - "settings_theme_stellar_blade_enya", - 0xEEF0F3, - 0x3E90C9, - ), - ( - "kaya", - "settings_theme_stellar_blade_kaya", - 0x1A1622, - 0xCFA8FF, - ), - ], - ), - ]; - - let mut col = column![].spacing(12); - - for (theme_key, label_key, icon, variants) in theme_families { - let is_selected_family = self.selected_theme == theme_key; - let label = i18n::t(label_key); - - // Family name label with optional themed icon - let label_text = if icon.is_empty() { - label.clone() - } else { - format!("{} {}", icon, label) - }; - let family_label = - text(label_text) - .size(self.sz(13)) - .font(medium) - .color(if is_selected_family { - Palette::TEXT_PRIMARY() - } else { - Palette::TEXT_SECONDARY() - }); - - // Variant cards as a horizontal row of mini color-swatch cards - let mut variant_row = row![].spacing(8); - - for (var_key, var_label_key, bg_hex, accent_hex) in &variants { - let is_active = is_selected_family && self.selected_variant == *var_key; - let theme_owned = theme_key.to_string(); - let var_owned = var_key.to_string(); - - // Parse swatch colors - let bg_color = iced::Color { - r: ((*bg_hex >> 16) & 0xFF) as f32 / 255.0, - g: ((*bg_hex >> 8) & 0xFF) as f32 / 255.0, - b: (*bg_hex & 0xFF) as f32 / 255.0, - a: 1.0, - }; - let accent_color = iced::Color { - r: ((*accent_hex >> 16) & 0xFF) as f32 / 255.0, - g: ((*accent_hex >> 8) & 0xFF) as f32 / 255.0, - b: (*accent_hex & 0xFF) as f32 / 255.0, - a: 1.0, - }; - - // Color swatch: bg stripe + accent dot - let swatch_bg = container(text("")) - .width(Length::Fill) - .height(Length::Fixed(4.0)) - .style(move |_theme| container::Style { - background: Some(accent_color.into()), - border: iced::Border::default().rounded(2), - ..Default::default() - }); - - let swatch = container(swatch_bg) - .width(Length::Fill) - .height(Length::Fixed(28.0)) - .padding(iced::Padding { - top: 20.0, - right: 6.0, - bottom: 4.0, - left: 6.0, - }) - .style(move |_theme| container::Style { - background: Some(bg_color.into()), - border: iced::Border::default().rounded(6), - ..Default::default() - }); - - // Variant label below the swatch - let var_label = text(i18n::t(var_label_key)) - .size(self.sz(10)) - .font(font) - .color(if is_active { - Palette::TEXT_PRIMARY() - } else { - Palette::TEXT_MUTED() - }); - - // Check indicator for active variant - let indicator: Element<'_, Message> = if is_active { - text("\u{f00c}") - .size(self.sz(8)) - .font(font) - .color(Palette::ACCENT()) - .into() - } else { - text("").size(self.sz(8)).into() - }; - - let card_content = column![ - swatch, - container( - row![var_label, indicator] - .spacing(4) - .align_y(iced::Alignment::Center) - ) - .padding(iced::Padding { - top: 4.0, - right: 0.0, - bottom: 0.0, - left: 2.0 - }), - ] - .spacing(0) - .width(Length::Fill); - - let card = button(card_content) - .on_press(Message::SelectThemeVariant(theme_owned, var_owned)) - .padding(4) - .width(Length::Fill) - .style(move |_theme, status| { - let border_color = match status { - _ if is_active => Palette::ACCENT(), - button::Status::Hovered => Palette::TEXT_DIMMER(), - _ => Palette::BORDER_SUBTLE(), - }; - button::Style { - background: Some(Palette::BG_CARD().into()), - text_color: Palette::TEXT_PRIMARY(), - border: iced::Border { - color: border_color, - width: if is_active { 2.0 } else { 1.0 }, - radius: 8.0.into(), - }, - ..Default::default() - } - }); - - variant_row = variant_row.push(card); - } - - col = col.push(column![family_label, variant_row,].spacing(6)); - } - - col.into() + widgets::theme_picker( + &self.typo(), + &self.selected_theme, + &self.selected_variant, + |family, variant| { + Message::SelectThemeVariant(family.to_string(), variant.to_string()) + }, + ) } // ── Colors & accents sub-section ── + /// The accent swatches, plus the separate "derive the accent from the + /// background" behaviour toggle. + /// + /// The eight colours were hardcoded here as a `vec![]` of literals. They + /// now come from `ACCENT_OVERRIDES`, generated from `tokens/accents.toml`, + /// which matters more than the line count: **the order is load-bearing**. + /// Colony buckets a hash of each installed app's name into that list to + /// pick its identity tint, so a local edit that reordered or inserted an + /// entry would silently re-colour every icon on every machine - and there + /// was nothing here to say so. fn view_colors_section(&self) -> Element<'_, Message> { - let font = self.app_font(); - - // Accent colors: (key, i18n_label_key, hex_color) - let accent_colors: Vec<(&str, &str, u32)> = vec![ - ("red", "settings_accent_red", 0xE05555), - ("orange", "settings_accent_orange", 0xE0855A), - ("yellow", "settings_accent_yellow", 0xC8A832), - ("green", "settings_accent_green", 0x55B87A), - ("blue", "settings_accent_blue", 0x6B8BD6), - ("indigo", "settings_accent_indigo", 0x7B6BD6), - ("violet", "settings_accent_violet", 0xB06BD6), - ("amber", "settings_accent_amber", 0xD4A030), - ]; - - let mut color_row = row![].spacing(8).align_y(iced::Alignment::Center); - - for (color_key, _label_key, hex) in &accent_colors { - let is_active = self.selected_accent == *color_key; - let color_key_owned = color_key.to_string(); - let r = ((*hex >> 16) & 0xFF) as f32 / 255.0; - let g = ((*hex >> 8) & 0xFF) as f32 / 255.0; - let b = (*hex & 0xFF) as f32 / 255.0; - let dot_color = iced::Color { r, g, b, a: 1.0 }; - - // Circular color swatch button - let check_icon: Element<'_, Message> = if is_active { - text("\u{f00c}") - .size(self.sz(8)) - .font(font) - .color(iced::Color::WHITE) - .into() - } else { - text("").size(self.sz(8)).into() - }; - let swatch = button( - container(check_icon) - .center_x(Length::Fill) - .center_y(Length::Fill), - ) - .on_press(Message::SelectAccentColor(color_key_owned)) - .width(Length::Fixed(28.0)) - .height(Length::Fixed(28.0)) - .padding(0) - .style(move |_theme, status| { - let border_color = match status { - _ if is_active => Palette::TEXT_PRIMARY(), - button::Status::Hovered => Palette::TEXT_DIMMER(), - _ => iced::Color::TRANSPARENT, - }; - button::Style { - background: Some(dot_color.into()), - text_color: iced::Color::WHITE, - border: iced::Border { - color: border_color, - width: if is_active { 2.0 } else { 0.0 }, - radius: 14.0.into(), - }, - ..Default::default() - } - }); - - color_row = color_row.push(swatch); - } + let swatches = widgets::accent_picker( + &self.typo(), + Some(self.selected_accent.as_str()), + |key| Message::SelectAccentColor(key.to_string()), + ); - // Auto accent toggle let auto_accent_row = self.view_functional_toggle( &i18n::t("settings_auto_accent"), &i18n::t("settings_auto_accent_desc"), @@ -1393,82 +846,34 @@ impl App { Message::ToggleAutoAccent, ); - column![color_row, container(text("")).height(12), auto_accent_row,] + column![swatches, container(text("")).height(12), auto_accent_row] .spacing(0) .into() } // ── Collapsible section ── + /// A collapsible settings section. + /// + /// Kept as a method rather than calling the crate from all sixteen sites: + /// the expanded set and the message are the HOST's, the drawing is the + /// crate's. That split is why no call site changed. fn view_collapsible_section<'a>( &self, key: &str, title: &str, content: Element<'a, Message>, ) -> Element<'a, Message> { - let is_expanded = self.settings_expanded_sections.contains(key); - let arrow = if is_expanded { "\u{f078}" } else { "\u{f054}" }; // chevron down / right - let key_owned = key.to_string(); - let title_owned = title.to_string(); - - // Header: clean flat style, no box — just text + chevron - let header_btn = button( - row![ - text(title_owned) - .size(self.sz(15)) - .font(self.app_font_with_weight(Weight::Bold)) - .color(Palette::TEXT_PRIMARY()), - container(text("")).width(Fill), - text(arrow) - .size(self.sz(9)) - .font(self.app_font()) - .color(Palette::TEXT_DIMMER()), - ] - .spacing(8) - .align_y(iced::Alignment::Center), + widgets::collapsible_section( + &self.typo(), + title, + self.settings_expanded_sections.contains(key), + Message::SettingsToggleSection(key.to_string()), + content, ) - .on_press(Message::SettingsToggleSection(key_owned)) - .padding([12, 4]) - .width(Fill) - .style(move |_theme, status| { - let bg = match status { - button::Status::Hovered => Palette::BG_CARD_HOVER(), - _ => iced::Color::TRANSPARENT, - }; - button::Style { - background: Some(bg.into()), - text_color: Palette::TEXT_PRIMARY(), - border: iced::Border::default().rounded(6), - ..Default::default() - } - }); - - if is_expanded { - // Thin divider line under header - let divider = - container(text("")) - .width(Fill) - .height(1) - .style(|_theme| container::Style { - background: Some(Palette::DIVIDER().into()), - ..Default::default() - }); - - let body = container(content) - .padding(iced::Padding { - top: 12.0, - right: 4.0, - bottom: 4.0, - left: 4.0, - }) - .width(Fill); - - column![header_btn, divider, body].spacing(0).into() - } else { - header_btn.into() - } } - /// A functional toggle: clicking sends the given message. + /// A labelled on/off row. Same split as above: eleven call sites, none of + /// which changed. fn view_functional_toggle( &self, title: &str, @@ -1476,71 +881,7 @@ impl App { on: bool, msg: Message, ) -> Element<'_, Message> { - let font = self.app_font(); - let track_bg = if on { - Palette::ACCENT() - } else { - Palette::BG_CARD_HOVER() - }; - let knob_offset: f32 = if on { 16.0 } else { 2.0 }; - - let knob = container(text("")) - .width(Length::Fixed(14.0)) - .height(Length::Fixed(14.0)) - .style(move |_theme| container::Style { - background: Some(Palette::TEXT_PRIMARY().into()), - border: iced::Border::default().rounded(7), - ..Default::default() - }); - let toggle_visual = container(container(knob).padding(iced::Padding { - top: 1.0, - right: 0.0, - bottom: 0.0, - left: knob_offset, - })) - .width(Length::Fixed(34.0)) - .height(Length::Fixed(18.0)) - .style(move |_theme| container::Style { - background: Some(track_bg.into()), - border: iced::Border::default().rounded(9), - ..Default::default() - }); - - button( - row![ - column![ - text(title.to_string()) - .size(self.sz(13)) - .font(font) - .color(Palette::TEXT_PRIMARY()), - text(desc.to_string()) - .size(self.sz(11)) - .font(font) - .color(Palette::TEXT_DIMMER()), - ] - .spacing(2), - container(text("")).width(Fill), - toggle_visual, - ] - .spacing(10) - .align_y(iced::Alignment::Center), - ) - .on_press(msg) - .padding([6, 4]) - .width(Fill) - .style(|_theme, status| { - let bg = match status { - button::Status::Hovered => Palette::BG_CARD_HOVER(), - _ => iced::Color::TRANSPARENT, - }; - button::Style { - background: Some(bg.into()), - text_color: Palette::TEXT_PRIMARY(), - border: iced::Border::default().rounded(6), - ..Default::default() - } - }) - .into() + widgets::functional_toggle(&self.typo(), title, desc, on, msg) } /// A setting row with a pick_list dropdown for selecting from options. diff --git a/src/ui/theme.rs b/src/ui/theme.rs index c2ddfeb..c400ac9 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -1,2935 +1,14 @@ -use iced::Color; -use std::sync::RwLock; - -/// Runtime theme palette — all semantic UI colors. -#[derive(Debug, Clone, Copy)] -pub struct ThemePalette { - // --- Backgrounds --- - pub bg_primary: Color, - pub bg_sidebar: Color, - pub bg_card: Color, - pub bg_card_hover: Color, - pub bg_card_pressed: Color, - pub bg_selected: Color, - pub bg_input: Color, - pub bg_progress: Color, - - // --- Text --- - pub text_primary: Color, - pub text_secondary: Color, - pub text_muted: Color, - pub text_dim: Color, - pub text_dimmer: Color, - pub text_dimmest: Color, - pub text_placeholder: Color, - - // --- Accent --- - pub accent_blue: Color, - pub accent_icon: Color, - pub accent_progress: Color, - - // --- Buttons --- - pub btn_default: Color, - pub btn_hover: Color, - pub btn_pressed: Color, - - // --- Success --- - pub success: Color, - pub success_bg: Color, - pub btn_success: Color, - pub btn_success_hover: Color, - pub btn_success_pressed: Color, - - // --- Warning --- - pub warning: Color, - pub warning_bg: Color, - - // --- Error --- - pub error: Color, - pub error_light: Color, - pub error_bg: Color, - pub btn_danger_bg: Color, - pub btn_danger_hover: Color, - pub btn_trash_hover: Color, - pub btn_trash_pressed: Color, - - // --- Modal --- - pub bg_modal_section: Color, - pub border_subtle: Color, - pub divider: Color, -} - -// ── Global active palette ── - -static ACTIVE_PALETTE: RwLock = RwLock::new(ThemePalette::GRUVBOX_DARK); - -/// User-chosen accent color override. `None` means "auto" (use theme default). -static ACTIVE_ACCENT: RwLock> = RwLock::new(None); - -/// Set the user accent override. Pass `None` for auto (theme default). -pub fn set_active_accent(color: Option) { - *ACTIVE_ACCENT.write().unwrap() = color; -} - -/// Resolve the effective accent color: user override or theme default. -pub fn effective_accent() -> Color { - ACTIVE_ACCENT - .read() - .unwrap() - .unwrap_or_else(|| active_palette().accent_blue) -} - -/// Convert an accent key to its Color, or None for "auto". -pub fn accent_key_to_color(key: &str) -> Option { - let hex: u32 = match key { - "red" => 0xE05555, - "orange" => 0xE0855A, - "yellow" => 0xC8A832, - "green" => 0x55B87A, - "blue" => 0x6B8BD6, - "indigo" => 0x7B6BD6, - "violet" => 0xB06BD6, - "amber" => 0xD4A030, - _ => return None, - }; - Some(Color { - r: ((hex >> 16) & 0xFF) as f32 / 255.0, - g: ((hex >> 8) & 0xFF) as f32 / 255.0, - b: (hex & 0xFF) as f32 / 255.0, - a: 1.0, - }) -} - -/// Deterministic identity tint for an app, derived from its NAME only (stable -/// across install/uninstall and across machines). Buckets the name hash into -/// the 8 accent hues so every app gets a distinct, palette-harmonious color for -/// its hexagon cell — no per-app assets required. -pub fn app_tint(name: &str) -> Color { - const KEYS: [&str; 8] = [ - "red", "orange", "yellow", "green", "blue", "indigo", "violet", "amber", - ]; - // Classic string hash: h = c + h*31, wrapping. - let mut h: i64 = 0; - for b in name.bytes() { - h = (b as i64).wrapping_add(h.wrapping_shl(5)).wrapping_sub(h); - } - let idx = (h.unsigned_abs() % KEYS.len() as u64) as usize; - accent_key_to_color(KEYS[idx]).unwrap_or_else(|| active_palette().accent_blue) -} - -/// Pick a legible foreground (near-black or near-white) for a glyph drawn on top -/// of `bg`, using perceptual (YIQ) luminance. -pub fn contrast_on(bg: Color) -> Color { - let yiq = 0.299 * bg.r + 0.587 * bg.g + 0.114 * bg.b; - if yiq > 0.6 { - Color { - r: 0.08, - g: 0.08, - b: 0.10, - a: 1.0, - } - } else { - Color { - r: 0.97, - g: 0.98, - b: 1.0, - a: 1.0, - } - } -} - -/// Set the active palette based on theme + variant keys. -pub fn set_active_theme(theme: &str, variant: &str) { - let palette = match (theme, variant) { - // Catppuccin - ("catppuccin", "latte") => ThemePalette::CATPPUCCIN_LATTE, - ("catppuccin", "frappe") => ThemePalette::CATPPUCCIN_FRAPPE, - ("catppuccin", "macchiato") => ThemePalette::CATPPUCCIN_MACCHIATO, - ("catppuccin", "mocha") => ThemePalette::CATPPUCCIN_MOCHA, - // Gruvbox - ("gruvbox", "light") => ThemePalette::GRUVBOX_LIGHT, - ("gruvbox", "dark") => ThemePalette::GRUVBOX_DARK, - // Everblush - ("everblush", "light") => ThemePalette::EVERBLUSH_LIGHT, - ("everblush", "dark") => ThemePalette::EVERBLUSH_DARK, - // Kanagawa - ("kanagawa", "light") => ThemePalette::KANAGAWA_LOTUS, - ("kanagawa", "dark") => ThemePalette::KANAGAWA_WAVE, - ("kanagawa", "journal") => ThemePalette::KANAGAWA_DRAGON, - // Nord - ("nord", "dark") => ThemePalette::NORD_DARK, - ("nord", "light") => ThemePalette::NORD_LIGHT, - // Dracula - ("dracula", "dark") => ThemePalette::DRACULA_DARK, - ("dracula", "light") => ThemePalette::DRACULA_LIGHT, - // Solarized - ("solarized", "dark") => ThemePalette::SOLARIZED_DARK, - ("solarized", "light") => ThemePalette::SOLARIZED_LIGHT, - // Tokyo Night - ("tokyonight", "night") => ThemePalette::TOKYONIGHT_NIGHT, - ("tokyonight", "day") => ThemePalette::TOKYONIGHT_DAY, - // Rosé Pine - ("rosepine", "main") => ThemePalette::ROSEPINE_MAIN, - ("rosepine", "moon") => ThemePalette::ROSEPINE_MOON, - ("rosepine", "dawn") => ThemePalette::ROSEPINE_DAWN, - // One Dark - ("onedark", "dark") => ThemePalette::ONEDARK_DARK, - ("onedark", "light") => ThemePalette::ONEDARK_LIGHT, - // Monokai Pro - ("monokai", "pro") => ThemePalette::MONOKAI_PRO, - ("monokai", "classic") => ThemePalette::MONOKAI_CLASSIC, - ("monokai", "spectrum") => ThemePalette::MONOKAI_SPECTRUM, - // Ayu - ("ayu", "dark") => ThemePalette::AYU_DARK, - ("ayu", "mirage") => ThemePalette::AYU_MIRAGE, - ("ayu", "light") => ThemePalette::AYU_LIGHT, - // Everforest - ("everforest", "dark") => ThemePalette::EVERFOREST_DARK, - ("everforest", "light") => ThemePalette::EVERFOREST_LIGHT, - // Material - ("material", "oceanic") => ThemePalette::MATERIAL_OCEANIC, - ("material", "palenight") => ThemePalette::MATERIAL_PALENIGHT, - ("material", "deepocean") => ThemePalette::MATERIAL_DEEPOCEAN, - // Flexoki - ("flexoki", "dark") => ThemePalette::FLEXOKI_DARK, - ("flexoki", "light") => ThemePalette::FLEXOKI_LIGHT, - // Nightfox - ("nightfox", "nightfox") => ThemePalette::NIGHTFOX, - ("nightfox", "dawnfox") => ThemePalette::DAWNFOX, - // Sonokai - ("sonokai", "default") => ThemePalette::SONOKAI_DEFAULT, - // Oxocarbon - ("oxocarbon", "dark") => ThemePalette::OXOCARBON_DARK, - ("oxocarbon", "light") => ThemePalette::OXOCARBON_LIGHT, - // Night Owl - ("nightowl", "dark") => ThemePalette::NIGHTOWL_DARK, - ("nightowl", "light") => ThemePalette::NIGHTOWL_LIGHT, - // Iceberg - ("iceberg", "dark") => ThemePalette::ICEBERG_DARK, - ("iceberg", "light") => ThemePalette::ICEBERG_LIGHT, - // Horizon - ("horizon", "dark") => ThemePalette::HORIZON_DARK, - // Melange - ("melange", "dark") => ThemePalette::MELANGE_DARK, - ("melange", "light") => ThemePalette::MELANGE_LIGHT, - // Synthwave '84 - ("synthwave", "dark") => ThemePalette::SYNTHWAVE_DARK, - // Modus - ("modus", "operandi") => ThemePalette::MODUS_OPERANDI, - ("modus", "vivendi") => ThemePalette::MODUS_VIVENDI, - // Stellar Blade (fan-made character set) - ("stellar_blade", "eve") => ThemePalette::STELLAR_EVE, - ("stellar_blade", "tachy") => ThemePalette::STELLAR_TACHY, - ("stellar_blade", "lily") => ThemePalette::STELLAR_LILY, - ("stellar_blade", "enya") => ThemePalette::STELLAR_ENYA, - ("stellar_blade", "kaya") => ThemePalette::STELLAR_KAYA, - // Fallback - _ => ThemePalette::GRUVBOX_DARK, - }; - *ACTIVE_PALETTE.write().unwrap() = palette; -} - -/// High-contrast mode flag. -static HIGH_CONTRAST: RwLock = RwLock::new(false); - -/// Set high-contrast mode. -pub fn set_high_contrast(enabled: bool) { - *HIGH_CONTRAST.write().unwrap() = enabled; -} - -/// Check if high contrast is active. -pub fn is_high_contrast() -> bool { - *HIGH_CONTRAST.read().unwrap() -} - -/// Convenience accessor — read the current palette. -pub fn active_palette() -> ThemePalette { - let base = *ACTIVE_PALETTE.read().unwrap(); - if is_high_contrast() { - base.with_high_contrast() - } else { - base - } -} - -// ── Public façade (drop-in replacement for the old `Palette::CONST` API) ── - -pub struct Palette; - -#[allow(non_snake_case, dead_code)] -impl Palette { - // Backgrounds - pub fn BG_PRIMARY() -> Color { - active_palette().bg_primary - } - pub fn BG_SIDEBAR() -> Color { - active_palette().bg_sidebar - } - pub fn BG_CARD() -> Color { - active_palette().bg_card - } - pub fn BG_CARD_HOVER() -> Color { - active_palette().bg_card_hover - } - pub fn BG_CARD_PRESSED() -> Color { - active_palette().bg_card_pressed - } - pub fn BG_SELECTED() -> Color { - active_palette().bg_selected - } - pub fn BG_INPUT() -> Color { - active_palette().bg_input - } - pub fn BG_PROGRESS() -> Color { - active_palette().bg_progress - } - - // Text - pub fn TEXT_PRIMARY() -> Color { - active_palette().text_primary - } - pub fn TEXT_SECONDARY() -> Color { - active_palette().text_secondary - } - pub fn TEXT_MUTED() -> Color { - active_palette().text_muted - } - pub fn TEXT_DIM() -> Color { - active_palette().text_dim - } - pub fn TEXT_DIMMER() -> Color { - active_palette().text_dimmer - } - pub fn TEXT_DIMMEST() -> Color { - active_palette().text_dimmest - } - pub fn TEXT_PLACEHOLDER() -> Color { - active_palette().text_placeholder - } - - // Accent - /// User-selected accent (or theme default if auto). - pub fn ACCENT() -> Color { - effective_accent() - } - pub fn ACCENT_ICON() -> Color { - active_palette().accent_icon - } - pub fn ACCENT_PROGRESS() -> Color { - active_palette().accent_progress - } - - // Buttons - pub fn BTN_DEFAULT() -> Color { - active_palette().btn_default - } - pub fn BTN_HOVER() -> Color { - active_palette().btn_hover - } - pub fn BTN_PRESSED() -> Color { - active_palette().btn_pressed - } - - // Success - pub fn SUCCESS() -> Color { - active_palette().success - } - pub fn SUCCESS_BG() -> Color { - active_palette().success_bg - } - pub fn BTN_SUCCESS() -> Color { - active_palette().btn_success - } - pub fn BTN_SUCCESS_HOVER() -> Color { - active_palette().btn_success_hover - } - pub fn BTN_SUCCESS_PRESSED() -> Color { - active_palette().btn_success_pressed - } - - // Warning - pub fn WARNING() -> Color { - active_palette().warning - } - pub fn WARNING_BG() -> Color { - active_palette().warning_bg - } - - // Error - pub fn ERROR() -> Color { - active_palette().error - } - pub fn ERROR_LIGHT() -> Color { - active_palette().error_light - } - pub fn ERROR_BG() -> Color { - active_palette().error_bg - } - pub fn BTN_DANGER_BG() -> Color { - active_palette().btn_danger_bg - } - pub fn BTN_DANGER_HOVER() -> Color { - active_palette().btn_danger_hover - } - pub fn BTN_TRASH_HOVER() -> Color { - active_palette().btn_trash_hover - } - pub fn BTN_TRASH_PRESSED() -> Color { - active_palette().btn_trash_pressed - } - - // Modal - pub fn BG_MODAL_SECTION() -> Color { - active_palette().bg_modal_section - } - pub fn BORDER_SUBTLE() -> Color { - active_palette().border_subtle - } - pub fn DIVIDER() -> Color { - active_palette().divider - } -} - -// ── Compile-time hex → Color helper ── - -const fn hex(h: u32) -> Color { - let r = ((h >> 16) & 0xFF) as f32 / 255.0; - let g = ((h >> 8) & 0xFF) as f32 / 255.0; - let b = (h & 0xFF) as f32 / 255.0; - Color { r, g, b, a: 1.0 } -} - -// ════════════════════════════════════════════════════════════════════════ -// Theme palette definitions -// ════════════════════════════════════════════════════════════════════════ - -impl ThemePalette { - /// Boost contrast for accessibility: brighten text, darken backgrounds, sharpen borders. - pub fn with_high_contrast(mut self) -> Self { - fn boost(c: Color, amount: f32) -> Color { - Color { - r: (c.r + amount).min(1.0), - g: (c.g + amount).min(1.0), - b: (c.b + amount).min(1.0), - a: c.a, - } - } - fn darken(c: Color, amount: f32) -> Color { - Color { - r: (c.r - amount).max(0.0), - g: (c.g - amount).max(0.0), - b: (c.b - amount).max(0.0), - a: c.a, - } - } - // Detect light vs dark theme by bg brightness - let bg_luma = - self.bg_primary.r * 0.299 + self.bg_primary.g * 0.587 + self.bg_primary.b * 0.114; - let is_light = bg_luma > 0.5; - - if is_light { - // Light theme: darken text more, lighten backgrounds - self.text_primary = darken(self.text_primary, 0.15); - self.text_secondary = darken(self.text_secondary, 0.12); - self.text_muted = darken(self.text_muted, 0.10); - self.text_dim = darken(self.text_dim, 0.10); - self.text_dimmer = darken(self.text_dimmer, 0.08); - self.border_subtle = darken(self.border_subtle, 0.15); - self.divider = darken(self.divider, 0.15); - } else { - // Dark theme: brighten text, sharpen borders - self.text_primary = boost(self.text_primary, 0.12); - self.text_secondary = boost(self.text_secondary, 0.10); - self.text_muted = boost(self.text_muted, 0.10); - self.text_dim = boost(self.text_dim, 0.08); - self.text_dimmer = boost(self.text_dimmer, 0.08); - self.border_subtle = boost(self.border_subtle, 0.12); - self.divider = boost(self.divider, 0.12); - } - self - } - - // ── Catppuccin Latte (light) ── - pub const CATPPUCCIN_LATTE: Self = Self { - bg_primary: hex(0xeff1f5), - bg_sidebar: hex(0xe6e9ef), - bg_card: hex(0xe6e9ef), - bg_card_hover: hex(0xdce0e8), - bg_card_pressed: hex(0xccd0da), - bg_selected: hex(0xccd0da), - bg_input: hex(0xdce0e8), - bg_progress: hex(0xe6e9ef), - - text_primary: hex(0x4c4f69), - text_secondary: hex(0x5c5f77), - text_muted: hex(0x6c6f85), - text_dim: hex(0x7c7f93), - text_dimmer: hex(0x8c8fa1), - text_dimmest: hex(0x9ca0b0), - text_placeholder: hex(0x8c8fa1), - - accent_blue: hex(0x1e66f5), - accent_icon: hex(0x7287fd), - accent_progress: hex(0x7287fd), - - btn_default: hex(0xdce0e8), - btn_hover: hex(0xccd0da), - btn_pressed: hex(0xbcc0cc), - - success: hex(0x40a02b), - success_bg: hex(0xd5f0cd), - btn_success: hex(0x40a02b), - btn_success_hover: hex(0x369222), - btn_success_pressed: hex(0x2c8219), - - warning: hex(0xdf8e1d), - warning_bg: hex(0xf5e6c8), - - error: hex(0xd20f39), - error_light: hex(0xe64553), - error_bg: hex(0xf5d0d6), - btn_danger_bg: hex(0xf5d0d6), - btn_danger_hover: hex(0xeab0ba), - btn_trash_hover: hex(0xd20f39), - btn_trash_pressed: hex(0xb00c30), - - bg_modal_section: hex(0xe6e9ef), - border_subtle: hex(0xccd0da), - divider: hex(0xccd0da), - }; - - // ── Catppuccin Frappé ── - pub const CATPPUCCIN_FRAPPE: Self = Self { - bg_primary: hex(0x303446), - bg_sidebar: hex(0x292c3c), - bg_card: hex(0x292c3c), - bg_card_hover: hex(0x414559), - bg_card_pressed: hex(0x51576d), - bg_selected: hex(0x51576d), - bg_input: hex(0x414559), - bg_progress: hex(0x292c3c), - - text_primary: hex(0xc6d0f5), - text_secondary: hex(0xb5bfe2), - text_muted: hex(0xa5adce), - text_dim: hex(0x949cbb), - text_dimmer: hex(0x838ba7), - text_dimmest: hex(0x737994), - text_placeholder: hex(0x838ba7), - - accent_blue: hex(0x8caaee), - accent_icon: hex(0xbabbf1), - accent_progress: hex(0xbabbf1), - - btn_default: hex(0x414559), - btn_hover: hex(0x51576d), - btn_pressed: hex(0x626880), - - success: hex(0xa6d189), - success_bg: hex(0x2a3a28), - btn_success: hex(0x549444), - btn_success_hover: hex(0x468838), - btn_success_pressed: hex(0x387a2c), - - warning: hex(0xe5c890), - warning_bg: hex(0x3a3828), - - error: hex(0xe78284), - error_light: hex(0xea999c), - error_bg: hex(0x3a2628), - btn_danger_bg: hex(0x3a2628), - btn_danger_hover: hex(0x5a3638), - btn_trash_hover: hex(0x9a3234), - btn_trash_pressed: hex(0x7a2224), - - bg_modal_section: hex(0x292c3c), - border_subtle: hex(0x414559), - divider: hex(0x414559), - }; - - // ── Catppuccin Macchiato ── - pub const CATPPUCCIN_MACCHIATO: Self = Self { - bg_primary: hex(0x24273a), - bg_sidebar: hex(0x1e2030), - bg_card: hex(0x1e2030), - bg_card_hover: hex(0x363a4f), - bg_card_pressed: hex(0x494d64), - bg_selected: hex(0x494d64), - bg_input: hex(0x363a4f), - bg_progress: hex(0x1e2030), - - text_primary: hex(0xcad3f5), - text_secondary: hex(0xb8c0e0), - text_muted: hex(0xa5adcb), - text_dim: hex(0x939ab7), - text_dimmer: hex(0x8087a2), - text_dimmest: hex(0x6e738d), - text_placeholder: hex(0x8087a2), - - accent_blue: hex(0x8aadf4), - accent_icon: hex(0xb7bdf8), - accent_progress: hex(0xb7bdf8), - - btn_default: hex(0x363a4f), - btn_hover: hex(0x494d64), - btn_pressed: hex(0x5b6078), - - success: hex(0xa6da95), - success_bg: hex(0x243826), - btn_success: hex(0x4e9240), - btn_success_hover: hex(0x408634), - btn_success_pressed: hex(0x327a28), - - warning: hex(0xeed49f), - warning_bg: hex(0x383428), - - error: hex(0xed8796), - error_light: hex(0xee99a0), - error_bg: hex(0x382428), - btn_danger_bg: hex(0x382428), - btn_danger_hover: hex(0x583438), - btn_trash_hover: hex(0x983038), - btn_trash_pressed: hex(0x782028), - - bg_modal_section: hex(0x1e2030), - border_subtle: hex(0x363a4f), - divider: hex(0x363a4f), - }; - - // ── Catppuccin Mocha ── - pub const CATPPUCCIN_MOCHA: Self = Self { - bg_primary: hex(0x1e1e2e), - bg_sidebar: hex(0x181825), - bg_card: hex(0x181825), - bg_card_hover: hex(0x313244), - bg_card_pressed: hex(0x45475a), - bg_selected: hex(0x45475a), - bg_input: hex(0x313244), - bg_progress: hex(0x181825), - - text_primary: hex(0xcdd6f4), - text_secondary: hex(0xbac2de), - text_muted: hex(0xa6adc8), - text_dim: hex(0x9399b2), - text_dimmer: hex(0x7f849c), - text_dimmest: hex(0x6c7086), - text_placeholder: hex(0x7f849c), - - accent_blue: hex(0x89b4fa), - accent_icon: hex(0xb4befe), - accent_progress: hex(0xb4befe), - - btn_default: hex(0x313244), - btn_hover: hex(0x45475a), - btn_pressed: hex(0x585b70), - - success: hex(0xa6e3a1), - success_bg: hex(0x1e3a1e), - btn_success: hex(0x48904a), - btn_success_hover: hex(0x3a843c), - btn_success_pressed: hex(0x2c782e), - - warning: hex(0xf9e2af), - warning_bg: hex(0x3a3420), - - error: hex(0xf38ba8), - error_light: hex(0xf5a0b8), - error_bg: hex(0x3a1e28), - btn_danger_bg: hex(0x3a1e28), - btn_danger_hover: hex(0x5a2e38), - btn_trash_hover: hex(0x952e3a), - btn_trash_pressed: hex(0x751e2a), - - bg_modal_section: hex(0x181825), - border_subtle: hex(0x313244), - divider: hex(0x313244), - }; - - // ── Gruvbox Dark ── - pub const GRUVBOX_DARK: Self = Self { - bg_primary: hex(0x282828), - bg_sidebar: hex(0x1d2021), - bg_card: hex(0x3c3836), - bg_card_hover: hex(0x504945), - bg_card_pressed: hex(0x665c54), - bg_selected: hex(0x504945), - bg_input: hex(0x3c3836), - bg_progress: hex(0x32302f), - - text_primary: hex(0xebdbb2), - text_secondary: hex(0xd5c4a1), - text_muted: hex(0xbdae93), - text_dim: hex(0xa89984), - text_dimmer: hex(0x928374), - text_dimmest: hex(0x7c6f64), - text_placeholder: hex(0x928374), - - accent_blue: hex(0x83a598), - accent_icon: hex(0x83a598), - accent_progress: hex(0x83a598), - - btn_default: hex(0x3c3836), - btn_hover: hex(0x504945), - btn_pressed: hex(0x665c54), - - success: hex(0xb8bb26), - success_bg: hex(0x2a3020), - btn_success: hex(0x689d6a), - btn_success_hover: hex(0x5a8f5c), - btn_success_pressed: hex(0x4c814e), - - warning: hex(0xfabd2f), - warning_bg: hex(0x3a3420), - - error: hex(0xfb4934), - error_light: hex(0xfe6050), - error_bg: hex(0x3c1f1f), - btn_danger_bg: hex(0x3c1f1f), - btn_danger_hover: hex(0x5c2f2f), - btn_trash_hover: hex(0x9d3030), - btn_trash_pressed: hex(0x7d2020), - - bg_modal_section: hex(0x282828), - border_subtle: hex(0x3c3836), - divider: hex(0x3c3836), - }; - - // ── Gruvbox Light ── - pub const GRUVBOX_LIGHT: Self = Self { - bg_primary: hex(0xfbf1c7), - bg_sidebar: hex(0xf2e5bc), - bg_card: hex(0xebdbb2), - bg_card_hover: hex(0xd5c4a1), - bg_card_pressed: hex(0xbdae93), - bg_selected: hex(0xd5c4a1), - bg_input: hex(0xebdbb2), - bg_progress: hex(0xf2e5bc), - - text_primary: hex(0x3c3836), - text_secondary: hex(0x504945), - text_muted: hex(0x665c54), - text_dim: hex(0x7c6f64), - text_dimmer: hex(0x928374), - text_dimmest: hex(0xa89984), - text_placeholder: hex(0x928374), - - accent_blue: hex(0x458588), - accent_icon: hex(0x458588), - accent_progress: hex(0x458588), - - btn_default: hex(0xebdbb2), - btn_hover: hex(0xd5c4a1), - btn_pressed: hex(0xbdae93), - - success: hex(0x98971a), - success_bg: hex(0xdde6b0), - btn_success: hex(0x689d6a), - btn_success_hover: hex(0x5a8f5c), - btn_success_pressed: hex(0x4c814e), - - warning: hex(0xd79921), - warning_bg: hex(0xf0e0b0), - - error: hex(0xcc241d), - error_light: hex(0xd44040), - error_bg: hex(0xf0c8c8), - btn_danger_bg: hex(0xf0c8c8), - btn_danger_hover: hex(0xe0a8a8), - btn_trash_hover: hex(0xcc241d), - btn_trash_pressed: hex(0xaa1818), - - bg_modal_section: hex(0xfbf1c7), - border_subtle: hex(0xd5c4a1), - divider: hex(0xd5c4a1), - }; - - // ── Everblush Dark ── - pub const EVERBLUSH_DARK: Self = Self { - bg_primary: hex(0x141b1e), - bg_sidebar: hex(0x1a2124), - bg_card: hex(0x232a2d), - bg_card_hover: hex(0x2c3538), - bg_card_pressed: hex(0x354043), - bg_selected: hex(0x2c3538), - bg_input: hex(0x232a2d), - bg_progress: hex(0x1a2124), - - text_primary: hex(0xdadada), - text_secondary: hex(0xc4c4c4), - text_muted: hex(0xb3b9b8), - text_dim: hex(0x9aa0a0), - text_dimmer: hex(0x808888), - text_dimmest: hex(0x667070), - text_placeholder: hex(0x808888), - - accent_blue: hex(0x67b0e8), - accent_icon: hex(0x67b0e8), - accent_progress: hex(0x67b0e8), - - btn_default: hex(0x232a2d), - btn_hover: hex(0x2c3538), - btn_pressed: hex(0x354043), - - success: hex(0x8ccf7e), - success_bg: hex(0x1a2e1e), - btn_success: hex(0x5aaa50), - btn_success_hover: hex(0x4c9e44), - btn_success_pressed: hex(0x3e9238), - - warning: hex(0xe5c76b), - warning_bg: hex(0x2e2a1a), - - error: hex(0xe57474), - error_light: hex(0xf08888), - error_bg: hex(0x2e1a1a), - btn_danger_bg: hex(0x2e1a1a), - btn_danger_hover: hex(0x4e2a2a), - btn_trash_hover: hex(0x9a3030), - btn_trash_pressed: hex(0x7a2020), - - bg_modal_section: hex(0x232a2d), - border_subtle: hex(0x2c3538), - divider: hex(0x2c3538), - }; - - // ── Everblush Light ── - pub const EVERBLUSH_LIGHT: Self = Self { - bg_primary: hex(0xe8eded), - bg_sidebar: hex(0xdce3e3), - bg_card: hex(0xd0d8d8), - bg_card_hover: hex(0xc4cece), - bg_card_pressed: hex(0xb8c4c4), - bg_selected: hex(0xc4cece), - bg_input: hex(0xd0d8d8), - bg_progress: hex(0xdce3e3), - - text_primary: hex(0x2d3437), - text_secondary: hex(0x3a4448), - text_muted: hex(0x4a5558), - text_dim: hex(0x5a6668), - text_dimmer: hex(0x6a7878), - text_dimmest: hex(0x7a8a8a), - text_placeholder: hex(0x6a7878), - - accent_blue: hex(0x3a88c0), - accent_icon: hex(0x3a88c0), - accent_progress: hex(0x3a88c0), - - btn_default: hex(0xd0d8d8), - btn_hover: hex(0xc4cece), - btn_pressed: hex(0xb8c4c4), - - success: hex(0x5aaa4e), - success_bg: hex(0xc8e8c4), - btn_success: hex(0x5aaa4e), - btn_success_hover: hex(0x4c9e42), - btn_success_pressed: hex(0x3e9236), - - warning: hex(0xc0a030), - warning_bg: hex(0xe8e0c0), - - error: hex(0xc85050), - error_light: hex(0xd86868), - error_bg: hex(0xf0d0d0), - btn_danger_bg: hex(0xf0d0d0), - btn_danger_hover: hex(0xe0b0b0), - btn_trash_hover: hex(0xc85050), - btn_trash_pressed: hex(0xa84040), - - bg_modal_section: hex(0xe8eded), - border_subtle: hex(0xc4cece), - divider: hex(0xc4cece), - }; - - // ── Kanagawa Wave (dark — default) ── - pub const KANAGAWA_WAVE: Self = Self { - bg_primary: hex(0x1F1F28), - bg_sidebar: hex(0x181820), - bg_card: hex(0x2A2A37), - bg_card_hover: hex(0x363646), - bg_card_pressed: hex(0x54546D), - bg_selected: hex(0x363646), - bg_input: hex(0x2A2A37), - bg_progress: hex(0x223249), - - text_primary: hex(0xDCD7BA), - text_secondary: hex(0xC8C093), - text_muted: hex(0x9CABCA), - text_dim: hex(0x938AA9), - text_dimmer: hex(0x727169), - text_dimmest: hex(0x54546D), - text_placeholder: hex(0x727169), - - accent_blue: hex(0x7E9CD8), - accent_icon: hex(0x7FB4CA), - accent_progress: hex(0x7FB4CA), - - btn_default: hex(0x2A2A37), - btn_hover: hex(0x363646), - btn_pressed: hex(0x54546D), - - success: hex(0x98BB6C), - success_bg: hex(0x2B3328), - btn_success: hex(0x76946A), - btn_success_hover: hex(0x68885c), - btn_success_pressed: hex(0x5a7c4e), - - warning: hex(0xE6C384), - warning_bg: hex(0x49443C), - - error: hex(0xE46876), - error_light: hex(0xFF5D62), - error_bg: hex(0x43242B), - btn_danger_bg: hex(0x43242B), - btn_danger_hover: hex(0x63343B), - btn_trash_hover: hex(0xC34043), - btn_trash_pressed: hex(0xA33033), - - bg_modal_section: hex(0x1F1F28), - border_subtle: hex(0x2A2A37), - divider: hex(0x2A2A37), - }; - - // ── Kanagawa Journal (warm parchment light) ── - // Inspired by the Grape "Mode journal" — warm beige/sepia paper tones - pub const KANAGAWA_DRAGON: Self = Self { - bg_primary: hex(0xd5cea3), // warm parchment - bg_sidebar: hex(0xc8b98e), // slightly darker sidebar - bg_card: hex(0xc8b98e), - bg_card_hover: hex(0xbdae80), - bg_card_pressed: hex(0xb0a070), - bg_selected: hex(0xbdae80), - bg_input: hex(0xc8b98e), - bg_progress: hex(0xc8b98e), - - text_primary: hex(0x43412e), // dark brown - text_secondary: hex(0x5c5840), - text_muted: hex(0x736e55), - text_dim: hex(0x8a856c), - text_dimmer: hex(0xa09a80), - text_dimmest: hex(0xb5ae94), - text_placeholder: hex(0x8a856c), - - accent_blue: hex(0x7a6840), // warm brown accent (matching screenshot) - accent_icon: hex(0x7a6840), - accent_progress: hex(0x7a6840), - - btn_default: hex(0xc8b98e), - btn_hover: hex(0xbdae80), - btn_pressed: hex(0xb0a070), - - success: hex(0x6f894e), - success_bg: hex(0xcbd8b8), - btn_success: hex(0x6e915f), - btn_success_hover: hex(0x608554), - btn_success_pressed: hex(0x527949), - - warning: hex(0xc49a20), - warning_bg: hex(0xe0d4a0), - - error: hex(0xc84053), - error_light: hex(0xd7474b), - error_bg: hex(0xe0bfb0), - btn_danger_bg: hex(0xe0bfb0), - btn_danger_hover: hex(0xd0a898), - btn_trash_hover: hex(0xc84053), - btn_trash_pressed: hex(0xa83043), - - bg_modal_section: hex(0xd5cea3), - border_subtle: hex(0xb8a878), - divider: hex(0xb8a878), - }; - - // ── Kanagawa Lotus (light) ── - pub const KANAGAWA_LOTUS: Self = Self { - bg_primary: hex(0xf2ecbc), - bg_sidebar: hex(0xe5ddb0), - bg_card: hex(0xe7dba0), - bg_card_hover: hex(0xd5cea3), - bg_card_pressed: hex(0xc8c093), - bg_selected: hex(0xd5cea3), - bg_input: hex(0xe4d794), - bg_progress: hex(0xe5ddb0), - - text_primary: hex(0x545464), - text_secondary: hex(0x43436c), - text_muted: hex(0x716e61), - text_dim: hex(0x8a8980), - text_dimmer: hex(0xa09cac), - text_dimmest: hex(0xb0acb8), - text_placeholder: hex(0x8a8980), - - accent_blue: hex(0x4d699b), - accent_icon: hex(0x5d57a3), - accent_progress: hex(0x5d57a3), - - btn_default: hex(0xe7dba0), - btn_hover: hex(0xd5cea3), - btn_pressed: hex(0xc8c093), - - success: hex(0x6f894e), - success_bg: hex(0xb7d0ae), - btn_success: hex(0x6e915f), - btn_success_hover: hex(0x608554), - btn_success_pressed: hex(0x527949), - - warning: hex(0xde9800), - warning_bg: hex(0xf9d791), - - error: hex(0xc84053), - error_light: hex(0xd7474b), - error_bg: hex(0xd9a594), - btn_danger_bg: hex(0xd9a594), - btn_danger_hover: hex(0xc89484), - btn_trash_hover: hex(0xc84053), - btn_trash_pressed: hex(0xa83043), - - bg_modal_section: hex(0xf2ecbc), - border_subtle: hex(0xd5cea3), - divider: hex(0xd5cea3), - }; -} - -// ════════════════════════════════════════════════════════════════════════ -// Additional theme palettes -// ════════════════════════════════════════════════════════════════════════ - -impl ThemePalette { - // ── Nord Dark ── - pub const NORD_DARK: Self = Self { - bg_primary: hex(0x2E3440), - bg_sidebar: hex(0x252B37), - bg_card: hex(0x3B4252), - bg_card_hover: hex(0x434C5E), - bg_card_pressed: hex(0x4C566A), - bg_selected: hex(0x434C5E), - bg_input: hex(0x3B4252), - bg_progress: hex(0x252B37), - text_primary: hex(0xD8DEE9), - text_secondary: hex(0xC0C8D4), - text_muted: hex(0xA0AEC0), - text_dim: hex(0x7E8FA4), - text_dimmer: hex(0x616E88), - text_dimmest: hex(0x4C566A), - text_placeholder: hex(0x616E88), - accent_blue: hex(0x88C0D0), - accent_icon: hex(0x81A1C1), - accent_progress: hex(0x81A1C1), - btn_default: hex(0x3B4252), - btn_hover: hex(0x434C5E), - btn_pressed: hex(0x4C566A), - success: hex(0xA3BE8C), - success_bg: hex(0x2A3425), - btn_success: hex(0x809B6C), - btn_success_hover: hex(0x728F60), - btn_success_pressed: hex(0x648354), - warning: hex(0xEBCB8B), - warning_bg: hex(0x3A3828), - error: hex(0xBF616A), - error_light: hex(0xD08080), - error_bg: hex(0x3C2830), - btn_danger_bg: hex(0x3C2830), - btn_danger_hover: hex(0x5C3840), - btn_trash_hover: hex(0xBF616A), - btn_trash_pressed: hex(0x9F5158), - bg_modal_section: hex(0x2E3440), - border_subtle: hex(0x3B4252), - divider: hex(0x3B4252), - }; - - // ── Nord Light ── - pub const NORD_LIGHT: Self = Self { - bg_primary: hex(0xECEFF4), - bg_sidebar: hex(0xE5E9F0), - bg_card: hex(0xD8DEE9), - bg_card_hover: hex(0xCCD4DF), - bg_card_pressed: hex(0xB8C2D0), - bg_selected: hex(0xCCD4DF), - bg_input: hex(0xD8DEE9), - bg_progress: hex(0xE5E9F0), - text_primary: hex(0x2E3440), - text_secondary: hex(0x3B4252), - text_muted: hex(0x4C566A), - text_dim: hex(0x636E80), - text_dimmer: hex(0x7E8FA4), - text_dimmest: hex(0xA0AAB8), - text_placeholder: hex(0x7E8FA4), - accent_blue: hex(0x5E81AC), - accent_icon: hex(0x81A1C1), - accent_progress: hex(0x81A1C1), - btn_default: hex(0xD8DEE9), - btn_hover: hex(0xCCD4DF), - btn_pressed: hex(0xB8C2D0), - success: hex(0x6B8C56), - success_bg: hex(0xD0E0C8), - btn_success: hex(0x6B8C56), - btn_success_hover: hex(0x5E8048), - btn_success_pressed: hex(0x50743A), - warning: hex(0xB5A050), - warning_bg: hex(0xE8DFC0), - error: hex(0xA04048), - error_light: hex(0xBF616A), - error_bg: hex(0xE8C8C8), - btn_danger_bg: hex(0xE8C8C8), - btn_danger_hover: hex(0xD8B0B0), - btn_trash_hover: hex(0xA04048), - btn_trash_pressed: hex(0x883438), - bg_modal_section: hex(0xECEFF4), - border_subtle: hex(0xCCD4DF), - divider: hex(0xCCD4DF), - }; - - // ── Dracula Dark ── - pub const DRACULA_DARK: Self = Self { - bg_primary: hex(0x282A36), - bg_sidebar: hex(0x21222C), - bg_card: hex(0x44475A), - bg_card_hover: hex(0x515470), - bg_card_pressed: hex(0x606382), - bg_selected: hex(0x44475A), - bg_input: hex(0x44475A), - bg_progress: hex(0x21222C), - text_primary: hex(0xF8F8F2), - text_secondary: hex(0xE8E8E0), - text_muted: hex(0xC8C8C0), - text_dim: hex(0x9898A0), - text_dimmer: hex(0x6272A4), - text_dimmest: hex(0x4C5478), - text_placeholder: hex(0x6272A4), - accent_blue: hex(0xBD93F9), - accent_icon: hex(0xFF79C6), - accent_progress: hex(0xBD93F9), - btn_default: hex(0x44475A), - btn_hover: hex(0x515470), - btn_pressed: hex(0x606382), - success: hex(0x50FA7B), - success_bg: hex(0x1E3228), - btn_success: hex(0x40C862), - btn_success_hover: hex(0x38B858), - btn_success_pressed: hex(0x30A84E), - warning: hex(0xF1FA8C), - warning_bg: hex(0x3A382A), - error: hex(0xFF5555), - error_light: hex(0xFF7777), - error_bg: hex(0x3C222A), - btn_danger_bg: hex(0x3C222A), - btn_danger_hover: hex(0x5C323A), - btn_trash_hover: hex(0xFF5555), - btn_trash_pressed: hex(0xDD4444), - bg_modal_section: hex(0x282A36), - border_subtle: hex(0x44475A), - divider: hex(0x44475A), - }; - - // ── Dracula Light ── - pub const DRACULA_LIGHT: Self = Self { - bg_primary: hex(0xFFFBEB), - bg_sidebar: hex(0xF4EFD8), - bg_card: hex(0xE8E4CF), - bg_card_hover: hex(0xDCD8C0), - bg_card_pressed: hex(0xD0CCB8), - bg_selected: hex(0xDCD8C0), - bg_input: hex(0xE8E4CF), - bg_progress: hex(0xF4EFD8), - text_primary: hex(0x1F1F1F), - text_secondary: hex(0x333333), - text_muted: hex(0x555555), - text_dim: hex(0x777777), - text_dimmer: hex(0x7970A9), - text_dimmest: hex(0xA09CA0), - text_placeholder: hex(0x7970A9), - accent_blue: hex(0x7C5FC2), - accent_icon: hex(0xE04886), - accent_progress: hex(0x7C5FC2), - btn_default: hex(0xE8E4CF), - btn_hover: hex(0xDCD8C0), - btn_pressed: hex(0xD0CCB8), - success: hex(0x2BA479), - success_bg: hex(0xD0E8D8), - btn_success: hex(0x2BA479), - btn_success_hover: hex(0x24966C), - btn_success_pressed: hex(0x1D8860), - warning: hex(0xD5A212), - warning_bg: hex(0xEDE0C0), - error: hex(0xDE3535), - error_light: hex(0xE85858), - error_bg: hex(0xEEDCDC), - btn_danger_bg: hex(0xEEDCDC), - btn_danger_hover: hex(0xDEC8C8), - btn_trash_hover: hex(0xDE3535), - btn_trash_pressed: hex(0xC02828), - bg_modal_section: hex(0xFFFBEB), - border_subtle: hex(0xDCD8C0), - divider: hex(0xDCD8C0), - }; - - // ── Solarized Dark ── - pub const SOLARIZED_DARK: Self = Self { - bg_primary: hex(0x002B36), - bg_sidebar: hex(0x00222C), - bg_card: hex(0x073642), - bg_card_hover: hex(0x0A4858), - bg_card_pressed: hex(0x1A5A6C), - bg_selected: hex(0x073642), - bg_input: hex(0x073642), - bg_progress: hex(0x00222C), - text_primary: hex(0x839496), - text_secondary: hex(0x93A1A1), - text_muted: hex(0x738C90), - text_dim: hex(0x657B83), - text_dimmer: hex(0x586E75), - text_dimmest: hex(0x475860), - text_placeholder: hex(0x586E75), - accent_blue: hex(0x268BD2), - accent_icon: hex(0x2AA198), - accent_progress: hex(0x268BD2), - btn_default: hex(0x073642), - btn_hover: hex(0x0A4858), - btn_pressed: hex(0x1A5A6C), - success: hex(0x859900), - success_bg: hex(0x0A2A18), - btn_success: hex(0x6A7A00), - btn_success_hover: hex(0x5C6C00), - btn_success_pressed: hex(0x4E5E00), - warning: hex(0xB58900), - warning_bg: hex(0x1A2818), - error: hex(0xDC322F), - error_light: hex(0xE8504E), - error_bg: hex(0x2A1A1A), - btn_danger_bg: hex(0x2A1A1A), - btn_danger_hover: hex(0x3A2A2A), - btn_trash_hover: hex(0xDC322F), - btn_trash_pressed: hex(0xBC2220), - bg_modal_section: hex(0x002B36), - border_subtle: hex(0x073642), - divider: hex(0x073642), - }; - - // ── Solarized Light ── - pub const SOLARIZED_LIGHT: Self = Self { - bg_primary: hex(0xFDF6E3), - bg_sidebar: hex(0xEEE8D5), - bg_card: hex(0xE6E0CA), - bg_card_hover: hex(0xDCD6C0), - bg_card_pressed: hex(0xD0CAB4), - bg_selected: hex(0xDCD6C0), - bg_input: hex(0xE6E0CA), - bg_progress: hex(0xEEE8D5), - text_primary: hex(0x657B83), - text_secondary: hex(0x586E75), - text_muted: hex(0x7C8E94), - text_dim: hex(0x93A1A1), - text_dimmer: hex(0xA8B4B8), - text_dimmest: hex(0xC0C8CA), - text_placeholder: hex(0xA8B4B8), - accent_blue: hex(0x268BD2), - accent_icon: hex(0x2AA198), - accent_progress: hex(0x268BD2), - btn_default: hex(0xE6E0CA), - btn_hover: hex(0xDCD6C0), - btn_pressed: hex(0xD0CAB4), - success: hex(0x859900), - success_bg: hex(0xDAE8C8), - btn_success: hex(0x6A7A00), - btn_success_hover: hex(0x5C6C00), - btn_success_pressed: hex(0x4E5E00), - warning: hex(0xB58900), - warning_bg: hex(0xECE0C0), - error: hex(0xDC322F), - error_light: hex(0xE8504E), - error_bg: hex(0xECCCCC), - btn_danger_bg: hex(0xECCCCC), - btn_danger_hover: hex(0xDCB8B8), - btn_trash_hover: hex(0xDC322F), - btn_trash_pressed: hex(0xBC2220), - bg_modal_section: hex(0xFDF6E3), - border_subtle: hex(0xDCD6C0), - divider: hex(0xDCD6C0), - }; - - // ── Tokyo Night ── - pub const TOKYONIGHT_NIGHT: Self = Self { - bg_primary: hex(0x1A1B26), - bg_sidebar: hex(0x16161E), - bg_card: hex(0x292E42), - bg_card_hover: hex(0x3B4261), - bg_card_pressed: hex(0x414868), - bg_selected: hex(0x2D3F76), - bg_input: hex(0x292E42), - bg_progress: hex(0x16161E), - text_primary: hex(0xC0CAF5), - text_secondary: hex(0xA9B1D6), - text_muted: hex(0x8890B0), - text_dim: hex(0x737AA2), - text_dimmer: hex(0x565F89), - text_dimmest: hex(0x414868), - text_placeholder: hex(0x565F89), - accent_blue: hex(0x7AA2F7), - accent_icon: hex(0x7DCFFF), - accent_progress: hex(0x7AA2F7), - btn_default: hex(0x292E42), - btn_hover: hex(0x3B4261), - btn_pressed: hex(0x414868), - success: hex(0x9ECE6A), - success_bg: hex(0x202830), - btn_success: hex(0x7EAA50), - btn_success_hover: hex(0x709E44), - btn_success_pressed: hex(0x629238), - warning: hex(0xE0AF68), - warning_bg: hex(0x302820), - error: hex(0xF7768E), - error_light: hex(0xDB4B4B), - error_bg: hex(0x302025), - btn_danger_bg: hex(0x302025), - btn_danger_hover: hex(0x503035), - btn_trash_hover: hex(0xF7768E), - btn_trash_pressed: hex(0xD75E76), - bg_modal_section: hex(0x1A1B26), - border_subtle: hex(0x292E42), - divider: hex(0x292E42), - }; - - // ── Tokyo Night Day ── - pub const TOKYONIGHT_DAY: Self = Self { - bg_primary: hex(0xE1E2E7), - bg_sidebar: hex(0xD0D5E3), - bg_card: hex(0xC4C8DA), - bg_card_hover: hex(0xB7C1E3), - bg_card_pressed: hex(0xA8B4D4), - bg_selected: hex(0xB7C1E3), - bg_input: hex(0xC4C8DA), - bg_progress: hex(0xD0D5E3), - text_primary: hex(0x3760BF), - text_secondary: hex(0x4A6CA8), - text_muted: hex(0x6172B0), - text_dim: hex(0x848CB5), - text_dimmer: hex(0xA1A6C5), - text_dimmest: hex(0xB4B5B9), - text_placeholder: hex(0xA1A6C5), - accent_blue: hex(0x2E7DE9), - accent_icon: hex(0x007197), - accent_progress: hex(0x2E7DE9), - btn_default: hex(0xC4C8DA), - btn_hover: hex(0xB7C1E3), - btn_pressed: hex(0xA8B4D4), - success: hex(0x587539), - success_bg: hex(0xC8DCC0), - btn_success: hex(0x587539), - btn_success_hover: hex(0x4C692E), - btn_success_pressed: hex(0x405D24), - warning: hex(0x8C6C3E), - warning_bg: hex(0xDCD8C0), - error: hex(0xF52A65), - error_light: hex(0xC64343), - error_bg: hex(0xE8C0C8), - btn_danger_bg: hex(0xE8C0C8), - btn_danger_hover: hex(0xD8A8B4), - btn_trash_hover: hex(0xF52A65), - btn_trash_pressed: hex(0xD52050), - bg_modal_section: hex(0xE1E2E7), - border_subtle: hex(0xC4C8DA), - divider: hex(0xC4C8DA), - }; - - // ── Rosé Pine ── - pub const ROSEPINE_MAIN: Self = Self { - bg_primary: hex(0x191724), - bg_sidebar: hex(0x1F1D2E), - bg_card: hex(0x26233A), - bg_card_hover: hex(0x403D52), - bg_card_pressed: hex(0x524F67), - bg_selected: hex(0x403D52), - bg_input: hex(0x26233A), - bg_progress: hex(0x1F1D2E), - text_primary: hex(0xE0DEF4), - text_secondary: hex(0xCCCADD), - text_muted: hex(0x908CAA), - text_dim: hex(0x7E7A96), - text_dimmer: hex(0x6E6A86), - text_dimmest: hex(0x555168), - text_placeholder: hex(0x6E6A86), - accent_blue: hex(0x9CCFD8), - accent_icon: hex(0xC4A7E7), - accent_progress: hex(0x9CCFD8), - btn_default: hex(0x26233A), - btn_hover: hex(0x403D52), - btn_pressed: hex(0x524F67), - success: hex(0x31748F), - success_bg: hex(0x1E2830), - btn_success: hex(0x2A6478), - btn_success_hover: hex(0x24586C), - btn_success_pressed: hex(0x1E4C60), - warning: hex(0xF6C177), - warning_bg: hex(0x302820), - error: hex(0xEB6F92), - error_light: hex(0xF08098), - error_bg: hex(0x301828), - btn_danger_bg: hex(0x301828), - btn_danger_hover: hex(0x502838), - btn_trash_hover: hex(0xEB6F92), - btn_trash_pressed: hex(0xCB5878), - bg_modal_section: hex(0x191724), - border_subtle: hex(0x26233A), - divider: hex(0x26233A), - }; - - // ── Rosé Pine Moon ── - pub const ROSEPINE_MOON: Self = Self { - bg_primary: hex(0x232136), - bg_sidebar: hex(0x2A273F), - bg_card: hex(0x393552), - bg_card_hover: hex(0x44415A), - bg_card_pressed: hex(0x56526E), - bg_selected: hex(0x44415A), - bg_input: hex(0x393552), - bg_progress: hex(0x2A273F), - text_primary: hex(0xE0DEF4), - text_secondary: hex(0xCCCADD), - text_muted: hex(0x908CAA), - text_dim: hex(0x7E7A96), - text_dimmer: hex(0x6E6A86), - text_dimmest: hex(0x555168), - text_placeholder: hex(0x6E6A86), - accent_blue: hex(0x9CCFD8), - accent_icon: hex(0xC4A7E7), - accent_progress: hex(0x9CCFD8), - btn_default: hex(0x393552), - btn_hover: hex(0x44415A), - btn_pressed: hex(0x56526E), - success: hex(0x3E8FB0), - success_bg: hex(0x202830), - btn_success: hex(0x347A98), - btn_success_hover: hex(0x2C6E88), - btn_success_pressed: hex(0x246278), - warning: hex(0xF6C177), - warning_bg: hex(0x382820), - error: hex(0xEB6F92), - error_light: hex(0xF08098), - error_bg: hex(0x381828), - btn_danger_bg: hex(0x381828), - btn_danger_hover: hex(0x582838), - btn_trash_hover: hex(0xEB6F92), - btn_trash_pressed: hex(0xCB5878), - bg_modal_section: hex(0x232136), - border_subtle: hex(0x393552), - divider: hex(0x393552), - }; - - // ── Rosé Pine Dawn (light) ── - pub const ROSEPINE_DAWN: Self = Self { - bg_primary: hex(0xFAF4ED), - bg_sidebar: hex(0xF2E9E1), - bg_card: hex(0xF4EDE8), - bg_card_hover: hex(0xDFDAD9), - bg_card_pressed: hex(0xCECACD), - bg_selected: hex(0xDFDAD9), - bg_input: hex(0xF4EDE8), - bg_progress: hex(0xF2E9E1), - text_primary: hex(0x575279), - text_secondary: hex(0x6E6A86), - text_muted: hex(0x797593), - text_dim: hex(0x9893A5), - text_dimmer: hex(0xB0ACC0), - text_dimmest: hex(0xC0BCC8), - text_placeholder: hex(0xB0ACC0), - accent_blue: hex(0x56949F), - accent_icon: hex(0x907AA9), - accent_progress: hex(0x56949F), - btn_default: hex(0xF4EDE8), - btn_hover: hex(0xDFDAD9), - btn_pressed: hex(0xCECACD), - success: hex(0x286983), - success_bg: hex(0xD0E0D8), - btn_success: hex(0x286983), - btn_success_hover: hex(0x205C74), - btn_success_pressed: hex(0x184F65), - warning: hex(0xEA9D34), - warning_bg: hex(0xECE0C8), - error: hex(0xB4637A), - error_light: hex(0xC87890), - error_bg: hex(0xE8D0D4), - btn_danger_bg: hex(0xE8D0D4), - btn_danger_hover: hex(0xD8BCC4), - btn_trash_hover: hex(0xB4637A), - btn_trash_pressed: hex(0x965268), - bg_modal_section: hex(0xFAF4ED), - border_subtle: hex(0xDFDAD9), - divider: hex(0xDFDAD9), - }; - - // ── One Dark ── - pub const ONEDARK_DARK: Self = Self { - bg_primary: hex(0x282C34), - bg_sidebar: hex(0x21252B), - bg_card: hex(0x3E4452), - bg_card_hover: hex(0x4B5162), - bg_card_pressed: hex(0x5C6370), - bg_selected: hex(0x3E4452), - bg_input: hex(0x3E4452), - bg_progress: hex(0x21252B), - text_primary: hex(0xABB2BF), - text_secondary: hex(0x9DA4B0), - text_muted: hex(0x848B98), - text_dim: hex(0x6B7280), - text_dimmer: hex(0x5C6370), - text_dimmest: hex(0x4B5162), - text_placeholder: hex(0x5C6370), - accent_blue: hex(0x61AFEF), - accent_icon: hex(0x56B6C2), - accent_progress: hex(0x61AFEF), - btn_default: hex(0x3E4452), - btn_hover: hex(0x4B5162), - btn_pressed: hex(0x5C6370), - success: hex(0x98C379), - success_bg: hex(0x1E3428), - btn_success: hex(0x78A060), - btn_success_hover: hex(0x6C9454), - btn_success_pressed: hex(0x608848), - warning: hex(0xE5C07B), - warning_bg: hex(0x343020), - error: hex(0xE06C75), - error_light: hex(0xE88888), - error_bg: hex(0x342028), - btn_danger_bg: hex(0x342028), - btn_danger_hover: hex(0x543038), - btn_trash_hover: hex(0xE06C75), - btn_trash_pressed: hex(0xC05860), - bg_modal_section: hex(0x282C34), - border_subtle: hex(0x3E4452), - divider: hex(0x3E4452), - }; - - // ── One Dark Light ── - pub const ONEDARK_LIGHT: Self = Self { - bg_primary: hex(0xFAFAFA), - bg_sidebar: hex(0xF0F0F0), - bg_card: hex(0xE2E2E2), - bg_card_hover: hex(0xD4D4D4), - bg_card_pressed: hex(0xC8C8C8), - bg_selected: hex(0xD4D4D4), - bg_input: hex(0xE2E2E2), - bg_progress: hex(0xF0F0F0), - text_primary: hex(0x383A42), - text_secondary: hex(0x4A4C56), - text_muted: hex(0x686A76), - text_dim: hex(0x818387), - text_dimmer: hex(0xA0A1A7), - text_dimmest: hex(0xB8B9BC), - text_placeholder: hex(0xA0A1A7), - accent_blue: hex(0x4078F2), - accent_icon: hex(0x0184BC), - accent_progress: hex(0x4078F2), - btn_default: hex(0xE2E2E2), - btn_hover: hex(0xD4D4D4), - btn_pressed: hex(0xC8C8C8), - success: hex(0x50A14F), - success_bg: hex(0xD0E8D0), - btn_success: hex(0x50A14F), - btn_success_hover: hex(0x449444), - btn_success_pressed: hex(0x388838), - warning: hex(0xC18401), - warning_bg: hex(0xECE0C0), - error: hex(0xE45649), - error_light: hex(0xF06860), - error_bg: hex(0xEACCCC), - btn_danger_bg: hex(0xEACCCC), - btn_danger_hover: hex(0xDAB8B8), - btn_trash_hover: hex(0xE45649), - btn_trash_pressed: hex(0xC44038), - bg_modal_section: hex(0xFAFAFA), - border_subtle: hex(0xD4D4D4), - divider: hex(0xD4D4D4), - }; - - // ── Monokai Pro ── - pub const MONOKAI_PRO: Self = Self { - bg_primary: hex(0x2D2A2E), - bg_sidebar: hex(0x221F22), - bg_card: hex(0x403E41), - bg_card_hover: hex(0x525052), - bg_card_pressed: hex(0x5B595C), - bg_selected: hex(0x403E41), - bg_input: hex(0x403E41), - bg_progress: hex(0x221F22), - text_primary: hex(0xFCFCFA), - text_secondary: hex(0xE0E0DE), - text_muted: hex(0xC1C0C0), - text_dim: hex(0x939293), - text_dimmer: hex(0x727072), - text_dimmest: hex(0x5B595C), - text_placeholder: hex(0x727072), - accent_blue: hex(0x78DCE8), - accent_icon: hex(0xAB9DF2), - accent_progress: hex(0x78DCE8), - btn_default: hex(0x403E41), - btn_hover: hex(0x525052), - btn_pressed: hex(0x5B595C), - success: hex(0xA9DC76), - success_bg: hex(0x222820), - btn_success: hex(0x88B860), - btn_success_hover: hex(0x7CAC54), - btn_success_pressed: hex(0x70A048), - warning: hex(0xFFD866), - warning_bg: hex(0x302A20), - error: hex(0xFF6188), - error_light: hex(0xFF8098), - error_bg: hex(0x341E28), - btn_danger_bg: hex(0x341E28), - btn_danger_hover: hex(0x542E38), - btn_trash_hover: hex(0xFF6188), - btn_trash_pressed: hex(0xDF5070), - bg_modal_section: hex(0x2D2A2E), - border_subtle: hex(0x403E41), - divider: hex(0x403E41), - }; - - // ── Monokai Classic ── - pub const MONOKAI_CLASSIC: Self = Self { - bg_primary: hex(0x272822), - bg_sidebar: hex(0x1A1A18), - bg_card: hex(0x49483E), - bg_card_hover: hex(0x5A5950), - bg_card_pressed: hex(0x6B6A62), - bg_selected: hex(0x49483E), - bg_input: hex(0x49483E), - bg_progress: hex(0x1A1A18), - text_primary: hex(0xF8F8F2), - text_secondary: hex(0xE0E0D8), - text_muted: hex(0xC1C0C0), - text_dim: hex(0x939293), - text_dimmer: hex(0x75715E), - text_dimmest: hex(0x5B595C), - text_placeholder: hex(0x75715E), - accent_blue: hex(0x66D9EF), - accent_icon: hex(0xAE81FF), - accent_progress: hex(0x66D9EF), - btn_default: hex(0x49483E), - btn_hover: hex(0x5A5950), - btn_pressed: hex(0x6B6A62), - success: hex(0xA6E22E), - success_bg: hex(0x1E2E18), - btn_success: hex(0x86C020), - btn_success_hover: hex(0x7AB418), - btn_success_pressed: hex(0x6EA810), - warning: hex(0xE6DB74), - warning_bg: hex(0x302A18), - error: hex(0xF92672), - error_light: hex(0xFF5090), - error_bg: hex(0x341828), - btn_danger_bg: hex(0x341828), - btn_danger_hover: hex(0x542838), - btn_trash_hover: hex(0xF92672), - btn_trash_pressed: hex(0xD91860), - bg_modal_section: hex(0x272822), - border_subtle: hex(0x49483E), - divider: hex(0x49483E), - }; - - // ── Monokai Spectrum ── - pub const MONOKAI_SPECTRUM: Self = Self { - bg_primary: hex(0x222222), - bg_sidebar: hex(0x191919), - bg_card: hex(0x363537), - bg_card_hover: hex(0x484749), - bg_card_pressed: hex(0x5A595B), - bg_selected: hex(0x363537), - bg_input: hex(0x363537), - bg_progress: hex(0x191919), - text_primary: hex(0xF7F1FF), - text_secondary: hex(0xE0DAE8), - text_muted: hex(0xC0BAC8), - text_dim: hex(0x938EA0), - text_dimmer: hex(0x69676C), - text_dimmest: hex(0x4A484C), - text_placeholder: hex(0x69676C), - accent_blue: hex(0x5AD4E6), - accent_icon: hex(0x948AE3), - accent_progress: hex(0x5AD4E6), - btn_default: hex(0x363537), - btn_hover: hex(0x484749), - btn_pressed: hex(0x5A595B), - success: hex(0x7BD88F), - success_bg: hex(0x1A2820), - btn_success: hex(0x62B074), - btn_success_hover: hex(0x58A468), - btn_success_pressed: hex(0x4E985C), - warning: hex(0xFCE566), - warning_bg: hex(0x2E2A1A), - error: hex(0xFC618D), - error_light: hex(0xFF80A0), - error_bg: hex(0x2E1A22), - btn_danger_bg: hex(0x2E1A22), - btn_danger_hover: hex(0x4E2A32), - btn_trash_hover: hex(0xFC618D), - btn_trash_pressed: hex(0xDC5078), - bg_modal_section: hex(0x222222), - border_subtle: hex(0x363537), - divider: hex(0x363537), - }; - - // ── Ayu Dark ── - pub const AYU_DARK: Self = Self { - bg_primary: hex(0x0B0E14), - bg_sidebar: hex(0x070A10), - bg_card: hex(0x1B2028), - bg_card_hover: hex(0x252B35), - bg_card_pressed: hex(0x303842), - bg_selected: hex(0x1B3A4B), - bg_input: hex(0x1B2028), - bg_progress: hex(0x070A10), - text_primary: hex(0xBFBDB6), - text_secondary: hex(0xA8A6A0), - text_muted: hex(0x8A8880), - text_dim: hex(0x7A786E), - text_dimmer: hex(0x626A73), - text_dimmest: hex(0x4A505A), - text_placeholder: hex(0x626A73), - accent_blue: hex(0xE6B450), - accent_icon: hex(0x59C2FF), - accent_progress: hex(0xE6B450), - btn_default: hex(0x1B2028), - btn_hover: hex(0x252B35), - btn_pressed: hex(0x303842), - success: hex(0xAAD94C), - success_bg: hex(0x0C1A10), - btn_success: hex(0x88B040), - btn_success_hover: hex(0x7CA436), - btn_success_pressed: hex(0x70982C), - warning: hex(0xFFB454), - warning_bg: hex(0x1A1808), - error: hex(0xD95757), - error_light: hex(0xE87070), - error_bg: hex(0x1A0C0C), - btn_danger_bg: hex(0x1A0C0C), - btn_danger_hover: hex(0x2A1C1C), - btn_trash_hover: hex(0xD95757), - btn_trash_pressed: hex(0xB94848), - bg_modal_section: hex(0x0B0E14), - border_subtle: hex(0x1B2028), - divider: hex(0x1B2028), - }; - - // ── Ayu Mirage ── - pub const AYU_MIRAGE: Self = Self { - bg_primary: hex(0x1F2430), - bg_sidebar: hex(0x1A1F2B), - bg_card: hex(0x2A303E), - bg_card_hover: hex(0x33415E), - bg_card_pressed: hex(0x3D4A68), - bg_selected: hex(0x33415E), - bg_input: hex(0x2A303E), - bg_progress: hex(0x1A1F2B), - text_primary: hex(0xCCCAC2), - text_secondary: hex(0xB4B2AA), - text_muted: hex(0x9A988E), - text_dim: hex(0x858380), - text_dimmer: hex(0x707A8C), - text_dimmest: hex(0x555D6E), - text_placeholder: hex(0x707A8C), - accent_blue: hex(0xFFCC66), - accent_icon: hex(0x73D0FF), - accent_progress: hex(0xFFCC66), - btn_default: hex(0x2A303E), - btn_hover: hex(0x33415E), - btn_pressed: hex(0x3D4A68), - success: hex(0xBAE67E), - success_bg: hex(0x1A2A1E), - btn_success: hex(0x98C066), - btn_success_hover: hex(0x8CB45A), - btn_success_pressed: hex(0x80A84E), - warning: hex(0xFFD580), - warning_bg: hex(0x2A2818), - error: hex(0xF28779), - error_light: hex(0xFF9E90), - error_bg: hex(0x2A1E1E), - btn_danger_bg: hex(0x2A1E1E), - btn_danger_hover: hex(0x4A2E2E), - btn_trash_hover: hex(0xF28779), - btn_trash_pressed: hex(0xD27060), - bg_modal_section: hex(0x1F2430), - border_subtle: hex(0x2A303E), - divider: hex(0x2A303E), - }; - - // ── Ayu Light ── - pub const AYU_LIGHT: Self = Self { - bg_primary: hex(0xFAFAFA), - bg_sidebar: hex(0xF0EEE4), - bg_card: hex(0xE8E6DC), - bg_card_hover: hex(0xDCD8CC), - bg_card_pressed: hex(0xD0CCC0), - bg_selected: hex(0xD1E4F4), - bg_input: hex(0xE8E6DC), - bg_progress: hex(0xF0EEE4), - text_primary: hex(0x575F66), - text_secondary: hex(0x6B737A), - text_muted: hex(0x848C94), - text_dim: hex(0x9CA4AC), - text_dimmer: hex(0xABB0B6), - text_dimmest: hex(0xC0C4C8), - text_placeholder: hex(0xABB0B6), - accent_blue: hex(0xFF9940), - accent_icon: hex(0x36A3D9), - accent_progress: hex(0xFF9940), - btn_default: hex(0xE8E6DC), - btn_hover: hex(0xDCD8CC), - btn_pressed: hex(0xD0CCC0), - success: hex(0x86B300), - success_bg: hex(0xD8E8C8), - btn_success: hex(0x86B300), - btn_success_hover: hex(0x78A400), - btn_success_pressed: hex(0x6A9600), - warning: hex(0xF29718), - warning_bg: hex(0xEDE0C0), - error: hex(0xF51818), - error_light: hex(0xF04040), - error_bg: hex(0xEEC8C8), - btn_danger_bg: hex(0xEEC8C8), - btn_danger_hover: hex(0xDEB0B0), - btn_trash_hover: hex(0xF51818), - btn_trash_pressed: hex(0xD50E0E), - bg_modal_section: hex(0xFAFAFA), - border_subtle: hex(0xDCD8CC), - divider: hex(0xDCD8CC), - }; - - // ── Everforest Dark ── - pub const EVERFOREST_DARK: Self = Self { - bg_primary: hex(0x2D353B), - bg_sidebar: hex(0x232A2E), - bg_card: hex(0x343F44), - bg_card_hover: hex(0x3D484D), - bg_card_pressed: hex(0x475258), - bg_selected: hex(0x543A48), - bg_input: hex(0x343F44), - bg_progress: hex(0x232A2E), - text_primary: hex(0xD3C6AA), - text_secondary: hex(0xC0B498), - text_muted: hex(0xA09880), - text_dim: hex(0x859289), - text_dimmer: hex(0x7A8478), - text_dimmest: hex(0x5C6A62), - text_placeholder: hex(0x7A8478), - accent_blue: hex(0x7FBBB3), - accent_icon: hex(0xA7C080), - accent_progress: hex(0x7FBBB3), - btn_default: hex(0x343F44), - btn_hover: hex(0x3D484D), - btn_pressed: hex(0x475258), - success: hex(0xA7C080), - success_bg: hex(0x1E2E24), - btn_success: hex(0x86A066), - btn_success_hover: hex(0x7A945A), - btn_success_pressed: hex(0x6E884E), - warning: hex(0xDBBC7F), - warning_bg: hex(0x2E2A1E), - error: hex(0xE67E80), - error_light: hex(0xF09090), - error_bg: hex(0x2E2020), - btn_danger_bg: hex(0x2E2020), - btn_danger_hover: hex(0x4E3030), - btn_trash_hover: hex(0xE67E80), - btn_trash_pressed: hex(0xC66868), - bg_modal_section: hex(0x2D353B), - border_subtle: hex(0x343F44), - divider: hex(0x343F44), - }; - - // ── Everforest Light ── - pub const EVERFOREST_LIGHT: Self = Self { - bg_primary: hex(0xFDF6E3), - bg_sidebar: hex(0xEFECD4), - bg_card: hex(0xF4F0D9), - bg_card_hover: hex(0xE6E2CC), - bg_card_pressed: hex(0xE0DCC7), - bg_selected: hex(0xEADDC0), - bg_input: hex(0xF4F0D9), - bg_progress: hex(0xEFECD4), - text_primary: hex(0x5C6A72), - text_secondary: hex(0x6E7A80), - text_muted: hex(0x829181), - text_dim: hex(0x939F91), - text_dimmer: hex(0xA6B0A0), - text_dimmest: hex(0xBCC5B8), - text_placeholder: hex(0xA6B0A0), - accent_blue: hex(0x3A94C5), - accent_icon: hex(0x8DA101), - accent_progress: hex(0x3A94C5), - btn_default: hex(0xF4F0D9), - btn_hover: hex(0xE6E2CC), - btn_pressed: hex(0xE0DCC7), - success: hex(0x8DA101), - success_bg: hex(0xD0E0C4), - btn_success: hex(0x8DA101), - btn_success_hover: hex(0x7E9200), - btn_success_pressed: hex(0x6F8300), - warning: hex(0xDFA000), - warning_bg: hex(0xECE0B8), - error: hex(0xF85552), - error_light: hex(0xE86868), - error_bg: hex(0xECC8C4), - btn_danger_bg: hex(0xECC8C4), - btn_danger_hover: hex(0xDCB0AC), - btn_trash_hover: hex(0xF85552), - btn_trash_pressed: hex(0xD84040), - bg_modal_section: hex(0xFDF6E3), - border_subtle: hex(0xE6E2CC), - divider: hex(0xE6E2CC), - }; - - // ── Material Oceanic ── - pub const MATERIAL_OCEANIC: Self = Self { - bg_primary: hex(0x263238), - bg_sidebar: hex(0x1E272C), - bg_card: hex(0x2E3C42), - bg_card_hover: hex(0x3A4A52), - bg_card_pressed: hex(0x465862), - bg_selected: hex(0x546E7A), - bg_input: hex(0x2E3C42), - bg_progress: hex(0x1E272C), - text_primary: hex(0xB0BEC5), - text_secondary: hex(0x9AAAB2), - text_muted: hex(0x849AA4), - text_dim: hex(0x6E8490), - text_dimmer: hex(0x546E7A), - text_dimmest: hex(0x405A64), - text_placeholder: hex(0x546E7A), - accent_blue: hex(0x89DDFF), - accent_icon: hex(0x80CBC4), - accent_progress: hex(0x89DDFF), - btn_default: hex(0x2E3C42), - btn_hover: hex(0x3A4A52), - btn_pressed: hex(0x465862), - success: hex(0xC3E88D), - success_bg: hex(0x1A3028), - btn_success: hex(0xA0C070), - btn_success_hover: hex(0x94B464), - btn_success_pressed: hex(0x88A858), - warning: hex(0xFFCB6B), - warning_bg: hex(0x2E2E1E), - error: hex(0xFF5370), - error_light: hex(0xFF7088), - error_bg: hex(0x2E1E22), - btn_danger_bg: hex(0x2E1E22), - btn_danger_hover: hex(0x4E2E32), - btn_trash_hover: hex(0xFF5370), - btn_trash_pressed: hex(0xDF4058), - bg_modal_section: hex(0x263238), - border_subtle: hex(0x2E3C42), - divider: hex(0x2E3C42), - }; - - // ── Material Palenight ── - pub const MATERIAL_PALENIGHT: Self = Self { - bg_primary: hex(0x292D3E), - bg_sidebar: hex(0x232838), - bg_card: hex(0x343A4E), - bg_card_hover: hex(0x414662), - bg_card_pressed: hex(0x515772), - bg_selected: hex(0x717CB4), - bg_input: hex(0x343A4E), - bg_progress: hex(0x232838), - text_primary: hex(0xA6ACCD), - text_secondary: hex(0x929ABE), - text_muted: hex(0x7E86AE), - text_dim: hex(0x717CB4), - text_dimmer: hex(0x676E95), - text_dimmest: hex(0x515772), - text_placeholder: hex(0x676E95), - accent_blue: hex(0xC792EA), - accent_icon: hex(0x82AAFF), - accent_progress: hex(0xC792EA), - btn_default: hex(0x343A4E), - btn_hover: hex(0x414662), - btn_pressed: hex(0x515772), - success: hex(0xC3E88D), - success_bg: hex(0x202E28), - btn_success: hex(0xA0C070), - btn_success_hover: hex(0x94B464), - btn_success_pressed: hex(0x88A858), - warning: hex(0xFFCB6B), - warning_bg: hex(0x2E2C1E), - error: hex(0xFF5370), - error_light: hex(0xFF7088), - error_bg: hex(0x2E1E22), - btn_danger_bg: hex(0x2E1E22), - btn_danger_hover: hex(0x4E2E32), - btn_trash_hover: hex(0xFF5370), - btn_trash_pressed: hex(0xDF4058), - bg_modal_section: hex(0x292D3E), - border_subtle: hex(0x343A4E), - divider: hex(0x343A4E), - }; - - // ── Material Deep Ocean ── - pub const MATERIAL_DEEPOCEAN: Self = Self { - bg_primary: hex(0x0F111A), - bg_sidebar: hex(0x090B10), - bg_card: hex(0x1A1C28), - bg_card_hover: hex(0x252836), - bg_card_pressed: hex(0x3B3F51), - bg_selected: hex(0x44475A), - bg_input: hex(0x1A1C28), - bg_progress: hex(0x090B10), - text_primary: hex(0x8F93A2), - text_secondary: hex(0xA0A4B4), - text_muted: hex(0x7880A0), - text_dim: hex(0x606888), - text_dimmer: hex(0x464B5D), - text_dimmest: hex(0x3B3F51), - text_placeholder: hex(0x464B5D), - accent_blue: hex(0x84FFFF), - accent_icon: hex(0x82AAFF), - accent_progress: hex(0x84FFFF), - btn_default: hex(0x1A1C28), - btn_hover: hex(0x252836), - btn_pressed: hex(0x3B3F51), - success: hex(0xC3E88D), - success_bg: hex(0x0C1A14), - btn_success: hex(0xA0C070), - btn_success_hover: hex(0x94B464), - btn_success_pressed: hex(0x88A858), - warning: hex(0xFFCB6B), - warning_bg: hex(0x1A180C), - error: hex(0xFF5370), - error_light: hex(0xFF7088), - error_bg: hex(0x1A0C10), - btn_danger_bg: hex(0x1A0C10), - btn_danger_hover: hex(0x3A1C20), - btn_trash_hover: hex(0xFF5370), - btn_trash_pressed: hex(0xDF4058), - bg_modal_section: hex(0x0F111A), - border_subtle: hex(0x1A1C28), - divider: hex(0x1A1C28), - }; - - // ── Flexoki Dark ── - pub const FLEXOKI_DARK: Self = Self { - bg_primary: hex(0x100F0F), - bg_sidebar: hex(0x1C1B1A), - bg_card: hex(0x282726), - bg_card_hover: hex(0x343331), - bg_card_pressed: hex(0x403E3C), - bg_selected: hex(0x403E3C), - bg_input: hex(0x282726), - bg_progress: hex(0x1C1B1A), - text_primary: hex(0xCECDC3), - text_secondary: hex(0xB7B5AC), - text_muted: hex(0x9F9D96), - text_dim: hex(0x878580), - text_dimmer: hex(0x6F6E69), - text_dimmest: hex(0x575653), - text_placeholder: hex(0x6F6E69), - accent_blue: hex(0x4385BE), - accent_icon: hex(0xDA702C), - accent_progress: hex(0x4385BE), - btn_default: hex(0x282726), - btn_hover: hex(0x343331), - btn_pressed: hex(0x403E3C), - success: hex(0x879A39), - success_bg: hex(0x141A10), - btn_success: hex(0x6E8030), - btn_success_hover: hex(0x627428), - btn_success_pressed: hex(0x566820), - warning: hex(0xD0A215), - warning_bg: hex(0x1E1A0C), - error: hex(0xD14D41), - error_light: hex(0xE06858), - error_bg: hex(0x1E100C), - btn_danger_bg: hex(0x1E100C), - btn_danger_hover: hex(0x3E201C), - btn_trash_hover: hex(0xD14D41), - btn_trash_pressed: hex(0xB13830), - bg_modal_section: hex(0x100F0F), - border_subtle: hex(0x282726), - divider: hex(0x282726), - }; - - // ── Flexoki Light ── - pub const FLEXOKI_LIGHT: Self = Self { - bg_primary: hex(0xFFFCF0), - bg_sidebar: hex(0xF2F0E5), - bg_card: hex(0xE6E4D9), - bg_card_hover: hex(0xDAD8CE), - bg_card_pressed: hex(0xCECDC3), - bg_selected: hex(0xE6E4D9), - bg_input: hex(0xE6E4D9), - bg_progress: hex(0xF2F0E5), - text_primary: hex(0x403E3C), - text_secondary: hex(0x575653), - text_muted: hex(0x6F6E69), - text_dim: hex(0x878580), - text_dimmer: hex(0x9F9D96), - text_dimmest: hex(0xB7B5AC), - text_placeholder: hex(0x9F9D96), - accent_blue: hex(0x205EA6), - accent_icon: hex(0xBC5215), - accent_progress: hex(0x205EA6), - btn_default: hex(0xE6E4D9), - btn_hover: hex(0xDAD8CE), - btn_pressed: hex(0xCECDC3), - success: hex(0x66800B), - success_bg: hex(0xD0E0C0), - btn_success: hex(0x66800B), - btn_success_hover: hex(0x587208), - btn_success_pressed: hex(0x4A6405), - warning: hex(0xAD8301), - warning_bg: hex(0xE0DCC0), - error: hex(0xAF3029), - error_light: hex(0xD14D41), - error_bg: hex(0xE0C8C0), - btn_danger_bg: hex(0xE0C8C0), - btn_danger_hover: hex(0xD0B4AC), - btn_trash_hover: hex(0xAF3029), - btn_trash_pressed: hex(0x902420), - bg_modal_section: hex(0xFFFCF0), - border_subtle: hex(0xDAD8CE), - divider: hex(0xDAD8CE), - }; - - // ── Nightfox ── - pub const NIGHTFOX: Self = Self { - bg_primary: hex(0x192330), - bg_sidebar: hex(0x131A24), - bg_card: hex(0x2B3B51), - bg_card_hover: hex(0x3C5372), - bg_card_pressed: hex(0x39506D), - bg_selected: hex(0x2B3B51), - bg_input: hex(0x2B3B51), - bg_progress: hex(0x131A24), - text_primary: hex(0xCDCECF), - text_secondary: hex(0xAEAFB0), - text_muted: hex(0x8E9098), - text_dim: hex(0x738091), - text_dimmer: hex(0x5C6A7C), - text_dimmest: hex(0x3E4E62), - text_placeholder: hex(0x5C6A7C), - accent_blue: hex(0x719CD6), - accent_icon: hex(0x63CDCF), - accent_progress: hex(0x719CD6), - btn_default: hex(0x2B3B51), - btn_hover: hex(0x3C5372), - btn_pressed: hex(0x39506D), - success: hex(0x81B29A), - success_bg: hex(0x142820), - btn_success: hex(0x68907E), - btn_success_hover: hex(0x5C8472), - btn_success_pressed: hex(0x507866), - warning: hex(0xDBC074), - warning_bg: hex(0x282418), - error: hex(0xC94F6D), - error_light: hex(0xD66880), - error_bg: hex(0x281820), - btn_danger_bg: hex(0x281820), - btn_danger_hover: hex(0x482830), - btn_trash_hover: hex(0xC94F6D), - btn_trash_pressed: hex(0xA9405C), - bg_modal_section: hex(0x192330), - border_subtle: hex(0x2B3B51), - divider: hex(0x2B3B51), - }; - - // ── Dawnfox (light) ── - pub const DAWNFOX: Self = Self { - bg_primary: hex(0xFAF4ED), - bg_sidebar: hex(0xEBE5DF), - bg_card: hex(0xEBD8CE), - bg_card_hover: hex(0xDACDC3), - bg_card_pressed: hex(0xC8BEB4), - bg_selected: hex(0xEBD8CE), - bg_input: hex(0xEBD8CE), - bg_progress: hex(0xEBE5DF), - text_primary: hex(0x575279), - text_secondary: hex(0x625C87), - text_muted: hex(0x6E6A86), - text_dim: hex(0x9893A5), - text_dimmer: hex(0xAEA8B8), - text_dimmest: hex(0xC0BAC8), - text_placeholder: hex(0xAEA8B8), - accent_blue: hex(0x286983), - accent_icon: hex(0x907AA9), - accent_progress: hex(0x286983), - btn_default: hex(0xEBD8CE), - btn_hover: hex(0xDACDC3), - btn_pressed: hex(0xC8BEB4), - success: hex(0x618774), - success_bg: hex(0xD0E0D4), - btn_success: hex(0x618774), - btn_success_hover: hex(0x547A68), - btn_success_pressed: hex(0x476D5C), - warning: hex(0xEA9D34), - warning_bg: hex(0xECE0C8), - error: hex(0xB4637A), - error_light: hex(0xC87890), - error_bg: hex(0xE8D0D4), - btn_danger_bg: hex(0xE8D0D4), - btn_danger_hover: hex(0xD8BCC4), - btn_trash_hover: hex(0xB4637A), - btn_trash_pressed: hex(0x965268), - bg_modal_section: hex(0xFAF4ED), - border_subtle: hex(0xDACDC3), - divider: hex(0xDACDC3), - }; - - // ── Sonokai ── - pub const SONOKAI_DEFAULT: Self = Self { - bg_primary: hex(0x2C2E34), - bg_sidebar: hex(0x242529), - bg_card: hex(0x33353F), - bg_card_hover: hex(0x3B3E48), - bg_card_pressed: hex(0x414550), - bg_selected: hex(0x3B3E48), - bg_input: hex(0x33353F), - bg_progress: hex(0x242529), - text_primary: hex(0xE2E2E3), - text_secondary: hex(0xCCCCD0), - text_muted: hex(0xA8A8B0), - text_dim: hex(0x8C8C96), - text_dimmer: hex(0x7F8490), - text_dimmest: hex(0x585C68), - text_placeholder: hex(0x7F8490), - accent_blue: hex(0x76CCE0), - accent_icon: hex(0xB39DF3), - accent_progress: hex(0x76CCE0), - btn_default: hex(0x33353F), - btn_hover: hex(0x3B3E48), - btn_pressed: hex(0x414550), - success: hex(0x9ED072), - success_bg: hex(0x1E2E22), - btn_success: hex(0x7EAA5A), - btn_success_hover: hex(0x729E4E), - btn_success_pressed: hex(0x669242), - warning: hex(0xE7C664), - warning_bg: hex(0x2E2A1A), - error: hex(0xFC5D7C), - error_light: hex(0xFF7890), - error_bg: hex(0x2E1A20), - btn_danger_bg: hex(0x2E1A20), - btn_danger_hover: hex(0x4E2A30), - btn_trash_hover: hex(0xFC5D7C), - btn_trash_pressed: hex(0xDC4C66), - bg_modal_section: hex(0x2C2E34), - border_subtle: hex(0x33353F), - divider: hex(0x33353F), - }; - - // ── Oxocarbon Dark ── - pub const OXOCARBON_DARK: Self = Self { - bg_primary: hex(0x161616), - bg_sidebar: hex(0x0E0E0E), - bg_card: hex(0x262626), - bg_card_hover: hex(0x393939), - bg_card_pressed: hex(0x525252), - bg_selected: hex(0x393939), - bg_input: hex(0x262626), - bg_progress: hex(0x0E0E0E), - text_primary: hex(0xF2F4F8), - text_secondary: hex(0xDDE1E6), - text_muted: hex(0xB0B4BC), - text_dim: hex(0x8A8E96), - text_dimmer: hex(0x6E7278), - text_dimmest: hex(0x525252), - text_placeholder: hex(0x6E7278), - accent_blue: hex(0x78A9FF), - accent_icon: hex(0xBE95FF), - accent_progress: hex(0x78A9FF), - btn_default: hex(0x262626), - btn_hover: hex(0x393939), - btn_pressed: hex(0x525252), - success: hex(0x42BE65), - success_bg: hex(0x0C1C12), - btn_success: hex(0x359E52), - btn_success_hover: hex(0x2C9048), - btn_success_pressed: hex(0x24823E), - warning: hex(0x08BDBA), - warning_bg: hex(0x0C1A1A), - error: hex(0xEE5396), - error_light: hex(0xFF7EB6), - error_bg: hex(0x1C0C14), - btn_danger_bg: hex(0x1C0C14), - btn_danger_hover: hex(0x3C1C24), - btn_trash_hover: hex(0xEE5396), - btn_trash_pressed: hex(0xCE4480), - bg_modal_section: hex(0x161616), - border_subtle: hex(0x262626), - divider: hex(0x262626), - }; - - // ── Oxocarbon Light ── - pub const OXOCARBON_LIGHT: Self = Self { - bg_primary: hex(0xFFFFFF), - bg_sidebar: hex(0xF2F4F8), - bg_card: hex(0xDDE1E6), - bg_card_hover: hex(0xC8CCD2), - bg_card_pressed: hex(0xB4B8C0), - bg_selected: hex(0xDDE1E6), - bg_input: hex(0xDDE1E6), - bg_progress: hex(0xF2F4F8), - text_primary: hex(0x262626), - text_secondary: hex(0x393939), - text_muted: hex(0x525252), - text_dim: hex(0x6E7278), - text_dimmer: hex(0x8A8E96), - text_dimmest: hex(0xB0B4BC), - text_placeholder: hex(0x8A8E96), - accent_blue: hex(0x0F62FE), - accent_icon: hex(0x8A3FFC), - accent_progress: hex(0x0F62FE), - btn_default: hex(0xDDE1E6), - btn_hover: hex(0xC8CCD2), - btn_pressed: hex(0xB4B8C0), - success: hex(0x198038), - success_bg: hex(0xD0F0D8), - btn_success: hex(0x198038), - btn_success_hover: hex(0x14702E), - btn_success_pressed: hex(0x106024), - warning: hex(0x005D5D), - warning_bg: hex(0xD0E8E8), - error: hex(0xDA1E28), - error_light: hex(0xEE5396), - error_bg: hex(0xF0D0D8), - btn_danger_bg: hex(0xF0D0D8), - btn_danger_hover: hex(0xE0B8C4), - btn_trash_hover: hex(0xDA1E28), - btn_trash_pressed: hex(0xBA1420), - bg_modal_section: hex(0xFFFFFF), - border_subtle: hex(0xC8CCD2), - divider: hex(0xC8CCD2), - }; - - // ── Night Owl Dark ── - pub const NIGHTOWL_DARK: Self = Self { - bg_primary: hex(0x011627), - bg_sidebar: hex(0x011221), - bg_card: hex(0x0B2942), - bg_card_hover: hex(0x1D3B53), - bg_card_pressed: hex(0x2A4F6C), - bg_selected: hex(0x1D3B53), - bg_input: hex(0x0B2942), - bg_progress: hex(0x011221), - text_primary: hex(0xD6DEEB), - text_secondary: hex(0xB4C0D0), - text_muted: hex(0x8CA0B4), - text_dim: hex(0x7E94A8), - text_dimmer: hex(0x637777), - text_dimmest: hex(0x4A6060), - text_placeholder: hex(0x637777), - accent_blue: hex(0x82AAFF), - accent_icon: hex(0x7FDBCA), - accent_progress: hex(0x82AAFF), - btn_default: hex(0x0B2942), - btn_hover: hex(0x1D3B53), - btn_pressed: hex(0x2A4F6C), - success: hex(0xADDB67), - success_bg: hex(0x011E14), - btn_success: hex(0x8CB852), - btn_success_hover: hex(0x80AC48), - btn_success_pressed: hex(0x74A03E), - warning: hex(0xECC48D), - warning_bg: hex(0x1A1808), - error: hex(0xEF5350), - error_light: hex(0xFF6E6A), - error_bg: hex(0x1A0808), - btn_danger_bg: hex(0x1A0808), - btn_danger_hover: hex(0x3A1818), - btn_trash_hover: hex(0xEF5350), - btn_trash_pressed: hex(0xCF4040), - bg_modal_section: hex(0x011627), - border_subtle: hex(0x0B2942), - divider: hex(0x0B2942), - }; - - // ── Night Owl Light ── - pub const NIGHTOWL_LIGHT: Self = Self { - bg_primary: hex(0xFBFBFB), - bg_sidebar: hex(0xF0F0F0), - bg_card: hex(0xE8E8E8), - bg_card_hover: hex(0xE0E0E0), - bg_card_pressed: hex(0xD4D4D4), - bg_selected: hex(0xE0E0E0), - bg_input: hex(0xE8E8E8), - bg_progress: hex(0xF0F0F0), - text_primary: hex(0x403F53), - text_secondary: hex(0x565570), - text_muted: hex(0x6E6D88), - text_dim: hex(0x8888A0), - text_dimmer: hex(0x989FB1), - text_dimmest: hex(0xB0B4C0), - text_placeholder: hex(0x989FB1), - accent_blue: hex(0x4876D6), - accent_icon: hex(0x0C969B), - accent_progress: hex(0x4876D6), - btn_default: hex(0xE8E8E8), - btn_hover: hex(0xE0E0E0), - btn_pressed: hex(0xD4D4D4), - success: hex(0x2AA298), - success_bg: hex(0xD0E8E0), - btn_success: hex(0x2AA298), - btn_success_hover: hex(0x22948C), - btn_success_pressed: hex(0x1A8680), - warning: hex(0xD98E24), - warning_bg: hex(0xECE0C8), - error: hex(0xDE3D3B), - error_light: hex(0xE85858), - error_bg: hex(0xECC8C8), - btn_danger_bg: hex(0xECC8C8), - btn_danger_hover: hex(0xDCB0B0), - btn_trash_hover: hex(0xDE3D3B), - btn_trash_pressed: hex(0xBE2E2C), - bg_modal_section: hex(0xFBFBFB), - border_subtle: hex(0xE0E0E0), - divider: hex(0xE0E0E0), - }; - - // ── Iceberg Dark ── - pub const ICEBERG_DARK: Self = Self { - bg_primary: hex(0x161821), - bg_sidebar: hex(0x0F1117), - bg_card: hex(0x1E2132), - bg_card_hover: hex(0x2E313F), - bg_card_pressed: hex(0x3D425B), - bg_selected: hex(0x2E313F), - bg_input: hex(0x1E2132), - bg_progress: hex(0x0F1117), - text_primary: hex(0xC6C8D1), - text_secondary: hex(0xB0B4C0), - text_muted: hex(0x9498A8), - text_dim: hex(0x818596), - text_dimmer: hex(0x6B7089), - text_dimmest: hex(0x4E5268), - text_placeholder: hex(0x6B7089), - accent_blue: hex(0x84A0C6), - accent_icon: hex(0x89B8C2), - accent_progress: hex(0x84A0C6), - btn_default: hex(0x1E2132), - btn_hover: hex(0x2E313F), - btn_pressed: hex(0x3D425B), - success: hex(0xB4BE82), - success_bg: hex(0x141E18), - btn_success: hex(0x949E68), - btn_success_hover: hex(0x88925C), - btn_success_pressed: hex(0x7C8650), - warning: hex(0xE2A478), - warning_bg: hex(0x201C14), - error: hex(0xE27878), - error_light: hex(0xF09090), - error_bg: hex(0x201418), - btn_danger_bg: hex(0x201418), - btn_danger_hover: hex(0x402428), - btn_trash_hover: hex(0xE27878), - btn_trash_pressed: hex(0xC26060), - bg_modal_section: hex(0x161821), - border_subtle: hex(0x1E2132), - divider: hex(0x1E2132), - }; - - // ── Iceberg Light ── - pub const ICEBERG_LIGHT: Self = Self { - bg_primary: hex(0xE8E9EC), - bg_sidebar: hex(0xDCDFE7), - bg_card: hex(0xD0D4DE), - bg_card_hover: hex(0xCAD0DE), - bg_card_pressed: hex(0xBEC4D2), - bg_selected: hex(0xCAD0DE), - bg_input: hex(0xD0D4DE), - bg_progress: hex(0xDCDFE7), - text_primary: hex(0x33374C), - text_secondary: hex(0x444862), - text_muted: hex(0x5C6080), - text_dim: hex(0x747896), - text_dimmer: hex(0x8B98B6), - text_dimmest: hex(0xA8B0C8), - text_placeholder: hex(0x8B98B6), - accent_blue: hex(0x2D539E), - accent_icon: hex(0x33635C), - accent_progress: hex(0x2D539E), - btn_default: hex(0xD0D4DE), - btn_hover: hex(0xCAD0DE), - btn_pressed: hex(0xBEC4D2), - success: hex(0x668E3D), - success_bg: hex(0xD0E0C8), - btn_success: hex(0x668E3D), - btn_success_hover: hex(0x588032), - btn_success_pressed: hex(0x4A7228), - warning: hex(0xC57339), - warning_bg: hex(0xE4D8C8), - error: hex(0xCC517A), - error_light: hex(0xDD6890), - error_bg: hex(0xE4C8D4), - btn_danger_bg: hex(0xE4C8D4), - btn_danger_hover: hex(0xD4B4C0), - btn_trash_hover: hex(0xCC517A), - btn_trash_pressed: hex(0xAC4068), - bg_modal_section: hex(0xE8E9EC), - border_subtle: hex(0xCAD0DE), - divider: hex(0xCAD0DE), - }; - - // ── Horizon Dark ── - pub const HORIZON_DARK: Self = Self { - bg_primary: hex(0x1C1E26), - bg_sidebar: hex(0x16161C), - bg_card: hex(0x2E303E), - bg_card_hover: hex(0x3A3C4E), - bg_card_pressed: hex(0x484A60), - bg_selected: hex(0x2E303E), - bg_input: hex(0x2E303E), - bg_progress: hex(0x16161C), - text_primary: hex(0xD5D8DA), - text_secondary: hex(0xBEC0C4), - text_muted: hex(0xA0A2A8), - text_dim: hex(0x888A92), - text_dimmer: hex(0x6C6F93), - text_dimmest: hex(0x50526E), - text_placeholder: hex(0x6C6F93), - accent_blue: hex(0x26BBD9), - accent_icon: hex(0xB877DB), - accent_progress: hex(0x26BBD9), - btn_default: hex(0x2E303E), - btn_hover: hex(0x3A3C4E), - btn_pressed: hex(0x484A60), - success: hex(0x29D398), - success_bg: hex(0x141E1C), - btn_success: hex(0x22B480), - btn_success_hover: hex(0x1CA874), - btn_success_pressed: hex(0x169C68), - warning: hex(0xFAC29A), - warning_bg: hex(0x2A2218), - error: hex(0xE95678), - error_light: hex(0xF07090), - error_bg: hex(0x2A1620), - btn_danger_bg: hex(0x2A1620), - btn_danger_hover: hex(0x4A2630), - btn_trash_hover: hex(0xE95678), - btn_trash_pressed: hex(0xC94460), - bg_modal_section: hex(0x1C1E26), - border_subtle: hex(0x2E303E), - divider: hex(0x2E303E), - }; - - // ── Melange Dark ── - pub const MELANGE_DARK: Self = Self { - bg_primary: hex(0x292522), - bg_sidebar: hex(0x221E1B), - bg_card: hex(0x34302C), - bg_card_hover: hex(0x403A36), - bg_card_pressed: hex(0x4C4640), - bg_selected: hex(0x403A36), - bg_input: hex(0x34302C), - bg_progress: hex(0x221E1B), - text_primary: hex(0xECE1D7), - text_secondary: hex(0xD4C8BC), - text_muted: hex(0xBAB0A4), - text_dim: hex(0x9E9488), - text_dimmer: hex(0x867462), - text_dimmest: hex(0x6A5E50), - text_placeholder: hex(0x867462), - accent_blue: hex(0xA3A9CE), - accent_icon: hex(0x89B3B6), - accent_progress: hex(0xA3A9CE), - btn_default: hex(0x34302C), - btn_hover: hex(0x403A36), - btn_pressed: hex(0x4C4640), - success: hex(0x85B695), - success_bg: hex(0x1E2820), - btn_success: hex(0x6C9A7C), - btn_success_hover: hex(0x608E70), - btn_success_pressed: hex(0x548264), - warning: hex(0xEBC06D), - warning_bg: hex(0x2A261A), - error: hex(0xD47766), - error_light: hex(0xE89080), - error_bg: hex(0x2A1C18), - btn_danger_bg: hex(0x2A1C18), - btn_danger_hover: hex(0x4A2C28), - btn_trash_hover: hex(0xD47766), - btn_trash_pressed: hex(0xB46050), - bg_modal_section: hex(0x292522), - border_subtle: hex(0x34302C), - divider: hex(0x34302C), - }; - - // ── Melange Light ── - pub const MELANGE_LIGHT: Self = Self { - bg_primary: hex(0xF4F0ED), - bg_sidebar: hex(0xE9E1DB), - bg_card: hex(0xDDD2C8), - bg_card_hover: hex(0xD0C6BA), - bg_card_pressed: hex(0xC4BAAE), - bg_selected: hex(0xDDD2C8), - bg_input: hex(0xDDD2C8), - bg_progress: hex(0xE9E1DB), - text_primary: hex(0x54433A), - text_secondary: hex(0x6B5C4D), - text_muted: hex(0x7E6E5E), - text_dim: hex(0x948472), - text_dimmer: hex(0xA89888), - text_dimmest: hex(0xBEB0A0), - text_placeholder: hex(0xA89888), - accent_blue: hex(0x5E6DAB), - accent_icon: hex(0x3F7C82), - accent_progress: hex(0x5E6DAB), - btn_default: hex(0xDDD2C8), - btn_hover: hex(0xD0C6BA), - btn_pressed: hex(0xC4BAAE), - success: hex(0x4E7548), - success_bg: hex(0xD0E0D0), - btn_success: hex(0x4E7548), - btn_success_hover: hex(0x42683C), - btn_success_pressed: hex(0x365C30), - warning: hex(0x9A7C24), - warning_bg: hex(0xE0DCC0), - error: hex(0xA44C36), - error_light: hex(0xC06048), - error_bg: hex(0xE0CCC4), - btn_danger_bg: hex(0xE0CCC4), - btn_danger_hover: hex(0xD0B8B0), - btn_trash_hover: hex(0xA44C36), - btn_trash_pressed: hex(0x843C28), - bg_modal_section: hex(0xF4F0ED), - border_subtle: hex(0xD0C6BA), - divider: hex(0xD0C6BA), - }; - - // ── Synthwave '84 ── - pub const SYNTHWAVE_DARK: Self = Self { - bg_primary: hex(0x262335), - bg_sidebar: hex(0x241B2F), - bg_card: hex(0x34294F), - bg_card_hover: hex(0x3E3460), - bg_card_pressed: hex(0x463465), - bg_selected: hex(0x463465), - bg_input: hex(0x2A2139), - bg_progress: hex(0x241B2F), - text_primary: hex(0xFFFFFF), - text_secondary: hex(0xE2E2E2), - text_muted: hex(0xC0BCD0), - text_dim: hex(0xA09CB4), - text_dimmer: hex(0x848BBD), - text_dimmest: hex(0x614D85), - text_placeholder: hex(0x848BBD), - accent_blue: hex(0xFF7EDB), - accent_icon: hex(0x36F9F6), - accent_progress: hex(0xFF7EDB), - btn_default: hex(0x34294F), - btn_hover: hex(0x3E3460), - btn_pressed: hex(0x463465), - success: hex(0x72F1B8), - success_bg: hex(0x1A2828), - btn_success: hex(0x5CC898), - btn_success_hover: hex(0x50BC8C), - btn_success_pressed: hex(0x44B080), - warning: hex(0xFEDE5D), - warning_bg: hex(0x2A281A), - error: hex(0xFE4450), - error_light: hex(0xFF6670), - error_bg: hex(0x2A1A1E), - btn_danger_bg: hex(0x2A1A1E), - btn_danger_hover: hex(0x4A2A2E), - btn_trash_hover: hex(0xFE4450), - btn_trash_pressed: hex(0xDE3440), - bg_modal_section: hex(0x262335), - border_subtle: hex(0x34294F), - divider: hex(0x34294F), - }; - - // ── Modus Operandi (light, WCAG AAA) ── - pub const MODUS_OPERANDI: Self = Self { - bg_primary: hex(0xFFFFFF), - bg_sidebar: hex(0xF0F0F0), - bg_card: hex(0xE0E0E0), - bg_card_hover: hex(0xD0D0D0), - bg_card_pressed: hex(0xC4C4C4), - bg_selected: hex(0xD0D0D0), - bg_input: hex(0xE0E0E0), - bg_progress: hex(0xF0F0F0), - text_primary: hex(0x000000), - text_secondary: hex(0x1A1A1A), - text_muted: hex(0x333333), - text_dim: hex(0x595959), - text_dimmer: hex(0x7F7F7F), - text_dimmest: hex(0x9F9F9F), - text_placeholder: hex(0x7F7F7F), - accent_blue: hex(0x0031A9), - accent_icon: hex(0x005E8B), - accent_progress: hex(0x0031A9), - btn_default: hex(0xE0E0E0), - btn_hover: hex(0xD0D0D0), - btn_pressed: hex(0xC4C4C4), - success: hex(0x006800), - success_bg: hex(0xD0F0D0), - btn_success: hex(0x006800), - btn_success_hover: hex(0x005800), - btn_success_pressed: hex(0x004800), - warning: hex(0x6F5500), - warning_bg: hex(0xF0E8C8), - error: hex(0xA60000), - error_light: hex(0xD00000), - error_bg: hex(0xF0C8C8), - btn_danger_bg: hex(0xF0C8C8), - btn_danger_hover: hex(0xE0B0B0), - btn_trash_hover: hex(0xA60000), - btn_trash_pressed: hex(0x860000), - bg_modal_section: hex(0xFFFFFF), - border_subtle: hex(0xD0D0D0), - divider: hex(0xD0D0D0), - }; - - // ── Modus Vivendi (dark, WCAG AAA) ── - pub const MODUS_VIVENDI: Self = Self { - bg_primary: hex(0x000000), - bg_sidebar: hex(0x1E1E1E), - bg_card: hex(0x303030), - bg_card_hover: hex(0x404040), - bg_card_pressed: hex(0x535353), - bg_selected: hex(0x404040), - bg_input: hex(0x303030), - bg_progress: hex(0x1E1E1E), - text_primary: hex(0xFFFFFF), - text_secondary: hex(0xE0E0E0), - text_muted: hex(0xC0C0C0), - text_dim: hex(0x989898), - text_dimmer: hex(0x707070), - text_dimmest: hex(0x535353), - text_placeholder: hex(0x707070), - accent_blue: hex(0x2FAFFF), - accent_icon: hex(0x00D3D0), - accent_progress: hex(0x2FAFFF), - btn_default: hex(0x303030), - btn_hover: hex(0x404040), - btn_pressed: hex(0x535353), - success: hex(0x44BC44), - success_bg: hex(0x0A1A0A), - btn_success: hex(0x38A038), - btn_success_hover: hex(0x30942E), - btn_success_pressed: hex(0x288824), - warning: hex(0xD0BC00), - warning_bg: hex(0x1A1A00), - error: hex(0xFF5F59), - error_light: hex(0xFF7F86), - error_bg: hex(0x1A0808), - btn_danger_bg: hex(0x1A0808), - btn_danger_hover: hex(0x3A1818), - btn_trash_hover: hex(0xFF5F59), - btn_trash_pressed: hex(0xDF4F48), - bg_modal_section: hex(0x000000), - border_subtle: hex(0x303030), - divider: hex(0x303030), - }; - - // ── Stellar Blade (fan-made character set) ───────────────────────────── - // Duotone design language: one surface material + the character's signature - // color as the ink (text + accent). Derived from each character's on-screen - // design; Shift Up publishes no official codes. Same palettes as SphereCord's - // bundled sbThemes — keep the two in sync. - - /// EVE — her Planet Diving Suit: white/grey ceramic, green legs as the ink. Light. - pub const STELLAR_EVE: Self = Self { - bg_primary: hex(0xECEEEC), - bg_sidebar: hex(0xD3D7D3), - bg_card: hex(0xFFFFFF), - bg_card_hover: hex(0xE2E6E2), - bg_card_pressed: hex(0xD7DCD7), - bg_selected: hex(0xD9E4DA), - bg_input: hex(0xE0E4E0), - bg_progress: hex(0xDEE1DE), - text_primary: hex(0x1A6B3B), - text_secondary: hex(0x2B7D4A), - text_muted: hex(0x4E8A63), - text_dim: hex(0x6FA082), - text_dimmer: hex(0x84AD94), - text_dimmest: hex(0x9ABDA7), - text_placeholder: hex(0x84AD94), - accent_blue: hex(0x21A04D), - accent_icon: hex(0x21A04D), - accent_progress: hex(0x21A04D), - btn_default: hex(0xDFE3DF), - btn_hover: hex(0xD2D8D3), - btn_pressed: hex(0xC5CDC7), - success: hex(0x2F9E5C), - success_bg: hex(0xD3ECD9), - btn_success: hex(0x2F9E5C), - btn_success_hover: hex(0x288B50), - btn_success_pressed: hex(0x227A46), - warning: hex(0xC8861E), - warning_bg: hex(0xF0E6C8), - error: hex(0xD5493C), - error_light: hex(0xE05A4E), - error_bg: hex(0xF0CDC8), - btn_danger_bg: hex(0xF0CDC8), - btn_danger_hover: hex(0xE3B6B0), - btn_trash_hover: hex(0xC23A2E), - btn_trash_pressed: hex(0xA93227), - bg_modal_section: hex(0xE7EAE7), - border_subtle: hex(0xC0C8C1), - divider: hex(0xC0C8C1), - }; - - /// Tachy — carbon-navy suit, amber text, orange-glow accent; teal glints as success. - pub const STELLAR_TACHY: Self = Self { - bg_primary: hex(0x10121A), - bg_sidebar: hex(0x0A0B10), - bg_card: hex(0x171A24), - bg_card_hover: hex(0x1E2230), - bg_card_pressed: hex(0x252A3A), - bg_selected: hex(0x202536), - bg_input: hex(0x191C28), - bg_progress: hex(0x0D0E15), - text_primary: hex(0xF2BC4E), - text_secondary: hex(0xD9A133), - text_muted: hex(0xA58136), - text_dim: hex(0x75612F), - text_dimmer: hex(0x5C4D28), - text_dimmest: hex(0x453A20), - text_placeholder: hex(0x5C4D28), - accent_blue: hex(0xFF7C1E), - accent_icon: hex(0xEAA72C), - accent_progress: hex(0xFF7C1E), - btn_default: hex(0x171A24), - btn_hover: hex(0x1E2230), - btn_pressed: hex(0x252A3A), - success: hex(0x3FBFA8), - success_bg: hex(0x0E2420), - btn_success: hex(0x2F9D8A), - btn_success_hover: hex(0x298C7B), - btn_success_pressed: hex(0x237B6C), - warning: hex(0xEAA72C), - warning_bg: hex(0x2A2210), - error: hex(0xF0503C), - error_light: hex(0xFF6A55), - error_bg: hex(0x2A120E), - btn_danger_bg: hex(0x2A120E), - btn_danger_hover: hex(0x41201A), - btn_trash_hover: hex(0xB8402F), - btn_trash_pressed: hex(0x96331F), - bg_modal_section: hex(0x141720), - border_subtle: hex(0x2B3040), - divider: hex(0x2B3040), - }; - - /// Lily — strict duotone: amber-gold ink on neutral blacks. - pub const STELLAR_LILY: Self = Self { - bg_primary: hex(0x111010), - bg_sidebar: hex(0x0A0909), - bg_card: hex(0x191817), - bg_card_hover: hex(0x201E1C), - bg_card_pressed: hex(0x262421), - bg_selected: hex(0x232120), - bg_input: hex(0x1A1918), - bg_progress: hex(0x0D0C0C), - text_primary: hex(0xF6C832), - text_secondary: hex(0xD4A92C), - text_muted: hex(0xA5842A), - text_dim: hex(0x7A6526), - text_dimmer: hex(0x5F4F21), - text_dimmest: hex(0x483C1B), - text_placeholder: hex(0x5F4F21), - accent_blue: hex(0xFFD23E), - accent_icon: hex(0xFFD23E), - accent_progress: hex(0xFFD23E), - btn_default: hex(0x191817), - btn_hover: hex(0x201E1C), - btn_pressed: hex(0x262421), - success: hex(0x5FB55A), - success_bg: hex(0x15200F), - btn_success: hex(0x4C9D48), - btn_success_hover: hex(0x428C3F), - btn_success_pressed: hex(0x397B36), - warning: hex(0xE08A2A), - warning_bg: hex(0x261C0C), - error: hex(0xE0503C), - error_light: hex(0xF0654F), - error_bg: hex(0x26100C), - btn_danger_bg: hex(0x26100C), - btn_danger_hover: hex(0x3C1D16), - btn_trash_hover: hex(0xB8402F), - btn_trash_pressed: hex(0x96331F), - bg_modal_section: hex(0x151413), - border_subtle: hex(0x2E2B26), - divider: hex(0x2E2B26), - }; - - /// Enya — white ceramic armour, ice-blue sheen. Light. - pub const STELLAR_ENYA: Self = Self { - bg_primary: hex(0xEEF0F3), - bg_sidebar: hex(0xD5DAE1), - bg_card: hex(0xFFFFFF), - bg_card_hover: hex(0xE4E8EE), - bg_card_pressed: hex(0xD9DFE7), - bg_selected: hex(0xDCE3EC), - bg_input: hex(0xE2E6EC), - bg_progress: hex(0xDFE3EA), - text_primary: hex(0x171C23), - text_secondary: hex(0x3F4A58), - text_muted: hex(0x66727F), - text_dim: hex(0x8D97A3), - text_dimmer: hex(0xA3ADB8), - text_dimmest: hex(0xBAC2CC), - text_placeholder: hex(0xA3ADB8), - accent_blue: hex(0x3E90C9), - accent_icon: hex(0x3E90C9), - accent_progress: hex(0x3E90C9), - btn_default: hex(0xE1E5EB), - btn_hover: hex(0xD4DAE2), - btn_pressed: hex(0xC8CFD9), - success: hex(0x2F9E5C), - success_bg: hex(0xD3ECD9), - btn_success: hex(0x2F9E5C), - btn_success_hover: hex(0x288B50), - btn_success_pressed: hex(0x227A46), - warning: hex(0xC8861E), - warning_bg: hex(0xF0E6C8), - error: hex(0xD5493C), - error_light: hex(0xE05A4E), - error_bg: hex(0xF0CDC8), - btn_danger_bg: hex(0xF0CDC8), - btn_danger_hover: hex(0xE3B6B0), - btn_trash_hover: hex(0xC23A2E), - btn_trash_pressed: hex(0xA93227), - bg_modal_section: hex(0xE9ECF0), - border_subtle: hex(0xC2CAD4), - divider: hex(0xC2CAD4), - }; - - /// Kaya — lavender ink on deep violet; her khaki coat survives as the success state. - pub const STELLAR_KAYA: Self = Self { - bg_primary: hex(0x1A1622), - bg_sidebar: hex(0x14111B), - bg_card: hex(0x221D2E), - bg_card_hover: hex(0x292338), - bg_card_pressed: hex(0x302A42), - bg_selected: hex(0x2C2540), - bg_input: hex(0x241F31), - bg_progress: hex(0x171320), - text_primary: hex(0xD9C8F0), - text_secondary: hex(0xB49AE0), - text_muted: hex(0x8D7BAE), - text_dim: hex(0x6B5C88), - text_dimmer: hex(0x55486E), - text_dimmest: hex(0x423857), - text_placeholder: hex(0x55486E), - accent_blue: hex(0xCFA8FF), - accent_icon: hex(0xCFA8FF), - accent_progress: hex(0xCFA8FF), - btn_default: hex(0x221D2E), - btn_hover: hex(0x292338), - btn_pressed: hex(0x302A42), - success: hex(0x8A9455), - success_bg: hex(0x1D2012), - btn_success: hex(0x767F48), - btn_success_hover: hex(0x68703F), - btn_success_pressed: hex(0x5A6136), - warning: hex(0xD9A441), - warning_bg: hex(0x271F0E), - error: hex(0xE06A6A), - error_light: hex(0xEF8080), - error_bg: hex(0x291214), - btn_danger_bg: hex(0x291214), - btn_danger_hover: hex(0x3E1E21), - btn_trash_hover: hex(0xB84848), - btn_trash_pressed: hex(0x963A3A), - bg_modal_section: hex(0x1E1928), - border_subtle: hex(0x382F4C), - divider: hex(0x382F4C), - }; -} +//! Colony's theme — now shared with the rest of the ecosystem. +//! +//! The 38-field palette, all 57 theme palettes, the `(family, variant)` +//! resolver, the picker catalog, the accent overrides and the high-contrast +//! derivation live in `colony-ui`, generated from the design tokens in +//! Project-Colony-Resources. +//! +//! This module re-exports them so every existing `crate::ui::theme::…` call +//! site keeps working unchanged. +//! +//! **Adding a theme family no longer touches this repository.** Add the TOML +//! upstream, regenerate, and bump the `colony-ui` tag in `Cargo.toml`. + +pub use colony_ui::theme::*; diff --git a/src/update/mod.rs b/src/update/mod.rs index d5bf0b0..6e1ebbd 100644 --- a/src/update/mod.rs +++ b/src/update/mod.rs @@ -573,8 +573,14 @@ mod tests { let tmp = tempfile::tempdir().expect("tempdir"); let old_config = std::env::var_os("XDG_CONFIG_HOME"); let old_data = std::env::var_os("XDG_DATA_HOME"); + // XDG_CACHE_HOME matters since the caches moved out of the config + // directory: without it these tests read and write the developer's real + // ~/.cache, which makes them pass or fail depending on what a previous + // run left behind. + let old_cache = std::env::var_os("XDG_CACHE_HOME"); std::env::set_var("XDG_CONFIG_HOME", tmp.path().join("config")); std::env::set_var("XDG_DATA_HOME", tmp.path().join("data")); + std::env::set_var("XDG_CACHE_HOME", tmp.path().join("cache")); f(); match old_config { Some(v) => std::env::set_var("XDG_CONFIG_HOME", v), @@ -584,6 +590,10 @@ mod tests { Some(v) => std::env::set_var("XDG_DATA_HOME", v), None => std::env::remove_var("XDG_DATA_HOME"), } + match old_cache { + Some(v) => std::env::set_var("XDG_CACHE_HOME", v), + None => std::env::remove_var("XDG_CACHE_HOME"), + } } #[test] @@ -725,7 +735,7 @@ mod tests { assert!(matches!(app.github_state, GitHubState::Disconnected)); // Seed an orphaned doc cache for a repo that no longer exists. - let orphan = crate::persistence::colony_data_dir() + let orphan = crate::persistence::colony_cache_dir() .unwrap() .join("repo-docs") .join("Ghost");