feat: centralize theme palette + add waybar bluetooth module
Theme centralization: - theme/colors.json is now the single source of truth for the palette. - New theme/generate-theme.py renders, at install time, the three derived artifacts into ~/.config: colors.css (waybar/wofi @import), mako config, and hyprlock-colors.conf (hyprlang $tn_* variables). - theme/colors.css removed from the repo (now generated); install.sh calls the generator instead of the old inline mako snippet. - hyprlock.conf sources the generated color file and uses $tn_* variables instead of hardcoded rgba() values. - Generator covered by theme/test_generate_theme.py. Bluetooth: - Added waybar built-in bluetooth module (config.jsonc + style.css), with bluez/bluez-utils in packages.txt. Includes design spec and implementation plan under docs/superpowers/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# theme/generate-theme.py
|
||||
"""Generate desktop config artifacts from theme/colors.json (single source of truth)."""
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
def load_palette(path):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def hex_to_rgb(h):
|
||||
h = h.lstrip("#")
|
||||
return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
|
||||
|
||||
|
||||
def hex_to_hyprlock_rgba(h):
|
||||
return f"rgba({h.lstrip('#')}ff)"
|
||||
|
||||
|
||||
# (key in colors.json) -> (css variable suffix)
|
||||
_CSS_MAP = [
|
||||
("background", "bg"), ("background_alt", "bg-alt"), ("background_high", "bg-high"),
|
||||
("foreground", "fg"), ("foreground_dim", "fg-dim"), ("foreground_muted", "fg-muted"),
|
||||
("blue", "blue"), ("green", "green"), ("yellow", "yellow"),
|
||||
("red", "red"), ("magenta", "magenta"), ("cyan", "cyan"),
|
||||
]
|
||||
_ALPHA_VARIANTS = [("a90", "0.90"), ("a95", "0.95"), ("a96", "0.96")]
|
||||
|
||||
|
||||
def render_mako_config(pal):
|
||||
return (
|
||||
f"background-color={pal['background']}\n"
|
||||
f"text-color={pal['foreground']}\n"
|
||||
"border-size=2\n"
|
||||
f"border-color={pal['blue']}\n"
|
||||
"default-timeout=4000\n"
|
||||
)
|
||||
|
||||
|
||||
def render_colors_css(pal):
|
||||
lines = ["/* Tomorrow Night - GENERATED from colors.json by generate-theme.py. DO NOT EDIT. */", ""]
|
||||
for key, suffix in _CSS_MAP:
|
||||
lines.append(f"@define-color tn-{suffix} {pal[key]};")
|
||||
lines.append("")
|
||||
r, g, b = hex_to_rgb(pal["background"])
|
||||
for name, alpha in _ALPHA_VARIANTS:
|
||||
lines.append(f"@define-color tn-bg-{name} rgba({r}, {g}, {b}, {alpha});")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# (colors.json key) -> (hyprlock variable name)
|
||||
_HYPRLOCK_MAP = [
|
||||
("foreground", "tn_fg"), ("foreground_dim", "tn_fg_dim"),
|
||||
("background_alt", "tn_bg_alt"), ("blue", "tn_blue"),
|
||||
("red", "tn_red"), ("green", "tn_green"),
|
||||
]
|
||||
|
||||
|
||||
def render_hyprlock_colors(pal):
|
||||
lines = ["# GENERATED from colors.json by generate-theme.py. DO NOT EDIT."]
|
||||
for key, var in _HYPRLOCK_MAP:
|
||||
lines.append(f"${var} = {hex_to_hyprlock_rgba(pal[key])}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def main():
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
pal = load_palette(os.path.join(here, "colors.json"))
|
||||
cfgdir = os.path.expanduser("~/.config")
|
||||
os.makedirs(os.path.join(cfgdir, "theme"), exist_ok=True)
|
||||
os.makedirs(os.path.join(cfgdir, "mako"), exist_ok=True)
|
||||
writes = {
|
||||
os.path.join(cfgdir, "theme", "colors.css"): render_colors_css(pal),
|
||||
os.path.join(cfgdir, "mako", "config"): render_mako_config(pal),
|
||||
os.path.join(cfgdir, "theme", "hyprlock-colors.conf"): render_hyprlock_colors(pal),
|
||||
}
|
||||
for path, content in writes.items():
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
print(f" generated {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user