Give the desktop real themes, video wallpapers, and honest titlebars

Appearance now opens on Themes: light and dark side by side, each
remembering its own choice, over galleries of ten shipped themes —
Tokyo Moon and Day joined by Moon Rose, Catppuccin, Nord, Gruvbox and
Everforest in both modes. A theme is a complete palette: the catalog
lives in themes.json, Theme.qml reads every color token from the
active record, and one render pipeline carries it to kitty, tmux,
btop, GTK, Vicinae, Firefox's chrome, and the lock screen. The Theme
editor builds new ones from four wells — wheel, hex, or eyedropper —
with derived surfaces, a saturation slider, debounced fine-tune, and
effects that save with the theme. Custom edits finally keep GNOME's
accent, kitty's border, and hyprlock in sync.

Wallpapers can be video: mpvpaper per output, hardware-decoded, muted
and looped, supervised and respawned. Panama owns the pausing — games,
battery, and a bar pill for right now — because the compositor
rebuilds full-screen blur for every frame a video wallpaper draws.
The lock screen gets a still frame.

Titlebars stop lying. GNOME apps get close-only on your chosen side,
the maximize and double-click settings are gone, the Settings window
obeys the same rules, and its titlebar can be turned off entirely.
Typography becomes five labeled dropdowns instead of a wall of
samples.

Contracts updated and written throughout (165 now); per the redesign
workflow none were executed — the full sweep runs once at the end.

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-23 23:39:04 -04:00
parent 7578348db1
commit cb7c09d208
68 changed files with 6115 additions and 1138 deletions
+46 -31
View File
@@ -8,7 +8,9 @@
set -euo pipefail
# The accents, from the one file that holds them.
# The accents and the theme resolver, from the one file that holds them. The
# lock screen renders the same theme panama-theme-apps does, resolved by the
# same function rather than by a second copy of the lookup.
# shellcheck source=./panama-palette
source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/panama-palette"
@@ -87,6 +89,17 @@ valid_path() {
resolve_wallpaper_path() {
local candidate="$1"
# A video wallpaper cannot play on the lock screen; VideoWallpaper.qml
# grabs a still frame when one is selected, and that frame stands in.
# A video with no cached frame falls through to the shipped image.
if [[ "$candidate" =~ \.(mp4|mkv|webm)$ ]]; then
local frame="${XDG_STATE_HOME:-$HOME/.local/state}/panama/video-wallpaper-frame.png"
if valid_path "$frame"; then
resolved_wallpaper="$frame"
return
fi
candidate=""
fi
if valid_path "$candidate"; then
resolved_wallpaper="$candidate"
return
@@ -101,14 +114,6 @@ resolve_wallpaper_path() {
fi
}
# Same 8 named accents as config/Theme.qml's `accents` map (kept in sync by
# hand -- there is no shared source between QML and a shell script), primary
# hue per scheme only: the lock screen shows a flat focus ring, not the
# two-stop gradient the window border does. Falls back to blue for an unknown
# name, matching Theme.accentPair's own fallback.
# hyprlock wants "R, G, B" decimal, not hex.
load_preferences() {
if [[ -r "$settings" ]] && jq -e 'type == "object"' "$settings" >/dev/null 2>&1; then
settings_valid=true
@@ -153,28 +158,35 @@ load_preferences() {
5) blur_passes=5; blur_size=12 ;;
esac
if [[ "$color_scheme" == light ]]; then
background_color='rgba(225, 226, 231, 1.0)'
foreground_color='rgba(55, 96, 191, 1.0)'
dim_color='rgba(97, 114, 176, 1.0)'
error_color='rgba(245, 42, 101, 1.0)'
field_color='rgba(208, 213, 227, 0.85)'
dim_hex='6172b0'
error_hex='f52a65'
else
background_color='rgba(34, 36, 54, 1.0)'
foreground_color='rgba(200, 211, 245, 1.0)'
dim_color='rgba(130, 139, 184, 1.0)'
error_color='rgba(255, 117, 127, 1.0)'
field_color='rgba(46, 47, 61, 0.85)'
dim_hex='828bb8'
error_hex='ff757f'
fi
# The colours come from the ACTIVE THEME, resolved by panama-palette from
# settings.json and config/themes.json -- the same function
# panama-theme-apps renders hyprlock.conf.template through. They used to be
# two hardcoded Tokyo Night tables here, a third copy in panama-theme-apps
# and a fourth in link-dotfiles, which is why choosing any other theme left
# the lock screen in last season's colour with nothing to say why.
eval "$(theme_vars "$(resolve_theme "$color_scheme")")"
# The focus ring is the accent role, not a scheme-relative one: it follows
# accentName the same way ColorScheme.qml's active_border does, and needs
# both the accent and the scheme since each accent carries a separate pair.
accent_rgb="$(hex_to_rgb "$(accent_hex "$accent_name" "$color_scheme")")"
# An unreadable themes.json is the only way to arrive here without a
# palette. Refusing is right: the seeded fallback config is themed, and
# hyprlock's own defaults -- which is what a config full of empty colours
# resolves to -- are a bright grey screen.
for token in bg fg fgMuted red bgPanel; do
palette_token="pal_$token"
[[ -n "${!palette_token:-}" ]] || return 1
done
background_color="rgba($(hex_to_rgb "$pal_bg"), 1.0)"
foreground_color="rgba($(hex_to_rgb "$pal_fg"), 1.0)"
dim_color="rgba($(hex_to_rgb "$pal_fgMuted"), 1.0)"
error_color="rgba($(hex_to_rgb "$pal_red"), 1.0)"
field_color="rgba($(hex_to_rgb "$pal_bgPanel"), 0.85)"
dim_hex="$pal_fgMuted"
error_hex="$pal_red"
# The focus ring is the theme's accent. accentName is still read as the
# fallback: a stored custom theme may carry no accent of its own, and the
# curated pair for the recorded name is a better answer than none.
accent_rgb="$(hex_to_rgb "$(hex_or "${theme_accent:-}" "$(accent_hex "$accent_name" "$color_scheme")")")"
accent_color="rgba($accent_rgb, 1.0)"
accent_ring_color="rgba($accent_rgb, 0.9)"
}
@@ -329,7 +341,10 @@ write_status() {
}
generate() {
load_preferences
if ! load_preferences; then
write_status false "$fallback" true "The lock-screen colors could not be resolved."
return 1
fi
mkdir -p "$state_dir" || return 1
if ! emit_config >"$temporary"; then
rm -f "$temporary" 2>/dev/null || true