Skip to main content

max / makeover

Rename theme-common to makeover, ship the theme set Split out of MNW/shared/theme-common with history intact. The crate was only reachable by relative path into the MNW checkout, so nothing that depended on it could be built without that repo present. Bring MNW/shared/themes in as themes/. The data now travels with the loader, and dev_themes_dir -- which hardcoded a walk up to MNW/shared/themes -- is replaced by bundled_themes_dir(), resolved against this crate's own manifest dir so it works from a path dependency and a cargo git checkout alike. Add a test that loads every shipped theme, so malformed theme data fails here rather than at a user's first launch. Licensed MIT for crates.io publication. The adapted themes are each MIT-derived and carry their upstream attribution.
Author: Max Johnson <me@maxj.phd> · 2026-07-19 19:10 UTC
Commit: df70758292cc32181427fd87d03496d06dcd7e4a
Parent: e023d97
40 files changed, +1349 insertions, -541 deletions
A .gitignore +5
@@ -0,0 +1,5 @@
1 + /target
2 +
3 + # Claude Code instructions (project-local; not for the public repo)
4 + CLAUDE.md
5 + /Cargo.lock
D Cargo.lock -500
@@ -1,504 +0,0 @@
1 - # This file is automatically @generated by Cargo.
2 - # It is not intended for manual editing.
3 - version = 4
4 -
5 - [[package]]
6 - name = "anyhow"
7 - version = "1.0.102"
8 - source = "registry+https://github.com/rust-lang/crates.io-index"
9 - checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
10 -
11 - [[package]]
12 - name = "bitflags"
13 - version = "2.11.0"
14 - source = "registry+https://github.com/rust-lang/crates.io-index"
15 - checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
16 -
17 - [[package]]
18 - name = "cfg-if"
19 - version = "1.0.4"
20 - source = "registry+https://github.com/rust-lang/crates.io-index"
21 - checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
22 -
23 - [[package]]
24 - name = "equivalent"
25 - version = "1.0.2"
26 - source = "registry+https://github.com/rust-lang/crates.io-index"
27 - checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
28 -
29 - [[package]]
30 - name = "errno"
31 - version = "0.3.14"
32 - source = "registry+https://github.com/rust-lang/crates.io-index"
33 - checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
34 - dependencies = [
35 - "libc",
36 - "windows-sys",
37 - ]
38 -
39 - [[package]]
40 - name = "fastrand"
41 - version = "2.3.0"
42 - source = "registry+https://github.com/rust-lang/crates.io-index"
43 - checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
44 -
45 - [[package]]
46 - name = "foldhash"
47 - version = "0.1.5"
48 - source = "registry+https://github.com/rust-lang/crates.io-index"
49 - checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
50 -
51 - [[package]]
52 - name = "getrandom"
53 - version = "0.4.2"
54 - source = "registry+https://github.com/rust-lang/crates.io-index"
55 - checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
56 - dependencies = [
57 - "cfg-if",
58 - "libc",
59 - "r-efi",
60 - "wasip2",
61 - "wasip3",
62 - ]
63 -
64 - [[package]]
65 - name = "hashbrown"
66 - version = "0.15.5"
67 - source = "registry+https://github.com/rust-lang/crates.io-index"
68 - checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
69 - dependencies = [
70 - "foldhash",
71 - ]
72 -
73 - [[package]]
74 - name = "hashbrown"
75 - version = "0.16.1"
76 - source = "registry+https://github.com/rust-lang/crates.io-index"
77 - checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
78 -
79 - [[package]]
80 - name = "heck"
81 - version = "0.5.0"
82 - source = "registry+https://github.com/rust-lang/crates.io-index"
83 - checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
84 -
85 - [[package]]
86 - name = "id-arena"
87 - version = "2.3.0"
88 - source = "registry+https://github.com/rust-lang/crates.io-index"
89 - checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
90 -
91 - [[package]]
92 - name = "indexmap"
93 - version = "2.13.0"
94 - source = "registry+https://github.com/rust-lang/crates.io-index"
95 - checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
96 - dependencies = [
97 - "equivalent",
98 - "hashbrown 0.16.1",
99 - "serde",
100 - "serde_core",
101 - ]
102 -
103 - [[package]]
104 - name = "itoa"
105 - version = "1.0.18"
106 - source = "registry+https://github.com/rust-lang/crates.io-index"
107 - checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
108 -
109 - [[package]]
110 - name = "leb128fmt"
111 - version = "0.1.0"
112 - source = "registry+https://github.com/rust-lang/crates.io-index"
113 - checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
114 -
115 - [[package]]
116 - name = "libc"
117 - version = "0.2.183"
118 - source = "registry+https://github.com/rust-lang/crates.io-index"
119 - checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
120 -
121 - [[package]]
122 - name = "linux-raw-sys"
123 - version = "0.12.1"
124 - source = "registry+https://github.com/rust-lang/crates.io-index"
125 - checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
126 -
127 - [[package]]
128 - name = "log"
129 - version = "0.4.29"
130 - source = "registry+https://github.com/rust-lang/crates.io-index"
131 - checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
132 -
133 - [[package]]
134 - name = "memchr"
135 - version = "2.8.0"
136 - source = "registry+https://github.com/rust-lang/crates.io-index"
137 - checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
138 -
139 - [[package]]
140 - name = "once_cell"
141 - version = "1.21.4"
142 - source = "registry+https://github.com/rust-lang/crates.io-index"
143 - checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
144 -
145 - [[package]]
146 - name = "prettyplease"
147 - version = "0.2.37"
148 - source = "registry+https://github.com/rust-lang/crates.io-index"
149 - checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
150 - dependencies = [
151 - "proc-macro2",
152 - "syn",
153 - ]
154 -
155 - [[package]]
156 - name = "proc-macro2"
157 - version = "1.0.106"
158 - source = "registry+https://github.com/rust-lang/crates.io-index"
159 - checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
160 - dependencies = [
161 - "unicode-ident",
162 - ]
163 -
164 - [[package]]
165 - name = "quote"
166 - version = "1.0.45"
167 - source = "registry+https://github.com/rust-lang/crates.io-index"
168 - checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
169 - dependencies = [
170 - "proc-macro2",
171 - ]
172 -
173 - [[package]]
174 - name = "r-efi"
175 - version = "6.0.0"
176 - source = "registry+https://github.com/rust-lang/crates.io-index"
177 - checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
178 -
179 - [[package]]
180 - name = "rustix"
181 - version = "1.1.4"
182 - source = "registry+https://github.com/rust-lang/crates.io-index"
183 - checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
184 - dependencies = [
185 - "bitflags",
186 - "errno",
187 - "libc",
188 - "linux-raw-sys",
189 - "windows-sys",
190 - ]
191 -
192 - [[package]]
193 - name = "semver"
194 - version = "1.0.27"
195 - source = "registry+https://github.com/rust-lang/crates.io-index"
196 - checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
197 -
198 - [[package]]
199 - name = "serde"
200 - version = "1.0.228"
201 - source = "registry+https://github.com/rust-lang/crates.io-index"
202 - checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
203 - dependencies = [
204 - "serde_core",
205 - "serde_derive",
206 - ]
207 -
208 - [[package]]
209 - name = "serde_core"
210 - version = "1.0.228"
211 - source = "registry+https://github.com/rust-lang/crates.io-index"
212 - checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
213 - dependencies = [
214 - "serde_derive",
215 - ]
216 -
217 - [[package]]
218 - name = "serde_derive"
219 - version = "1.0.228"
220 - source = "registry+https://github.com/rust-lang/crates.io-index"
221 - checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
222 - dependencies = [
223 - "proc-macro2",
224 - "quote",
225 - "syn",
226 - ]
227 -
228 - [[package]]
229 - name = "serde_json"
230 - version = "1.0.149"
231 - source = "registry+https://github.com/rust-lang/crates.io-index"
232 - checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
233 - dependencies = [
234 - "itoa",
235 - "memchr",
236 - "serde",
237 - "serde_core",
238 - "zmij",
239 - ]
240 -
241 - [[package]]
242 - name = "serde_spanned"
243 - version = "0.6.9"
244 - source = "registry+https://github.com/rust-lang/crates.io-index"
245 - checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
246 - dependencies = [
247 - "serde",
248 - ]
249 -
250 - [[package]]
251 - name = "syn"
252 - version = "2.0.117"
253 - source = "registry+https://github.com/rust-lang/crates.io-index"
254 - checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
255 - dependencies = [
256 - "proc-macro2",
257 - "quote",
258 - "unicode-ident",
259 - ]
260 -
261 - [[package]]
262 - name = "tempfile"
263 - version = "3.27.0"
264 - source = "registry+https://github.com/rust-lang/crates.io-index"
265 - checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
266 - dependencies = [
267 - "fastrand",
268 - "getrandom",
269 - "once_cell",
270 - "rustix",
271 - "windows-sys",
272 - ]
273 -
274 - [[package]]
275 - name = "theme-common"
276 - version = "0.7.0"
277 - dependencies = [
278 - "serde",
279 - "tempfile",
280 - "toml",
281 - ]
282 -
283 - [[package]]
284 - name = "toml"
285 - version = "0.8.23"
286 - source = "registry+https://github.com/rust-lang/crates.io-index"
287 - checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
288 - dependencies = [
289 - "serde",
290 - "serde_spanned",
291 - "toml_datetime",
292 - "toml_edit",
293 - ]
294 -
295 - [[package]]
296 - name = "toml_datetime"
297 - version = "0.6.11"
298 - source = "registry+https://github.com/rust-lang/crates.io-index"
299 - checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
300 - dependencies = [
301 - "serde",
302 - ]
303 -
304 - [[package]]
305 - name = "toml_edit"
306 - version = "0.22.27"
307 - source = "registry+https://github.com/rust-lang/crates.io-index"
308 - checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
309 - dependencies = [
310 - "indexmap",
311 - "serde",
312 - "serde_spanned",
313 - "toml_datetime",
314 - "toml_write",
315 - "winnow",
316 - ]
317 -
318 - [[package]]
319 - name = "toml_write"
320 - version = "0.1.2"
321 - source = "registry+https://github.com/rust-lang/crates.io-index"
322 - checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
323 -
324 - [[package]]
325 - name = "unicode-ident"
326 - version = "1.0.24"
327 - source = "registry+https://github.com/rust-lang/crates.io-index"
328 - checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
329 -
330 - [[package]]
331 - name = "unicode-xid"
332 - version = "0.2.6"
333 - source = "registry+https://github.com/rust-lang/crates.io-index"
334 - checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
335 -
336 - [[package]]
337 - name = "wasip2"
338 - version = "1.0.2+wasi-0.2.9"
339 - source = "registry+https://github.com/rust-lang/crates.io-index"
340 - checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
341 - dependencies = [
342 - "wit-bindgen",
343 - ]
344 -
345 - [[package]]
346 - name = "wasip3"
347 - version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
348 - source = "registry+https://github.com/rust-lang/crates.io-index"
349 - checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
350 - dependencies = [
351 - "wit-bindgen",
352 - ]
353 -
354 - [[package]]
355 - name = "wasm-encoder"
356 - version = "0.244.0"
357 - source = "registry+https://github.com/rust-lang/crates.io-index"
358 - checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
359 - dependencies = [
360 - "leb128fmt",
361 - "wasmparser",
362 - ]
363 -
364 - [[package]]
365 - name = "wasm-metadata"
366 - version = "0.244.0"
367 - source = "registry+https://github.com/rust-lang/crates.io-index"
368 - checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
369 - dependencies = [
370 - "anyhow",
371 - "indexmap",
372 - "wasm-encoder",
373 - "wasmparser",
374 - ]
375 -
376 - [[package]]
377 - name = "wasmparser"
378 - version = "0.244.0"
379 - source = "registry+https://github.com/rust-lang/crates.io-index"
380 - checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
381 - dependencies = [
382 - "bitflags",
383 - "hashbrown 0.15.5",
384 - "indexmap",
385 - "semver",
386 - ]
387 -
388 - [[package]]
389 - name = "windows-link"
390 - version = "0.2.1"
391 - source = "registry+https://github.com/rust-lang/crates.io-index"
392 - checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
393 -
394 - [[package]]
395 - name = "windows-sys"
396 - version = "0.61.2"
397 - source = "registry+https://github.com/rust-lang/crates.io-index"
398 - checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
399 - dependencies = [
400 - "windows-link",
401 - ]
402 -
403 - [[package]]
404 - name = "winnow"
405 - version = "0.7.15"
406 - source = "registry+https://github.com/rust-lang/crates.io-index"
407 - checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
408 - dependencies = [
409 - "memchr",
410 - ]
411 -
412 - [[package]]
413 - name = "wit-bindgen"
414 - version = "0.51.0"
415 - source = "registry+https://github.com/rust-lang/crates.io-index"
416 - checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
417 - dependencies = [
418 - "wit-bindgen-rust-macro",
419 - ]
420 -
421 - [[package]]
422 - name = "wit-bindgen-core"
423 - version = "0.51.0"
424 - source = "registry+https://github.com/rust-lang/crates.io-index"
425 - checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
426 - dependencies = [
427 - "anyhow",
428 - "heck",
429 - "wit-parser",
430 - ]
431 -
432 - [[package]]
433 - name = "wit-bindgen-rust"
434 - version = "0.51.0"
435 - source = "registry+https://github.com/rust-lang/crates.io-index"
436 - checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
437 - dependencies = [
438 - "anyhow",
439 - "heck",
440 - "indexmap",
441 - "prettyplease",
442 - "syn",
443 - "wasm-metadata",
444 - "wit-bindgen-core",
445 - "wit-component",
446 - ]
447 -
448 - [[package]]
449 - name = "wit-bindgen-rust-macro"
450 - version = "0.51.0"
451 - source = "registry+https://github.com/rust-lang/crates.io-index"
452 - checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
453 - dependencies = [
454 - "anyhow",
455 - "prettyplease",
456 - "proc-macro2",
457 - "quote",
458 - "syn",
459 - "wit-bindgen-core",
460 - "wit-bindgen-rust",
461 - ]
462 -
463 - [[package]]
464 - name = "wit-component"
465 - version = "0.244.0"
466 - source = "registry+https://github.com/rust-lang/crates.io-index"
467 - checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
468 - dependencies = [
469 - "anyhow",
470 - "bitflags",
471 - "indexmap",
472 - "log",
473 - "serde",
474 - "serde_derive",
475 - "serde_json",
476 - "wasm-encoder",
477 - "wasm-metadata",
478 - "wasmparser",
479 - "wit-parser",
480 - ]
481 -
482 - [[package]]
483 - name = "wit-parser"
484 - version = "0.244.0"
485 - source = "registry+https://github.com/rust-lang/crates.io-index"
486 - checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
487 - dependencies = [
488 - "anyhow",
489 - "id-arena",
490 - "indexmap",
491 - "log",
492 - "semver",
493 - "serde",
494 - "serde_derive",
495 - "serde_json",
496 - "unicode-xid",
497 - "wasmparser",
498 - ]
499 -
500 - [[package]]
Lines truncated
M Cargo.toml +5 -2
@@ -1,7 +1,10 @@
1 1 [package]
2 - name = "theme-common"
3 - version = "0.7.0"
2 + name = "makeover"
3 + version = "0.8.0"
4 4 edition = "2024"
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 + license = "MIT"
7 + repository = "https://git.sr.ht/~maxmj/makeover"
5 8
6 9 [dependencies]
7 10 serde = { version = "1", features = ["derive"] }
A LICENSE +21
@@ -0,0 +1,21 @@
1 + MIT License
2 +
3 + Copyright (c) 2026 Make Creative, LLC
4 +
5 + Permission is hereby granted, free of charge, to any person obtaining a copy
6 + of this software and associated documentation files (the "Software"), to deal
7 + in the Software without restriction, including without limitation the rights
8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 + copies of the Software, and to permit persons to whom the Software is
10 + furnished to do so, subject to the following conditions:
11 +
12 + The above copyright notice and this permission notice shall be included in all
13 + copies or substantial portions of the Software.
14 +
15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 + SOFTWARE.
M README.md +16 -12
@@ -1,17 +1,19 @@
1 - # theme-common
1 + # makeover
2 2
3 - Shared theme loading logic for TOML-based theme files. Parses theme metadata and color values from `.toml` files on disk.
3 + Shared theme loading for the make-family apps. Parses theme metadata and color values from `.toml` files on disk, resolves them into intent-based tokens, and derives the rest perceptually (OKLab) with WCAG contrast checks.
4 4
5 - Used by GoingsOn and Balanced Breakfast (Tauri apps that load themes at runtime). audiofiles uses a different compile-time embedding approach and does not use this crate.
5 + The crate ships the theme set it loads, in `themes/`. Consumers get working themes from a clean checkout without depending on any sibling repo.
6 +
7 + Used by MNW server, GoingsOn, Balanced Breakfast, audiofiles, and Alloy.
6 8
7 9 ## Usage
8 10
9 11 ```rust
10 - use theme_common::{load_theme, list_themes_from_dirs, dev_themes_dir};
12 + use makeover::{load_theme, list_themes_from_dirs, bundled_themes_dir};
11 13 use std::path::PathBuf;
12 14
13 15 // Set up theme directories (later entries override earlier ones)
14 - let bundled = PathBuf::from("/path/to/MNW/shared/themes");
16 + let bundled = bundled_themes_dir().expect("makeover ships themes/");
15 17 let custom = PathBuf::from("/path/to/user/custom-themes");
16 18 let dirs = vec![(bundled, false), (custom, true)];
17 19
@@ -27,15 +29,15 @@ println!("Name: {}", theme.meta.name); // "Catppuccin Mocha"
27 29 println!("Variant: {}", theme.meta.variant); // "dark"
28 30 println!("BG: {}", theme.colors["background.primary"]); // "#181825"
29 31
30 - // Dev helper: find MNW/shared/themes/ relative to CARGO_MANIFEST_DIR
31 - if let Some(dev_dir) = dev_themes_dir(Path::new(env!("CARGO_MANIFEST_DIR")), 3) {
32 - // dev_dir = .../Code/MNW/shared/themes
32 + // Build-from-source fallback: the themes this crate ships
33 + if let Some(dir) = bundled_themes_dir() {
34 + // dir = <makeover checkout>/themes
33 35 }
34 36 ```
35 37
36 38 ## Theme File Format
37 39
38 - Theme files are TOML with four color sections. See `../themes/` for the 16 bundled themes.
40 + Theme files are TOML with four color sections. See `themes/` for the 31 bundled themes.
39 41
40 42 ```toml
41 43 # Attribution comment (optional, for credit)
@@ -95,16 +97,18 @@ The theme ID is the filename without `.toml` (e.g., `catppuccin-mocha.toml` has
95 97 | `parse_meta(id, table, is_custom)` | Parse `[meta]` from a TOML table into `ThemeMeta` |
96 98 | `extract_colors(table)` | Flatten color sections into a `HashMap<String, String>` |
97 99 | `validate_theme_id(id)` | Check that an ID contains only safe characters |
98 - | `dev_themes_dir(manifest_dir, levels)` | Walk up parent directories to find `MNW/shared/themes/` (dev convenience) |
100 + | `bundled_themes_dir()` | The `themes/` directory this crate ships, for build-from-source fallback |
99 101
100 102 ## Directory Priority
101 103
102 104 `list_themes_from_dirs` and `load_theme` accept a list of `(PathBuf, bool)` pairs. Later directories override earlier ones by theme ID. The `bool` marks whether the directory contains user-custom themes (`is_custom` on `ThemeMeta`).
103 105
104 106 Typical setup for a Tauri app:
105 - 1. Bundled themes from `MNW/shared/themes/` (is_custom = false)
107 + 1. Bundled themes, packaged by the app or from `bundled_themes_dir()` (is_custom = false)
106 108 2. User themes from an app data directory (is_custom = true)
107 109
108 110 ## License
109 111
110 - PolyForm Noncommercial 1.0.0
112 + MIT.
113 +
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.
@@ -1,4 +1,4 @@
1 - # theme-common — Architecture
1 + # makeover — Architecture
2 2
3 3 Shared TOML theme loading for GO, BB, and AF.
4 4
@@ -1,7 +1,7 @@
1 1 fn main() {
2 2 let id = std::env::args().nth(1).unwrap_or_else(|| "makenotwork".into());
3 3 let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join("themes");
4 - let t = theme_common::load_semantic(&[(dir, false)], &id).unwrap();
4 + let t = makeover::load_semantic(&[(dir, false)], &id).unwrap();
5 5 for (k, v) in &t.intents {
6 6 println!(" --{k}: {v};");
7 7 }
M src/lib.rs +35 -25
@@ -591,21 +591,21 @@ pub fn export_theme(dirs: &[(PathBuf, bool)], id: &str, dest_path: &Path) -> Res
591 591 Ok(())
592 592 }
593 593
594 - /// Construct a dev fallback theme directory path.
594 + /// The theme directory this crate ships, for use as a build-from-source
595 + /// fallback.
595 596 ///
596 - /// Given a `CARGO_MANIFEST_DIR`, walks up `levels` parent directories and
597 - /// appends `MNW/shared/themes`. Returns the path if the directory exists.
598 - pub fn dev_themes_dir(manifest_dir: &Path, levels: usize) -> Option<PathBuf> {
599 - let mut path = manifest_dir.to_path_buf();
600 - for _ in 0..levels {
601 - path = path.parent()?.to_path_buf();
602 - }
603 - let themes = path.join("MNW").join("shared").join("themes");
604 - if themes.is_dir() {
605 - Some(themes)
606 - } else {
607 - None
608 - }
597 + /// Resolves against `makeover`'s own manifest directory, fixed at compile
598 + /// time, so it works from a path dependency and from a cargo git checkout
599 + /// alike. Installed systems should put their packaged theme directory ahead
600 + /// of this in the search path; this is the entry that keeps `cargo run` in a
601 + /// fresh clone from coming up with no themes at all.
602 + ///
603 + /// Returns `None` when the directory is absent — a cargo cache that has been
604 + /// cleaned, or a vendored copy that dropped the data — so callers degrade to
605 + /// their remaining search path rather than failing.
606 + pub fn bundled_themes_dir() -> Option<PathBuf> {
607 + let themes = Path::new(env!("CARGO_MANIFEST_DIR")).join("themes");
608 + if themes.is_dir() { Some(themes) } else { None }
609 609 }
610 610
611 611 #[cfg(test)]
@@ -971,16 +971,26 @@ six = "#88c0d0"
971 971 }
972 972
973 973 #[test]
974 - fn dev_themes_dir_finds_structure() {
975 - let root = tempfile::tempdir().unwrap();
976 - let leaf = root.path().join("leaf");
977 - std::fs::create_dir_all(&leaf).unwrap();
978 - let themes = root.path().join("MNW").join("shared").join("themes");
979 - std::fs::create_dir_all(&themes).unwrap();
980 - let got = dev_themes_dir(&leaf, 1).unwrap().canonicalize().unwrap();
981 - assert_eq!(got, themes.canonicalize().unwrap());
982 - // Missing structure -> None.
983 - let bare = tempfile::tempdir().unwrap();
984 - assert!(dev_themes_dir(bare.path(), 1).is_none());
974 + fn bundled_themes_dir_resolves_to_shipped_themes() {
975 + // The crate ships its themes, so this must resolve in-tree and the
976 + // Akari defaults the console falls back to must be present.
977 + let dir = bundled_themes_dir().expect("makeover ships a themes/ directory");
978 + assert!(dir.join("akari-dawn.toml").is_file());
979 + assert!(dir.join("akari-night.toml").is_file());
980 + }
981 +
982 + #[test]
983 + fn every_shipped_theme_loads() {
984 + // Guards the data, not just the loader: a malformed or truncated
985 + // .toml in themes/ is a shipping bug, and it should fail here rather
986 + // than at a user's first launch.
987 + let dir = bundled_themes_dir().unwrap();
988 + let dirs = vec![(dir.clone(), false)];
989 + let themes = list_themes_from_dirs(&dirs);
990 + assert!(themes.len() >= 30, "expected the full theme set, got {}", themes.len());
991 + for meta in &themes {
992 + load_theme(&dirs, &meta.id)
993 + .unwrap_or_else(|e| panic!("shipped theme `{}` failed to load: {e}", meta.id));
994 + }
985 995 }
986 996 }
@@ -0,0 +1,94 @@
1 + # Themes
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:
29 +
30 + ```toml
31 + # Attribution (optional)
32 + # Based on Catppuccin by Catppuccin Org -- MIT License
33 +
34 + [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 + ```
60 +
61 + ### Required Fields
62 +
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
66 +
67 + All other fields are optional. Apps should fall back gracefully when a color key is missing.
68 +
69 + ### Theme ID
70 +
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`.
72 +
73 + ## How Apps Load Themes
74 +
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 |
80 +
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.
82 +
83 + audiofiles embeds theme files at compile time and parses TOML directly without the `theme-common` crate.
84 +
85 + ## Adding a New Theme
86 +
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)
91 +
92 + ## License
93 +
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.
@@ -0,0 +1,52 @@
1 + # Based on Akari by Shu Kutsuzawa (cappyzawa) — MIT License
2 + # https://github.com/cappyzawa/akari-theme
3 + #
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."
18 +
19 + [meta]
20 + name = "Akari Dawn"
21 + variant = "light"
22 +
23 + [surface]
24 + page = "#e4ded6"
25 + raised = "#ede7de"
26 + sunken = "#cfc4b6"
27 + overlay = "#f0ece4"
28 +
29 + [content]
30 + primary = "#1a1816"
31 + secondary = "#222d38"
32 + muted = "#514b45"
33 +
34 + [action]
35 + primary = "#8a4530"
36 +
37 + [status]
38 + danger = "#6a2828"
39 + success = "#3a5830"
40 + warning = "#b07840"
41 + info = "#304050"
42 +
43 + [line]
44 + border = "#cabeae"
45 +
46 + [category]
47 + one = "#8a4530"
48 + two = "#3a5830"
49 + three = "#304050"
50 + four = "#b07840"
51 + five = "#806080"
52 + six = "#305858"
@@ -0,0 +1,42 @@
1 + # Based on Akari by Shu Kutsuzawa (cappyzawa) — MIT License
2 + # https://github.com/cappyzawa/akari-theme
3 + #
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.
8 +
9 + [meta]
10 + name = "Akari Night"
11 + variant = "dark"
12 +
13 + [surface]
14 + page = "#25231f"
15 + raised = "#2e2c27"
16 + sunken = "#1c1a17"
17 + overlay = "#35322c"
18 +
19 + [content]
20 + primary = "#e6ded3"
21 + secondary = "#c0b8ad"
22 + muted = "#8e847a"
23 +
24 + [action]
25 + primary = "#e26a3b"
26 +
27 + [status]
28 + danger = "#d25046"
29 + success = "#7faf6a"
30 + warning = "#d4a05a"
31 + info = "#7a8fa2"
32 +
33 + [line]
34 + border = "#3d3a35"
35 +
36 + [category]
37 + one = "#e26a3b"
38 + two = "#7faf6a"
39 + three = "#7a8fa2"
40 + four = "#d4a05a"
41 + five = "#8e7ba0"
42 + six = "#6f8f8a"
@@ -0,0 +1,39 @@
1 + # audiofiles — the app's titular default skin. Cross-available as a universal
2 + # theme. Built from a 5-tone earthy palette: Ash Grey #CCDAD1 / #9CAEA9,
3 + # Grey Olive #788585, Dim Grey #6F6866, Deep Mocha #38302E. Accents are
4 + # desaturated naturals tuned to harmonize with the sage-to-mocha range.
5 +
6 + [meta]
7 + name = "audiofiles"
8 + variant = "light"
9 +
10 + [surface]
11 + page = "#CCDAD1"
12 + raised = "#DAE3DC"
13 + sunken = "#9CAEA9"
14 + overlay = "#B4C5BB"
15 +
16 + [content]
17 + primary = "#38302E"
18 + secondary = "#6F6866"
19 + muted = "#788585"
20 +
21 + [action]
22 + primary = "#5E7C8E"
23 +
24 + [status]
25 + danger = "#B05F4E"
26 + success = "#6F8A5C"
27 + warning = "#C19A53"
28 + info = "#5F8C82"
29 +
30 + [line]
31 + border = "#9CAEA9"
32 +
33 + [category]
34 + one = "#B05F4E"
35 + two = "#6F8A5C"
36 + three = "#5E7C8E"
37 + four = "#C19A53"
38 + five = "#836A80"
39 + six = "#5F8C82"
@@ -0,0 +1,37 @@
1 + # Based on Ayu by Konstantin Pschera — MIT License
2 + # https://github.com/ayu-theme
3 +
4 + [meta]
5 + name = "Ayu Light"
6 + variant = "light"
7 +
8 + [surface]
9 + page = "#e7eaed"
10 + raised = "#f2f4f6"
11 + sunken = "#d0d4d8"
12 + overlay = "#dde1e5"
13 +
14 + [content]
15 + primary = "#5c6166"
16 + secondary = "#6b7580"
17 + muted = "#8b9199"
18 +
19 + [action]
20 + primary = "#399ee6"
21 +
22 + [status]
23 + danger = "#f07171"
24 + success = "#86b300"
25 + warning = "#ffaa33"
26 + info = "#55b4d4"
27 +
28 + [line]
29 + border = "#828c9a"
30 +
31 + [category]
32 + one = "#f07171"
33 + two = "#86b300"
34 + three = "#399ee6"
35 + four = "#ffaa33"
36 + five = "#a37acc"
37 + six = "#55b4d4"
@@ -0,0 +1,37 @@
1 + # Based on Ayu by dempfi — MIT License
2 + # https://github.com/ayu-theme/ayu-colors
3 +
4 + [meta]
5 + name = "Ayu Mirage"
6 + variant = "dark"
7 +
8 + [surface]
9 + page = "#1f2430"
10 + raised = "#242936"
11 + sunken = "#272d38"
12 + overlay = "#191e29"
13 +
14 + [content]
15 + primary = "#cccac2"
16 + secondary = "#b8b4aa"
17 + muted = "#707a8c"
18 +
19 + [action]
20 + primary = "#5ccfe6"
21 +
22 + [status]
23 + danger = "#f28779"
24 + success = "#bae67e"
25 + warning = "#ffd580"
26 + info = "#95e6cb"
27 +
28 + [line]
29 + border = "#33415e"
30 +
31 + [category]
32 + one = "#f28779"
33 + two = "#bae67e"
34 + three = "#5ccfe6"
35 + four = "#ffd580"
36 + five = "#d4bfff"
37 + six = "#95e6cb"
@@ -0,0 +1,37 @@
1 + # Based on Carbonfox by EdenEast — MIT License
2 + # https://github.com/EdenEast/nightfox.nvim
3 +
4 + [meta]
5 + name = "Carbonfox"
6 + variant = "dark"
7 +
8 + [surface]
9 + page = "#161616"
10 + raised = "#252525"
11 + sunken = "#1e1e1e"
12 + overlay = "#101010"
13 +
14 + [content]
15 + primary = "#f2f4f8"
16 + secondary = "#dde1e6"
17 + muted = "#878d96"
18 +
19 + [action]
20 + primary = "#78a9ff"
21 +
22 + [status]
23 + danger = "#ee5396"
24 + success = "#25be6a"
25 + warning = "#08bdba"
26 + info = "#33b1ff"
27 +
28 + [line]
29 + border = "#393939"
30 +
31 + [category]
32 + one = "#ee5396"
33 + two = "#25be6a"
34 + three = "#78a9ff"
35 + four = "#08bdba"
36 + five = "#be95ff"
37 + six = "#33b1ff"
@@ -0,0 +1,37 @@
1 + # Based on Catppuccin by Catppuccin Org — MIT License
2 + # https://github.com/catppuccin/catppuccin
3 +
4 + [meta]
5 + name = "Catppuccin Frappé"
6 + variant = "dark"
7 +
8 + [surface]
9 + page = "#303446"
10 + raised = "#232634"
11 + sunken = "#414559"
12 + overlay = "#292c3c"
13 +
14 + [content]
15 + primary = "#c6d0f5"
16 + secondary = "#b5bfe2"
17 + muted = "#a5adce"
18 +
19 + [action]
20 + primary = "#8caaee"
21 +
22 + [status]
23 + danger = "#e78284"
24 + success = "#a6d189"
25 + warning = "#e5c890"
26 + info = "#81c8be"
27 +
28 + [line]
29 + border = "#626880"
30 +
31 + [category]
32 + one = "#e78284"
33 + two = "#a6d189"
34 + three = "#8caaee"
35 + four = "#e5c890"
36 + five = "#ca9ee6"
37 + six = "#81c8be"
@@ -0,0 +1,37 @@
1 + # Based on Catppuccin by Catppuccin Org — MIT License
2 + # https://github.com/catppuccin/catppuccin
3 +
4 + [meta]
5 + name = "Catppuccin Latte"
6 + variant = "light"
7 +
8 + [surface]
9 + page = "#e6e9ef"
10 + raised = "#eff1f5"
11 + sunken = "#ccd0da"
12 + overlay = "#dce0e8"
13 +
14 + [content]
15 + primary = "#4c4f69"
16 + secondary = "#5c5f77"
17 + muted = "#6c6f82"
18 +
19 + [action]
20 + primary = "#1e66f5"
21 +
22 + [status]
23 + danger = "#d20f39"
24 + success = "#40a02b"
25 + warning = "#df8e1d"
26 + info = "#04a5e5"
27 +
28 + [line]
29 + border = "#bcc0cc"
30 +
31 + [category]
32 + one = "#d20f39"
33 + two = "#40a02b"
34 + three = "#1e66f5"
35 + four = "#df8e1d"
36 + five = "#8839ef"
37 + six = "#04a5e5"
@@ -0,0 +1,37 @@
1 + # Based on Catppuccin by Catppuccin Org — MIT License
2 + # https://github.com/catppuccin/catppuccin
3 +
4 + [meta]
5 + name = "Catppuccin Macchiato"
6 + variant = "dark"
7 +
8 + [surface]
9 + page = "#24273a"
10 + raised = "#181926"
11 + sunken = "#363a4f"
12 + overlay = "#1e2030"
13 +
14 + [content]
15 + primary = "#cad3f5"
16 + secondary = "#b8c0e0"
17 + muted = "#a5adcb"
18 +
19 + [action]
20 + primary = "#8aadf4"
21 +
22 + [status]
23 + danger = "#ed8796"
24 + success = "#a6da95"
25 + warning = "#eed49f"
26 + info = "#8bd5ca"
27 +
28 + [line]
29 + border = "#5b6078"
30 +
31 + [category]
32 + one = "#ed8796"
33 + two = "#a6da95"
34 + three = "#8aadf4"
35 + four = "#eed49f"
36 + five = "#c6a0f6"
37 + six = "#8bd5ca"
@@ -0,0 +1,37 @@
1 + # Based on Catppuccin by Catppuccin Org — MIT License
2 + # https://github.com/catppuccin/catppuccin
3 +
4 + [meta]
5 + name = "Catppuccin Mocha"
6 + variant = "dark"
7 +
8 + [surface]
9 + page = "#181825"
10 + raised = "#1e1e2e"
11 + sunken = "#313244"
12 + overlay = "#11111b"
13 +
14 + [content]
15 + primary = "#cdd6f4"
16 + secondary = "#bac2de"
17 + muted = "#9399b2"
18 +
19 + [action]
20 + primary = "#89b4fa"
21 +
22 + [status]
23 + danger = "#f38ba8"
24 + success = "#a6e3a1"
25 + warning = "#f9e2af"
26 + info = "#89dceb"
27 +
28 + [line]
29 + border = "#45475a"
30 +
31 + [category]
32 + one = "#f38ba8"
33 + two = "#a6e3a1"
34 + three = "#89b4fa"
35 + four = "#f9e2af"
36 + five = "#cba6f7"
37 + six = "#89dceb"
@@ -0,0 +1,37 @@
1 + # Based on Dawnfox by EdenEast — MIT License
2 + # https://github.com/EdenEast/nightfox.nvim
3 +
4 + [meta]
5 + name = "Dawnfox"
6 + variant = "light"
7 +
8 + [surface]
9 + page = "#faf4ed"
10 + raised = "#f5efe8"
11 + sunken = "#ebe0df"
12 + overlay = "#ebe5df"
13 +
14 + [content]
15 + primary = "#575279"
16 + secondary = "#625c87"
17 + muted = "#a8a3b3"
18 +
19 + [action]
20 + primary = "#286983"
21 +
22 + [status]
23 + danger = "#b4637a"
24 + success = "#618774"
25 + warning = "#ea9d34"
26 + info = "#56949f"
27 +
28 + [line]
29 + border = "#bdbfc9"
30 +
31 + [category]
32 + one = "#b4637a"
33 + two = "#618774"
34 + three = "#286983"
35 + four = "#ea9d34"
36 + five = "#907aa9"
37 + six = "#56949f"