Files
thomas.koppandClaude Opus 5 4ea70c669e feat: Gruvbox Dark theme for Yazi 26.9, icons included
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>
2026-09-22 12:20:12 +02:00

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))