Panel chrome, filetype colours and the full 725-entry icon table. The icon table is Yazi's preset with every glyph colour remapped onto the Gruvbox palette: nearest neighbour in Oklab with lightness weighted down so hue drives the match, chromatic and greyish sources matched from separate pools, and target tones below 3:1 contrast on #282828 excluded. 221 upstream colours collapse onto 19 Gruvbox tones, dimmest icon at 3.48:1. regen-icons.py reproduces the mapping against a newer Yazi preset. Uses the current schema ([mgr]/[tabs]/[pick]/[cmp]), not the pre-25.x one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
2.7 KiB
Python
Executable File
82 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Regenerate the [icon] block of theme.toml against a newer Yazi preset.
|
|
# git clone --depth 1 https://github.com/sxyazi/yazi
|
|
# awk "/^\[icon\]/{f=1} f" yazi/yazi-config/preset/theme-dark.toml > icons-src.toml
|
|
# ./regen-icons.py icons-src.toml # writes icon-gruvbox.toml, paste over the [icon] block
|
|
import re, sys, math
|
|
|
|
SRC = sys.argv[1] if len(sys.argv) > 1 else "theme-dark.toml"
|
|
|
|
# Gruvbox dark palette, split so chromatic sources never collapse into grey
|
|
# and desaturated sources never turn into a vivid accent.
|
|
CHROMATIC = {
|
|
"#fb4934": "red",
|
|
"#b8bb26": "green", "#98971a": "green dim",
|
|
"#fabd2f": "yellow", "#d79921": "yellow dim",
|
|
"#83a598": "blue", "#458588": "blue dim",
|
|
"#d3869b": "purple", "#b16286": "purple dim",
|
|
"#8ec07c": "aqua", "#689d6a": "aqua dim",
|
|
"#fe8019": "orange", "#d65d0e": "orange dim",
|
|
}
|
|
NEUTRAL = {
|
|
"#fbf1c7": "fg0", "#ebdbb2": "fg1", "#d5c4a1": "fg2",
|
|
"#bdae93": "fg3", "#a89984": "fg4", "#928374": "gray",
|
|
}
|
|
|
|
def hex2rgb(h):
|
|
h = h.lstrip("#")
|
|
return tuple(int(h[i:i+2], 16) / 255 for i in (0, 2, 4))
|
|
|
|
def srgb_to_linear(c):
|
|
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
|
|
|
|
def oklab(h):
|
|
r, g, b = (srgb_to_linear(c) for c in hex2rgb(h))
|
|
l = 0.4122214708*r + 0.5363325363*g + 0.0514459929*b
|
|
m = 0.2119034982*r + 0.6806995451*g + 0.1073969566*b
|
|
s = 0.0883024619*r + 0.2817188376*g + 0.6299787005*b
|
|
l, m, s = (math.copysign(abs(v) ** (1/3), v) for v in (l, m, s))
|
|
return (
|
|
0.2104542553*l + 0.7936177850*m - 0.0040720468*s,
|
|
1.9779984951*l - 2.4285922050*m + 0.4505937099*s,
|
|
0.0259040371*l + 0.7827717662*m - 0.8086757660*s,
|
|
)
|
|
|
|
def chroma(h):
|
|
_, a, b = oklab(h)
|
|
return math.hypot(a, b)
|
|
|
|
CHROMA_FLOOR = 0.04 # below this a source colour reads as grey/white/black
|
|
|
|
def nearest(src):
|
|
pool = NEUTRAL if chroma(src) < CHROMA_FLOOR else CHROMATIC
|
|
sl, sa, sb = oklab(src)
|
|
best, bestd = None, None
|
|
for cand in pool:
|
|
cl, ca, cb = oklab(cand)
|
|
# lightness weighted down so hue drives the match
|
|
d = (0.7*(sl-cl))**2 + (sa-ca)**2 + (sb-cb)**2
|
|
if bestd is None or d < bestd:
|
|
best, bestd = cand, d
|
|
return best
|
|
|
|
text = open(SRC).read()
|
|
seen = {}
|
|
|
|
def sub(m):
|
|
src = m.group(1).lower()
|
|
dst = seen.setdefault(src, nearest(src))
|
|
return 'fg = "%s"' % dst
|
|
|
|
out = re.sub(r'fg = "(#[0-9a-fA-F]{6})"', sub, text)
|
|
open("icon-gruvbox.toml", "w").write(out)
|
|
|
|
# report
|
|
from collections import Counter
|
|
c = Counter(seen.values())
|
|
names = {**CHROMATIC, **NEUTRAL}
|
|
print("distinct source colours:", len(seen))
|
|
print("mapped onto %d gruvbox tones:" % len(c))
|
|
for col, n in c.most_common():
|
|
print(" %-9s %-11s %3d sources" % (col, names[col], n))
|