From 7b11746f0b6a280169150fc8e18146963a65c55c Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 11 Sep 2026 18:57:59 +0200 Subject: feat(palette): add Obsidian and Typora as consumers Both expose a first-class, update-safe theming hook, which is what separates them from the Electron apps that cannot be consumers: Signal, Discord and Spotify ship closed bundles whose only theming route is patching a signed asar that every update reverts. Obsidian gets a CSS snippet rather than a theme, so it layers over whatever theme a vault already uses instead of replacing the user's choice. Enabling it means appending to enabledCssSnippets in each vault's appearance.json, which bin/udt-appthemes does by reading the list and adding to it: rewriting the file would have switched off the layout snippet one vault was already using. Typora's theme is derived from the Dracula theme installed here, whose 350 rules all resolve through :root variables, so only that block is templated and none of the layout is touched. Verified across six vaults, nested ones included: the vault with an existing snippet kept it, a vault with no appearance.json got a valid one, and three consecutive installs leave exactly one udt entry. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015gbjA2bmswN8jyPDKzrvqe --- bin/udt-palette | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) (limited to 'bin/udt-palette') diff --git a/bin/udt-palette b/bin/udt-palette index edde7f3..f2807af 100755 --- a/bin/udt-palette +++ b/bin/udt-palette @@ -223,6 +223,89 @@ def gen_kitty(res, scheme, palette): return "\n".join(lines) + "\n" +def gen_obsidian(res, scheme, palette): + """Obsidian: a CSS snippet overriding the theme variables. + + A snippet rather than a theme, because a theme replaces the user's choice + outright while a snippet layers over whatever they have enabled. Obsidian + loads snippets after the theme, so these win without the theme having to go. + + The variables are Obsidian's documented public API for this. Only colour is + set: spacing and typography belong to whatever theme the vault uses. + """ + def c(role): + return hex6(res[role]) + + return f"""/* {BANNER.format(scheme=scheme)} + * + * Enabled per vault in Appearance > CSS snippets. install.sh writes this into + * every vault it finds and enables it without disturbing the snippets already + * on, so a vault keeps its layout snippets and gains these colours. + */ + +.theme-dark {{ + --background-primary: {c('bg')}; + --background-primary-alt: {c('bg_alt')}; + --background-secondary: {c('bg_alt')}; + --background-secondary-alt: {c('bg_deep')}; + --background-modifier-border: {c('border')}; + --background-modifier-hover: {c('surface')}; + --background-modifier-error: {c('critical')}; + --background-modifier-success:{c('success')}; + + --text-normal: {c('fg')}; + --text-muted: {c('fg_dim')}; + --text-faint: {c('fg_faint')}; + --text-error: {c('critical')}; + --text-success: {c('success')}; + --text-accent: {c('accent')}; + --text-accent-hover:{c('accent_bright')}; + --text-on-accent: {c('bg')}; + --text-selection: {hex8((*res['accent'][:3], 30))}; + --text-highlight-bg:{hex8((*res['warning'][:3], 40))}; + + --interactive-normal: {c('surface')}; + --interactive-hover: {c('surface_alt')}; + --interactive-accent: {c('accent')}; + --interactive-accent-hover: {c('accent_bright')}; + + --h1-color: {c('accent')}; + --h2-color: {c('accent')}; + --h3-color: {c('info')}; + --h4-color: {c('info')}; + --h5-color: {c('fg_dim')}; + --h6-color: {c('fg_dim')}; + + --code-normal: {c('warning')}; + --code-background: {c('bg_alt')}; + --blockquote-border-color: {c('accent')}; + --hr-color: {c('border')}; + --checkbox-color: {c('accent')}; + --tag-color: {c('info')}; + --tag-background: {hex8((*res['info'][:3], 20))}; +}} +""" + + +def gen_typora(res, scheme, palette, template): + """Typora: substitute the palette into the theme's :root block. + + Typora themes are one self-contained stylesheet, but this one routes all + 350 of its rules through variables in :root, so only that block is a + template. Derived from the dracula theme already installed here. + """ + out = template + for role in TYPORA_ROLES: + out = out.replace(f"@{role.upper()}@", hex6(res[role])) + return out + + +# Exactly the placeholders templates/typora/udt.css.in carries. +TYPORA_ROLES = ["bg", "bg_deep", "surface_alt", "fg", "fg_dim", "fg_faint", + "accent", "accent_bright", "critical", "warning", "success", + "info", "highlight"] + + KVANTUM_ROLES = ["bg", "bg_alt", "surface", "surface_alt", "surface_high", "fg", "fg_dim", "fg_faint", "mid_high", "accent", "accent_dim", "accent_bright", "highlight", "critical"] @@ -436,6 +519,8 @@ TARGETS = [ ("templates/conky.conf", gen_conky, "templates/conky.conf.in"), ("bin/udt_colors.py", gen_accent_py, None), ("templates/homepage/custom.css", gen_homepage, None), + ("templates/obsidian/udt.css", gen_obsidian, None), + ("templates/typora/udt.css", gen_typora, "templates/typora/udt.css.in"), ("templates/kvantum/theme.kvconfig", gen_kvantum, "templates/kvantum/theme.kvconfig.in"), ("templates/kvantum/theme.svg", gen_kvantum, "templates/kvantum/theme.svg.in"), ] -- cgit v1.2.3