Skip to main content

max / makeover

0.9.0: embed themes, correct third-party attribution Add embedded_themes(), the shipped set as (id, toml_source) pairs with no path involved. Consumers that embed at compile time or bundle assets at build time cannot point at a registry checkout, whose location is not knowable when they are built; this gives them one source of truth. include_dir stays an implementation detail so the embedding can change without a breaking release. Correct the attribution on every adapted theme after verifying each upstream against its actual LICENSE file. Several inline claims were wrong: nightfox is James Simpson (not the EdenEast handle), oxocarbon is Riccardo Mazzarini (not the nyoom-engineering org), one-dark is GitHub Inc., rosepine is the Rose Pine org (not mvllow), and nord's line carries a date range and address. Each file now reproduces its upstream copyright line verbatim, as MIT requires. Drop palenight. It was credited to Mattia Astorino under MIT and both fields were wrong: the original Material Theme was retroactively relicensed to proprietary in 2025 with its history rewritten, so the chain of permission cannot be audited. Record gruvbox and flatwhite honestly rather than inventing a holder: gruvbox ships no LICENSE file at all, and flatwhite's MIT template was never filled in. Add THIRD-PARTY-NOTICES.md and tests that fail if a theme is missing from it, if an adapted theme loses its inline attribution, or if the embedded set drifts from themes/.
Author: Max Johnson <me@maxj.phd> · 2026-07-19 19:44 UTC
Commit: dbad6be52122a783a77a0fcea84e6dc42deac0f0
Parent: df70758
32 files changed, +444 insertions, -204 deletions
M Cargo.toml +2 -1
@@ -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"
M README.md +44 -39
@@ -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.
M src/lib.rs +102
@@ -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
M themes/README.md +55 -75
@@ -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"