max / makeover
32 files changed,
+444 insertions,
-204 deletions
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | [package] | |
| 2 | 2 | name = "makeover" | |
| 3 | - | version = "0.8.0" | |
| 3 | + | version = "0.9.0" | |
| 4 | 4 | edition = "2024" | |
| 5 | 5 | description = "Shared theme loading for the make-family apps: TOML theme files parsed into intent-based color tokens, with perceptual derivations and WCAG contrast." | |
| 6 | 6 | license = "MIT" | |
| @@ -9,6 +9,7 @@ repository = "https://git.sr.ht/~maxmj/makeover" | |||
| 9 | 9 | [dependencies] | |
| 10 | 10 | serde = { version = "1", features = ["derive"] } | |
| 11 | 11 | toml = "0.8" | |
| 12 | + | include_dir = "0.7" | |
| 12 | 13 | ||
| 13 | 14 | [dev-dependencies] | |
| 14 | 15 | tempfile = "3" |
| @@ -6,6 +6,8 @@ The crate ships the theme set it loads, in `themes/`. Consumers get working them | |||
| 6 | 6 | ||
| 7 | 7 | Used by MNW server, GoingsOn, Balanced Breakfast, audiofiles, and Alloy. | |
| 8 | 8 | ||
| 9 | + | The crate ships 30 themes. `bundled_themes_dir()` hands back the directory; `embedded_themes()` hands back the same set as `(id, toml_source)` pairs with no filesystem path involved, for consumers that embed at compile time or bundle assets at build time. | |
| 10 | + | ||
| 9 | 11 | ## Usage | |
| 10 | 12 | ||
| 11 | 13 | ```rust | |
| @@ -27,7 +29,7 @@ for t in &themes { | |||
| 27 | 29 | let theme = load_theme(&dirs, "catppuccin-mocha").unwrap(); | |
| 28 | 30 | println!("Name: {}", theme.meta.name); // "Catppuccin Mocha" | |
| 29 | 31 | println!("Variant: {}", theme.meta.variant); // "dark" | |
| 30 | - | println!("BG: {}", theme.colors["background.primary"]); // "#181825" | |
| 32 | + | println!("BG: {}", theme.colors["surface.page"]); // "#1e1e2e" | |
| 31 | 33 | ||
| 32 | 34 | // Build-from-source fallback: the themes this crate ships | |
| 33 | 35 | if let Some(dir) = bundled_themes_dir() { | |
| @@ -37,51 +39,48 @@ if let Some(dir) = bundled_themes_dir() { | |||
| 37 | 39 | ||
| 38 | 40 | ## Theme File Format | |
| 39 | 41 | ||
| 40 | - | Theme files are TOML with four color sections. See `themes/` for the 31 bundled themes. | |
| 42 | + | Colors are declared by **intent** (the role a color plays), not by hue. See `themes/` for the 30 bundled themes. | |
| 41 | 43 | ||
| 42 | 44 | ```toml | |
| 43 | - | # Attribution comment (optional, for credit) | |
| 44 | - | # Based on Catppuccin by Catppuccin Org -- MIT License | |
| 45 | - | ||
| 46 | 45 | [meta] | |
| 47 | - | name = "Theme Name" # Display name (required) | |
| 48 | - | variant = "dark" # "dark", "light", or "high-contrast" (default: "dark") | |
| 49 | - | ||
| 50 | - | [background] | |
| 51 | - | primary = "#181825" # Main background | |
| 52 | - | secondary = "#11111b" # Sidebar / panel background (optional) | |
| 53 | - | tertiary = "#313244" # Hover / selection background (optional) | |
| 54 | - | surface = "#1e1e2e" # Card / elevated surface (optional) | |
| 55 | - | ||
| 56 | - | [foreground] | |
| 57 | - | primary = "#cdd6f4" # Main text | |
| 58 | - | secondary = "#bac2de" # Secondary text (optional) | |
| 59 | - | muted = "#9399b2" # Placeholder / disabled text (optional) | |
| 60 | - | ||
| 61 | - | [accent] | |
| 62 | - | red = "#f38ba8" # Error, destructive actions | |
| 63 | - | green = "#a6e3a1" # Success, positive actions | |
| 64 | - | blue = "#89b4fa" # Links, primary accent | |
| 65 | - | yellow = "#f9e2af" # Warnings | |
| 66 | - | purple = "#cba6f7" # Tags, special elements | |
| 67 | - | cyan = "#89dceb" # Info, secondary accent | |
| 68 | - | ||
| 69 | - | [border] | |
| 70 | - | default = "#45475a" # Default border color | |
| 71 | - | ``` | |
| 46 | + | name = "Nord" # Display name; falls back to the filename | |
| 47 | + | variant = "dark" # "dark", "light", or "high-contrast" (default: "dark") | |
| 72 | 48 | ||
| 73 | - | ### Color Key Flattening | |
| 49 | + | [surface] # Container backgrounds by elevation | |
| 50 | + | page = "#2e3440" | |
| 51 | + | raised = "#3b4252" | |
| 52 | + | sunken = "#434c5e" | |
| 53 | + | overlay = "#3b4252" | |
| 74 | 54 | ||
| 75 | - | Colors are loaded into a flat `HashMap<String, String>` with dotted keys: | |
| 55 | + | [content] # Text and ink by emphasis | |
| 56 | + | primary = "#d8dee9" | |
| 57 | + | secondary = "#e5e9f0" | |
| 58 | + | muted = "#616e88" | |
| 76 | 59 | ||
| 60 | + | [action] # Interactive / brand color | |
| 61 | + | primary = "#81a1c1" | |
| 62 | + | ||
| 63 | + | [status] # State semantics | |
| 64 | + | danger = "#bf616a" | |
| 65 | + | success = "#a3be8c" | |
| 66 | + | warning = "#ebcb8b" | |
| 67 | + | info = "#88c0d0" | |
| 68 | + | ||
| 69 | + | [line] | |
| 70 | + | border = "#4c566a" | |
| 71 | + | ||
| 72 | + | [category] # Optional: tag and label colors | |
| 77 | 73 | ``` | |
| 78 | - | "background.primary" -> "#181825" | |
| 79 | - | "foreground.muted" -> "#9399b2" | |
| 80 | - | "accent.blue" -> "#89b4fa" | |
| 81 | - | "border.default" -> "#45475a" | |
| 82 | - | ``` | |
| 83 | 74 | ||
| 84 | - | Apps map these keys to CSS variables or egui color values. | |
| 75 | + | ### Derived tokens | |
| 76 | + | ||
| 77 | + | Interactive states are not authored. `resolve()` derives hover, active, | |
| 78 | + | selection, row striping, and contrast pairings perceptually in OKLab, so theme | |
| 79 | + | files stay small and every consuming app derives them identically rather than | |
| 80 | + | each recomputing its own. | |
| 81 | + | ||
| 82 | + | `intent_css_vars()` renders a resolved theme as a `:root { … }` block for web | |
| 83 | + | consumers; native consumers read RGB tuples off the same resolved tokens. | |
| 85 | 84 | ||
| 86 | 85 | ### Theme ID | |
| 87 | 86 | ||
| @@ -98,6 +97,10 @@ The theme ID is the filename without `.toml` (e.g., `catppuccin-mocha.toml` has | |||
| 98 | 97 | | `extract_colors(table)` | Flatten color sections into a `HashMap<String, String>` | | |
| 99 | 98 | | `validate_theme_id(id)` | Check that an ID contains only safe characters | | |
| 100 | 99 | | `bundled_themes_dir()` | The `themes/` directory this crate ships, for build-from-source fallback | | |
| 100 | + | | `embedded_themes()` | The shipped themes as `(id, toml_source)` pairs, embedded at compile time (no path needed) | | |
| 101 | + | | `parse_theme_str(id, source, is_custom)` | Parse a theme from a string, for use with `embedded_themes()` | | |
| 102 | + | | `resolve(theme)` | Resolve authored intents into the full token set, deriving interactive states | | |
| 103 | + | | `intent_css_vars(tokens)` | Render resolved tokens as a `:root { … }` CSS block | | |
| 101 | 104 | ||
| 102 | 105 | ## Directory Priority | |
| 103 | 106 | ||
| @@ -111,4 +114,6 @@ Typical setup for a Tauri app: | |||
| 111 | 114 | ||
| 112 | 115 | MIT. | |
| 113 | 116 | ||
| 114 | - | The themes in `themes/` are adapted from third-party color schemes (Catppuccin, Dracula, Nord, gruvbox, Tokyo Night, Rose Pine, and others), each MIT-licensed. Every adapted file carries an attribution comment naming its source and author; keep those in place when editing or redistributing. | |
| 117 | + | The themes in `themes/` adapt palettes from third-party projects (Catppuccin, Dracula, Nord, gruvbox, Tokyo Night, Rosé Pine, and others). Each upstream, its license, and the exact copyright line that license requires reproducing are recorded in [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md), verified against the upstream LICENSE files themselves. Every adapted theme file also carries that information in a header comment, so credit travels with the file. | |
| 118 | + | ||
| 119 | + | If an attribution is wrong or you would prefer your work not be included, write to info@makenot.work and it will be corrected or removed. |
| @@ -0,0 +1,96 @@ | |||
| 1 | + | # Third-party notices | |
| 2 | + | ||
| 3 | + | The `themes/` directory contains color palettes adapted from third-party open | |
| 4 | + | source projects. This file records, for each one, the upstream project, its | |
| 5 | + | license, and the exact copyright line that license requires be reproduced. | |
| 6 | + | ||
| 7 | + | Every upstream listed here was verified against its actual LICENSE file rather | |
| 8 | + | than against secondhand claims. Where verification turned up a gap, the gap is | |
| 9 | + | stated plainly instead of being papered over with a plausible-looking name. | |
| 10 | + | ||
| 11 | + | ## What "adapted" means | |
| 12 | + | ||
| 13 | + | None of these files are verbatim copies. Upstream projects publish palettes as | |
| 14 | + | editor themes, Vim colorschemes, or design tokens; none ship a file in | |
| 15 | + | makeover's intent schema. Each theme here re-expresses an upstream palette into | |
| 16 | + | that schema, mapping colors onto roles (`surface`, `content`, `action`, | |
| 17 | + | `status`, `line`, `category`) and deriving interactive states perceptually. The | |
| 18 | + | hex values originate upstream. The structure, role assignments, and derived | |
| 19 | + | tokens are this project's work. | |
| 20 | + | ||
| 21 | + | The adaptations are offered under this project's MIT license. The underlying | |
| 22 | + | palettes remain the work of their original authors, credited below. | |
| 23 | + | ||
| 24 | + | ## Adapted themes | |
| 25 | + | ||
| 26 | + | | Theme files | Upstream | License | Copyright | | |
| 27 | + | |---|---|---|---| | |
| 28 | + | | `akari-dawn`, `akari-night` | [Akari](https://github.com/cappyzawa/akari-theme) | MIT | Copyright (c) 2025 Shu Kutsuzawa | | |
| 29 | + | | `ayu-mirage` | [Ayu](https://github.com/dempfi/ayu) | MIT | Copyright (c) 2016 Ike Kurghinyani | | |
| 30 | + | | `ayu-light` | [ayu-colors](https://github.com/ayu-theme/ayu-colors) | MIT | Copyright (c) Konstantin Pschera \<me@kons.ch\> | | |
| 31 | + | | `carbonfox`, `dawnfox`, `nightfox` | [nightfox.nvim](https://github.com/EdenEast/nightfox.nvim) | MIT | Copyright (c) 2021 James Simpson | | |
| 32 | + | | `catppuccin-frappe`, `catppuccin-latte`, `catppuccin-macchiato`, `catppuccin-mocha` | [Catppuccin](https://github.com/catppuccin/catppuccin) | MIT | Copyright (c) 2021 Catppuccin | | |
| 33 | + | | `dracula` | [Dracula](https://github.com/dracula/dracula-theme) | MIT | Copyright (c) 2023 Dracula Theme | | |
| 34 | + | | `everforest` | [Everforest](https://github.com/sainnhe/everforest) | MIT | Copyright (c) 2019 sainnhe | | |
| 35 | + | | `flatwhite` | [flatwhite-syntax](https://github.com/biletskyy/flatwhite-syntax) | MIT (see note) | no holder named upstream | | |
| 36 | + | | `gruvbox-dark`, `gruvbox-light` | [gruvbox](https://github.com/morhetz/gruvbox) | MIT/X11 asserted (see note) | no holder named upstream | | |
| 37 | + | | `kanagawa` | [kanagawa.nvim](https://github.com/rebelot/kanagawa.nvim) | MIT | Copyright (c) 2021 Tommaso Laurenzi | | |
| 38 | + | | `nord` | [Nord](https://github.com/nordtheme/nord) | MIT | Copyright (c) 2016-present Sven Greb \<development@svengreb.de\> | | |
| 39 | + | | `one-dark` | [Atom One Dark](https://github.com/atom/one-dark-syntax) | MIT | Copyright (c) 2016 GitHub Inc. | | |
| 40 | + | | `oxocarbon-dark`, `oxocarbon-light` | [oxocarbon.nvim](https://github.com/nyoom-engineering/oxocarbon.nvim) | MIT | Copyright (c) 2022 Riccardo Mazzarini | | |
| 41 | + | | `poimandres` | [poimandres-theme](https://github.com/drcmda/poimandres-theme) | MIT | Copyright (c) 2023 drcmda | | |
| 42 | + | | `rosepine`, `rosepine-dawn` | [Rosé Pine](https://github.com/rose-pine/neovim) | MIT | Copyright (c) 2023 Rosé Pine | | |
| 43 | + | | `solarized-dark` | [Solarized](https://github.com/altercation/solarized) | MIT | Copyright (c) 2011 Ethan Schoonover | | |
| 44 | + | | `tokyonight` | [Tokyo Night](https://github.com/tokyo-night/tokyo-night-vscode-theme) | MIT | Copyright (c) 2018-present Enkia | | |
| 45 | + | ||
| 46 | + | Several of these credit a person where the project is better known by a GitHub | |
| 47 | + | handle, because the license names the person: nightfox.nvim is James Simpson | |
| 48 | + | (handle EdenEast), oxocarbon.nvim is Riccardo Mazzarini (hosted under the | |
| 49 | + | nyoom-engineering org), and Atom One Dark is GitHub Inc. | |
| 50 | + | ||
| 51 | + | ## Notes on incomplete upstream licensing | |
| 52 | + | ||
| 53 | + | Two upstreams assert permissive terms but provide no copyright holder, so there | |
| 54 | + | is no line to reproduce. Both are long-established and widely redistributed as | |
| 55 | + | MIT. They are included on that basis, with the gap recorded rather than filled | |
| 56 | + | in by guesswork. | |
| 57 | + | ||
| 58 | + | **gruvbox** ships no LICENSE file. Its README states only "MIT/X11", linking to | |
| 59 | + | a Wikipedia article rather than to license text, and names no holder. Pavel | |
| 60 | + | Pertsev (morhetz) is the repository owner and apparent author, but that name | |
| 61 | + | appears in no license document. | |
| 62 | + | ||
| 63 | + | **flatwhite-syntax** ships an MIT license whose holder field was never filled | |
| 64 | + | in; it reads literally `<Your name here>`. biletskyy is the repository owner and | |
| 65 | + | apparent author. | |
| 66 | + | ||
| 67 | + | If you maintain either project, we would gladly correct this entry: adding or | |
| 68 | + | completing a LICENSE file upstream is all it takes. | |
| 69 | + | ||
| 70 | + | ## Removed | |
| 71 | + | ||
| 72 | + | **Material Palenight** was previously bundled and has been removed. It was | |
| 73 | + | credited to Mattia Astorino under MIT; both fields were wrong. The original | |
| 74 | + | Material Theme was retroactively relicensed to proprietary terms in February | |
| 75 | + | 2025 with its commit history rewritten, and its repository path now redirects to | |
| 76 | + | a commercial product. A genuinely MIT-licensed unofficial port exists | |
| 77 | + | ([whizkydee/vscode-palenight-theme](https://github.com/whizkydee/vscode-palenight-theme), | |
| 78 | + | Copyright (c) 2017-present Olaolu Olawuyi), but it derives from that same | |
| 79 | + | palette, so the chain of permission cannot be audited. The theme was dropped | |
| 80 | + | rather than shipped on an unverifiable basis. | |
| 81 | + | ||
| 82 | + | ## Original themes | |
| 83 | + | ||
| 84 | + | `akari-dawn` and `akari-night` are adaptations, listed above. The following are | |
| 85 | + | this project's own work and carry no third-party claim: | |
| 86 | + | ||
| 87 | + | - `makenotwork` — the makenot.work platform palette | |
| 88 | + | - `goingson` — the GoingsOn application palette | |
| 89 | + | - `audiofiles` — the audiofiles application palette | |
| 90 | + | - `high-contrast` — an accessibility-oriented palette | |
| 91 | + | ||
| 92 | + | ## Corrections | |
| 93 | + | ||
| 94 | + | If any attribution here is wrong, incomplete, or you would prefer your work not | |
| 95 | + | be included, open a ticket or write to info@makenot.work and it will be | |
| 96 | + | corrected or removed. |
| @@ -591,6 +591,36 @@ pub fn export_theme(dirs: &[(PathBuf, bool)], id: &str, dest_path: &Path) -> Res | |||
| 591 | 591 | Ok(()) | |
| 592 | 592 | } | |
| 593 | 593 | ||
| 594 | + | /// The themes this crate ships, embedded at compile time. | |
| 595 | + | /// | |
| 596 | + | /// `include_dir` is an implementation detail: the public API hands back plain | |
| 597 | + | /// `(id, toml_source)` pairs, so how the data is embedded can change without | |
| 598 | + | /// a breaking release. | |
| 599 | + | static EMBEDDED: include_dir::Dir<'static> = | |
| 600 | + | include_dir::include_dir!("$CARGO_MANIFEST_DIR/themes"); | |
| 601 | + | ||
| 602 | + | /// The themes this crate ships, as `(id, toml_source)` pairs. | |
| 603 | + | /// | |
| 604 | + | /// This is the path-free way to reach the bundled set, for consumers that | |
| 605 | + | /// cannot rely on a directory existing at runtime: a crate pulled from | |
| 606 | + | /// crates.io lives in a registry checkout whose location is not knowable at | |
| 607 | + | /// compile time, so `include_dir!` and asset-bundling globs in the depending | |
| 608 | + | /// crate have nothing stable to point at. Embedding here and re-exporting the | |
| 609 | + | /// contents gives them one source of truth without a path. | |
| 610 | + | /// | |
| 611 | + | /// Ordering follows the embedded directory and is not guaranteed; collect and | |
| 612 | + | /// sort by id where a stable order matters (a theme picker, say). | |
| 613 | + | pub fn embedded_themes() -> impl Iterator<Item = (&'static str, &'static str)> { | |
| 614 | + | EMBEDDED.files().filter_map(|file| { | |
| 615 | + | let path = file.path(); | |
| 616 | + | if path.extension().and_then(|e| e.to_str()) != Some("toml") { | |
| 617 | + | return None; | |
| 618 | + | } | |
| 619 | + | let id = path.file_stem()?.to_str()?; | |
| 620 | + | Some((id, file.contents_utf8()?)) | |
| 621 | + | }) | |
| 622 | + | } | |
| 623 | + | ||
| 594 | 624 | /// The theme directory this crate ships, for use as a build-from-source | |
| 595 | 625 | /// fallback. | |
| 596 | 626 | /// | |
| @@ -980,6 +1010,78 @@ six = "#88c0d0" | |||
| 980 | 1010 | } | |
| 981 | 1011 | ||
| 982 | 1012 | #[test] | |
| 1013 | + | fn every_theme_is_accounted_for_in_third_party_notices() { | |
| 1014 | + | // Attribution is a redistribution obligation, not a nicety: adding a | |
| 1015 | + | // theme without a notice entry silently ships someone's work | |
| 1016 | + | // uncredited. Fail here instead. | |
| 1017 | + | let notices = std::fs::read_to_string( | |
| 1018 | + | Path::new(env!("CARGO_MANIFEST_DIR")).join("THIRD-PARTY-NOTICES.md"), | |
| 1019 | + | ) | |
| 1020 | + | .expect("THIRD-PARTY-NOTICES.md must exist"); | |
| 1021 | + | let missing: Vec<&str> = embedded_themes() | |
| 1022 | + | .map(|(id, _)| id) | |
| 1023 | + | .filter(|id| !notices.contains(*id)) | |
| 1024 | + | .collect(); | |
| 1025 | + | assert!( | |
| 1026 | + | missing.is_empty(), | |
| 1027 | + | "themes missing from THIRD-PARTY-NOTICES.md: {missing:?}" | |
| 1028 | + | ); | |
| 1029 | + | } | |
| 1030 | + | ||
| 1031 | + | #[test] | |
| 1032 | + | fn adapted_themes_carry_inline_attribution() { | |
| 1033 | + | // Each adapted file must name its upstream in-file, so the credit | |
| 1034 | + | // survives someone copying a single .toml out of the crate. | |
| 1035 | + | const ORIGINALS: [&str; 4] = ["makenotwork", "goingson", "audiofiles", "high-contrast"]; | |
| 1036 | + | for (id, source) in embedded_themes() { | |
| 1037 | + | if ORIGINALS.contains(&id) { | |
| 1038 | + | continue; | |
| 1039 | + | } | |
| 1040 | + | assert!( | |
| 1041 | + | source.contains("adapted from"), | |
| 1042 | + | "adapted theme `{id}` is missing its inline attribution header" | |
| 1043 | + | ); | |
| 1044 | + | } | |
| 1045 | + | } | |
| 1046 | + | ||
| 1047 | + | #[test] | |
| 1048 | + | fn embedded_themes_match_the_directory() { | |
| 1049 | + | // The embedded copy and themes/ are two views of one source. If they | |
| 1050 | + | // ever disagree, path-based and path-free consumers render different | |
| 1051 | + | // theme sets, which is exactly the drift shipping the data was meant | |
| 1052 | + | // to prevent. | |
| 1053 | + | let dir = bundled_themes_dir().unwrap(); | |
| 1054 | + | let mut on_disk: Vec<String> = std::fs::read_dir(&dir) | |
| 1055 | + | .unwrap() | |
| 1056 | + | .filter_map(|e| { | |
| 1057 | + | let path = e.ok()?.path(); | |
| 1058 | + | if path.extension()? != "toml" { | |
| 1059 | + | return None; | |
| 1060 | + | } | |
| 1061 | + | Some(path.file_stem()?.to_str()?.to_string()) | |
| 1062 | + | }) | |
| 1063 | + | .collect(); | |
| 1064 | + | let mut embedded: Vec<String> = | |
| 1065 | + | embedded_themes().map(|(id, _)| id.to_string()).collect(); | |
| 1066 | + | on_disk.sort(); | |
| 1067 | + | embedded.sort(); | |
| 1068 | + | assert_eq!(embedded, on_disk, "embedded theme set drifted from themes/"); | |
| 1069 | + | } | |
| 1070 | + | ||
| 1071 | + | #[test] | |
| 1072 | + | fn every_embedded_theme_parses() { | |
| 1073 | + | // Guards the path-free consumers (MNW server, the Tauri build steps) | |
| 1074 | + | // the same way every_shipped_theme_loads guards the path-based ones. | |
| 1075 | + | let mut count = 0; | |
| 1076 | + | for (id, source) in embedded_themes() { | |
| 1077 | + | parse_theme_str(id, source, false) | |
| 1078 | + | .unwrap_or_else(|e| panic!("embedded theme `{id}` failed to parse: {e}")); | |
| 1079 | + | count += 1; | |
| 1080 | + | } | |
| 1081 | + | assert!(count >= 30, "expected the full theme set, got {count}"); | |
| 1082 | + | } | |
| 1083 | + | ||
| 1084 | + | #[test] | |
| 983 | 1085 | fn every_shipped_theme_loads() { | |
| 984 | 1086 | // Guards the data, not just the loader: a malformed or truncated | |
| 985 | 1087 | // .toml in themes/ is a shipping bug, and it should fail here rather |
| @@ -1,94 +1,74 @@ | |||
| 1 | 1 | # Themes | |
| 2 | 2 | ||
| 3 | - | 16 TOML color themes shared across GoingsOn, Balanced Breakfast, and audiofiles. | |
| 4 | - | ||
| 5 | - | ## Bundled Themes | |
| 6 | - | ||
| 7 | - | | File | Name | Variant | Source | | |
| 8 | - | |------|------|---------|--------| | |
| 9 | - | | `ayu-light.toml` | Ayu Light | light | [ayu-theme](https://github.com/ayu-theme) | | |
| 10 | - | | `catppuccin-latte.toml` | Catppuccin Latte | light | [Catppuccin](https://github.com/catppuccin/catppuccin) | | |
| 11 | - | | `catppuccin-mocha.toml` | Catppuccin Mocha | dark | [Catppuccin](https://github.com/catppuccin/catppuccin) | | |
| 12 | - | | `dracula.toml` | Dracula | dark | [Dracula](https://github.com/dracula/dracula-theme) | | |
| 13 | - | | `everforest.toml` | Everforest | dark | [Everforest](https://github.com/sainnhe/everforest) | | |
| 14 | - | | `flatwhite.toml` | Flatwhite | light | [Flatwhite](https://github.com/biletskyy/flatwhite-syntax) | | |
| 15 | - | | `gruvbox-dark.toml` | Gruvbox Dark | dark | [Gruvbox](https://github.com/morhetz/gruvbox) | | |
| 16 | - | | `gruvbox-light.toml` | Gruvbox Light | light | [Gruvbox](https://github.com/morhetz/gruvbox) | | |
| 17 | - | | `high-contrast.toml` | High Contrast | high-contrast | Original | | |
| 18 | - | | `kanagawa.toml` | Kanagawa | dark | [Kanagawa](https://github.com/rebelot/kanagawa.nvim) | | |
| 19 | - | | `neobrute.toml` | Neobrute | dark | Original | | |
| 20 | - | | `nord.toml` | Nord | dark | [Nord](https://github.com/nordtheme/nord) | | |
| 21 | - | | `rosepine-dawn.toml` | Rose Pine Dawn | light | [Rose Pine](https://github.com/rose-pine/rose-pine-theme) | | |
| 22 | - | | `rosepine.toml` | Rose Pine | dark | [Rose Pine](https://github.com/rose-pine/rose-pine-theme) | | |
| 23 | - | | `solarized-dark.toml` | Solarized Dark | dark | [Solarized](https://github.com/altercation/solarized) | | |
| 24 | - | | `tokyonight.toml` | Tokyo Night | dark | [Tokyo Night](https://github.com/folke/tokyonight.nvim) | | |
| 25 | - | ||
| 26 | - | ## File Format | |
| 27 | - | ||
| 28 | - | Each theme is a TOML file with four color sections: | |
| 3 | + | The 30 theme files this crate ships. Consumers get them two ways: as a | |
| 4 | + | directory via `bundled_themes_dir()`, or embedded with no path at all via | |
| 5 | + | `embedded_themes()`. | |
| 29 | 6 | ||
| 30 | - | ```toml | |
| 31 | - | # Attribution (optional) | |
| 32 | - | # Based on Catppuccin by Catppuccin Org -- MIT License | |
| 7 | + | Attribution for the adapted palettes lives in | |
| 8 | + | [THIRD-PARTY-NOTICES.md](../THIRD-PARTY-NOTICES.md). Each adapted file also | |
| 9 | + | carries its upstream, license, and copyright line in a header comment, so the | |
| 10 | + | credit travels with the file if someone copies one out on its own. | |
| 11 | + | ||
| 12 | + | ## File format | |
| 13 | + | ||
| 14 | + | A theme declares colors by intent, not by hue. Sections: | |
| 33 | 15 | ||
| 16 | + | ```toml | |
| 34 | 17 | [meta] | |
| 35 | - | name = "Theme Name" # Display name (required) | |
| 36 | - | variant = "dark" # "dark", "light", or "high-contrast" | |
| 37 | - | ||
| 38 | - | [background] | |
| 39 | - | primary = "#181825" # Main background | |
| 40 | - | secondary = "#11111b" # Sidebar / panel background | |
| 41 | - | tertiary = "#313244" # Hover / selection background | |
| 42 | - | surface = "#1e1e2e" # Card / elevated surface | |
| 43 | - | ||
| 44 | - | [foreground] | |
| 45 | - | primary = "#cdd6f4" # Main text | |
| 46 | - | secondary = "#bac2de" # Secondary text | |
| 47 | - | muted = "#9399b2" # Placeholder / disabled text | |
| 48 | - | ||
| 49 | - | [accent] | |
| 50 | - | red = "#f38ba8" # Error, destructive actions | |
| 51 | - | green = "#a6e3a1" # Success, positive actions | |
| 52 | - | blue = "#89b4fa" # Links, primary accent | |
| 53 | - | yellow = "#f9e2af" # Warnings | |
| 54 | - | purple = "#cba6f7" # Tags, special elements | |
| 55 | - | cyan = "#89dceb" # Info, secondary accent | |
| 56 | - | ||
| 57 | - | [border] | |
| 58 | - | default = "#45475a" # Default border color | |
| 59 | - | ``` | |
| 18 | + | name = "Nord" # display name; falls back to the filename | |
| 19 | + | variant = "dark" # "dark", "light", or "high-contrast" | |
| 60 | 20 | ||
| 61 | - | ### Required Fields | |
| 21 | + | [surface] # container backgrounds by elevation | |
| 22 | + | page = "#2e3440" | |
| 23 | + | raised = "#3b4252" | |
| 24 | + | sunken = "#434c5e" | |
| 25 | + | overlay = "#3b4252" | |
| 62 | 26 | ||
| 63 | - | - `[meta].name` -- falls back to the filename if missing | |
| 64 | - | - `[meta].variant` -- falls back to `"dark"` if missing | |
| 65 | - | - At least `[background].primary` and `[foreground].primary` for a usable theme | |
| 27 | + | [content] # text and ink by emphasis | |
| 28 | + | primary = "#d8dee9" | |
| 29 | + | secondary = "#e5e9f0" | |
| 30 | + | muted = "#616e88" | |
| 66 | 31 | ||
| 67 | - | All other fields are optional. Apps should fall back gracefully when a color key is missing. | |
| 32 | + | [action] # interactive / brand color | |
| 33 | + | primary = "#81a1c1" | |
| 68 | 34 | ||
| 69 | - | ### Theme ID | |
| 35 | + | [status] # state semantics | |
| 36 | + | danger = "#bf616a" | |
| 37 | + | success = "#a3be8c" | |
| 38 | + | warning = "#ebcb8b" | |
| 39 | + | info = "#88c0d0" | |
| 40 | + | ||
| 41 | + | [line] | |
| 42 | + | border = "#4c566a" | |
| 70 | 43 | ||
| 71 | - | The filename without `.toml` is the theme ID. IDs must contain only alphanumeric characters, hyphens, and underscores. Examples: `catppuccin-mocha`, `gruvbox-light`, `high-contrast`. | |
| 44 | + | [category] # optional; tag and label colors | |
| 45 | + | ``` | |
| 72 | 46 | ||
| 73 | - | ## How Apps Load Themes | |
| 47 | + | Interactive states (hover, active, selection, row striping, contrast pairings) | |
| 48 | + | are not authored. `resolve()` derives them perceptually in OKLab, so a theme | |
| 49 | + | file stays small and every consuming app derives them identically. | |
| 74 | 50 | ||
| 75 | - | | App | Method | Crate | | |
| 76 | - | |-----|--------|-------| | |
| 77 | - | | GoingsOn | Runtime from disk via Tauri resource glob | `theme-common` | | |
| 78 | - | | Balanced Breakfast | Runtime from disk via Tauri resource glob | `theme-common` | | |
| 79 | - | | audiofiles | Compile-time embed via `include_str!` | Direct TOML parsing | | |
| 51 | + | ### Theme ID | |
| 80 | 52 | ||
| 81 | - | GoingsOn and Balanced Breakfast include this directory as a Tauri resource (`../../../MNW/shared/themes/*.toml` in `tauri.conf.json`). The `theme-common` crate handles parsing and color extraction at runtime. | |
| 53 | + | The filename without `.toml`. IDs may contain only alphanumeric characters, | |
| 54 | + | hyphens, and underscores; path traversal characters are rejected. | |
| 82 | 55 | ||
| 83 | - | audiofiles embeds theme files at compile time and parses TOML directly without the `theme-common` crate. | |
| 56 | + | ## Adding a theme | |
| 84 | 57 | ||
| 85 | - | ## Adding a New Theme | |
| 58 | + | 1. Create `your-theme.toml` here. | |
| 59 | + | 2. If it adapts an existing palette, add the header comment naming the upstream | |
| 60 | + | project, its license, and the exact copyright line from its LICENSE file. | |
| 61 | + | Verify against the actual LICENSE, not a README or a secondhand claim. | |
| 62 | + | 3. Add a row to [THIRD-PARTY-NOTICES.md](../THIRD-PARTY-NOTICES.md). | |
| 63 | + | 4. Run `cargo test`. The suite fails if a theme is missing from the notices | |
| 64 | + | file, if an adapted theme has no inline attribution, or if the embedded set | |
| 65 | + | drifts from this directory. | |
| 86 | 66 | ||
| 87 | - | 1. Create `your-theme-name.toml` in this directory | |
| 88 | - | 2. Add an attribution comment at the top if based on an existing theme | |
| 89 | - | 3. Fill in all sections (`meta`, `background`, `foreground`, `accent`, `border`) | |
| 90 | - | 4. The theme will be available automatically on next build (GO/BB pick it up via the resource glob, AF picks it up via compile-time include) | |
| 67 | + | Original themes (no third-party palette) skip steps 2 and 3, but must be listed | |
| 68 | + | under "Original themes" in the notices file. | |
| 91 | 69 | ||
| 92 | 70 | ## License | |
| 93 | 71 | ||
| 94 | - | Individual theme color palettes are attributed to their original creators. The TOML files themselves are part of this project under PolyForm Noncommercial 1.0.0. | |
| 72 | + | The theme files are MIT, as is the rest of the crate. The underlying palettes | |
| 73 | + | of adapted themes remain their original authors' work; see | |
| 74 | + | [THIRD-PARTY-NOTICES.md](../THIRD-PARTY-NOTICES.md). |
| @@ -1,20 +1,9 @@ | |||
| 1 | - | # Based on Akari by Shu Kutsuzawa (cappyzawa) — MIT License | |
| 1 | + | # Colors adapted from Akari, used under the MIT License. | |
| 2 | 2 | # https://github.com/cappyzawa/akari-theme | |
| 3 | + | # Copyright (c) 2025 Shu Kutsuzawa | |
| 3 | 4 | # | |
| 4 | - | # Akari Dawn — inspired by Japanese alleys lit by round lanterns. | |
| 5 | - | # Design principles from the source: light is singular (lantern-orange | |
| 6 | - | # is the only primary accent), blue is air not light (night sky), | |
| 7 | - | # purple stays muted, green is life, black is warm grey. Alloy adopts | |
| 8 | - | # this as its default light theme. | |
| 9 | - | # | |
| 10 | - | # Note on surface tiers: theme-common's convention is that `raised` | |
| 11 | - | # reads as elevated (lighter in light mode) and `sunken` as recessed | |
| 12 | - | # (darker). Akari's own Helix theme uses different semantics (a darker | |
| 13 | - | # "surface" for statusline / cursorline). The tiers below reinterpret | |
| 14 | - | # Akari's palette to theme-common's L-ordering while staying inside | |
| 15 | - | # the source's tonal range — page and sunken take Akari's own values; | |
| 16 | - | # raised and overlay are computed warmer/lighter tones that read as | |
| 17 | - | # "paper caught in more lamp light." | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 18 | 7 | ||
| 19 | 8 | [meta] | |
| 20 | 9 | name = "Akari Dawn" |
| @@ -1,10 +1,9 @@ | |||
| 1 | - | # Based on Akari by Shu Kutsuzawa (cappyzawa) — MIT License | |
| 1 | + | # Colors adapted from Akari, used under the MIT License. | |
| 2 | 2 | # https://github.com/cappyzawa/akari-theme | |
| 3 | + | # Copyright (c) 2025 Shu Kutsuzawa | |
| 3 | 4 | # | |
| 4 | - | # Akari Night — dark counterpart to Akari Dawn. Same design principles: | |
| 5 | - | # lantern-orange is the only primary light source, blue is night sky | |
| 6 | - | # (not accent light), green is life, purple stays quiet. Alloy adopts | |
| 7 | - | # this as its default dark theme. | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 8 | 7 | ||
| 9 | 8 | [meta] | |
| 10 | 9 | name = "Akari Night" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Ayu by Konstantin Pschera — MIT License | |
| 2 | - | # https://github.com/ayu-theme | |
| 1 | + | # Colors adapted from Ayu, used under the MIT License. | |
| 2 | + | # https://github.com/ayu-theme/ayu-colors | |
| 3 | + | # Copyright (c) Konstantin Pschera <me@kons.ch> | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Ayu Light" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Ayu by dempfi — MIT License | |
| 2 | - | # https://github.com/ayu-theme/ayu-colors | |
| 1 | + | # Colors adapted from Ayu, used under the MIT License. | |
| 2 | + | # https://github.com/dempfi/ayu | |
| 3 | + | # Copyright (c) 2016 Ike Kurghinyani | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Ayu Mirage" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Carbonfox by EdenEast — MIT License | |
| 1 | + | # Colors adapted from Nightfox, used under the MIT License. | |
| 2 | 2 | # https://github.com/EdenEast/nightfox.nvim | |
| 3 | + | # Copyright (c) 2021 James Simpson | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Carbonfox" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Catppuccin by Catppuccin Org — MIT License | |
| 1 | + | # Colors adapted from Catppuccin, used under the MIT License. | |
| 2 | 2 | # https://github.com/catppuccin/catppuccin | |
| 3 | + | # Copyright (c) 2021 Catppuccin | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Catppuccin Frappé" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Catppuccin by Catppuccin Org — MIT License | |
| 1 | + | # Colors adapted from Catppuccin, used under the MIT License. | |
| 2 | 2 | # https://github.com/catppuccin/catppuccin | |
| 3 | + | # Copyright (c) 2021 Catppuccin | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Catppuccin Latte" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Catppuccin by Catppuccin Org — MIT License | |
| 1 | + | # Colors adapted from Catppuccin, used under the MIT License. | |
| 2 | 2 | # https://github.com/catppuccin/catppuccin | |
| 3 | + | # Copyright (c) 2021 Catppuccin | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Catppuccin Macchiato" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Catppuccin by Catppuccin Org — MIT License | |
| 1 | + | # Colors adapted from Catppuccin, used under the MIT License. | |
| 2 | 2 | # https://github.com/catppuccin/catppuccin | |
| 3 | + | # Copyright (c) 2021 Catppuccin | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Catppuccin Mocha" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Dawnfox by EdenEast — MIT License | |
| 1 | + | # Colors adapted from Nightfox, used under the MIT License. | |
| 2 | 2 | # https://github.com/EdenEast/nightfox.nvim | |
| 3 | + | # Copyright (c) 2021 James Simpson | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Dawnfox" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Dracula by Dracula Theme — MIT License | |
| 1 | + | # Colors adapted from Dracula, used under the MIT License. | |
| 2 | 2 | # https://github.com/dracula/dracula-theme | |
| 3 | + | # Copyright (c) 2023 Dracula Theme | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Dracula" |
| @@ -1,5 +1,9 @@ | |||
| 1 | - | # Based on Everforest by sainnhe — MIT License | |
| 1 | + | # Colors adapted from Everforest, used under the MIT License. | |
| 2 | 2 | # https://github.com/sainnhe/everforest | |
| 3 | + | # Copyright (c) 2019 sainnhe | |
| 4 | + | # | |
| 5 | + | # Adaptation only: the palette is re-expressed into makeover's intent | |
| 6 | + | # schema. Upstream ships no file in this format. See THIRD-PARTY-NOTICES.md. | |
| 3 | 7 | ||
| 4 | 8 | [meta] | |
| 5 | 9 | name = "Everforest" |
| @@ -1,5 +1,12 @@ | |||
| 1 | - | # Based on Flatwhite by biletskyy — MIT License | |
| 1 | + | # Colors adapted from Flatwhite, used under the MIT License. | |
| 2 | 2 | # https://github.com/biletskyy/flatwhite-syntax | |
| 3 | + | # | |
| 4 | + | # Licensing note: upstream's MIT license text is an unfilled template whose | |
| 5 | + | # copyright holder reads "<Your name here>", so there is no named holder to | |
| 6 | + | # reproduce. Apparent author: biletskyy (repository owner). See | |
| 7 | + | # THIRD-PARTY-NOTICES.md. | |
| 8 | + | # | |
| 9 | + | # Adaptation only: the palette is re-expressed into makeover's intent schema. | |
| 3 | 10 | ||
| 4 | 11 | [meta] | |
| 5 | 12 | name = "Flatwhite" |
| @@ -1,5 +1,12 @@ | |||
| 1 | - | # Based on Gruvbox by Pavel Pertsev (morhetz) — MIT License | |
| 1 | + | # Colors adapted from Gruvbox. | |
| 2 | 2 | # https://github.com/morhetz/gruvbox | |
| 3 | + | # | |
| 4 | + | # Licensing note: upstream ships NO LICENSE file. Its README states only | |
| 5 | + | # "MIT/X11", with no copyright holder given, so there is no upstream copyright | |
| 6 | + | # line to reproduce here. Apparent author: Pavel Pertsev (morhetz). See | |
| 7 | + | # THIRD-PARTY-NOTICES.md. | |
| 8 | + | # | |
| 9 | + | # Adaptation only: the palette is re-expressed into makeover's intent schema. | |
| 3 | 10 | ||
| 4 | 11 | [meta] | |
| 5 | 12 | name = "Gruvbox Dark" |
| @@ -1,5 +1,12 @@ | |||
| 1 | - | # Based on Gruvbox by Pavel Pertsev (morhetz) — MIT License | |
| 1 | + | # Colors adapted from Gruvbox. | |
| 2 | 2 | # https://github.com/morhetz/gruvbox | |
| 3 | + | # | |
| 4 | + | # Licensing note: upstream ships NO LICENSE file. Its README states only | |
| 5 | + | # "MIT/X11", with no copyright holder given, so there is no upstream copyright | |
| 6 | + | # line to reproduce here. Apparent author: Pavel Pertsev (morhetz). See | |
| 7 | + | # THIRD-PARTY-NOTICES.md. | |
| 8 | + | # | |
| 9 | + | # Adaptation only: the palette is re-expressed into makeover's intent schema. | |
| 3 | 10 | ||
| 4 | 11 | [meta] | |
| 5 | 12 | name = "Gruvbox Light" |