From 16275947ec0bf98ad6a3bdc3f950e0c768334cf1 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Mon, 6 Jul 2026 20:14:30 +0100 Subject: [PATCH] =?UTF-8?q?feat(themes):=20full=20revision=20of=20executiv?= =?UTF-8?q?e-slate=20=E2=80=94=20palette=20rebuild=20+=20real=20whole-swap?= =?UTF-8?q?=20bar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The executive-slate preset shipped a waybar.css that referenced @base/@accent with no @define-color block, and no matching waybar.jsonc — so the raw-read override ran a largely unstyled bar (the item-28 partial-whole-swap defect). The palette was also flat (base->surface only 1.13x) with a dim accent (#4D78CC, 3.91 on base). - Palette: widen surface stepping (base->surface 1.13x -> 1.29x so panels/ selection/hover separate), brighten the accent (#4D78CC -> #5A8AE6, 3.91 -> 5.04 on base, now AA text), lift subtext/good/warn/bad a tier, realign ansi, soften bright-white (#FFFFFF -> #F0F3F9). text stays 12.5 AAA. Contrast checker: all 23 themes x 7 pairings pass. - Waybar: complete the bespoke bar as a real whole-swap. New waybar.jsonc with the full 17-module set (parity with summer-day/night); waybar.css rewritten to lead with its own @define-color block and cover every module + state class the helper scripts emit (.recording/.activated/.on/.available/.dnd-*/battery). Identity: solid, edge-to-edge, sharp corners, accent underline on the active workspace. - Rofi: drop the broken var() idiom for the house *{}/@name convention (split border syntax, message/textbox/scrollbar elements for the theme-grid picker). - CONVENTIONS: Waybar parity rule now names executive-slate as a third whole-swap target (was stale at summer-only). V1: nix flake check --no-build green; strict JSON parse of both files; real rofi parsed the .rasi cleanly; with executive-slate set active, the module eval resolves waybar.settings -> the jsonc, waybar.style -> the css, rofi.theme -> the override rasi, and theme.nix accepts the palette. NOT yet done: on-screen render of the bar/rofi/desktop (V2/V3) and preview.png (deferred to on-hardware capture, per the chosen path). Co-Authored-By: Claude Opus 4.8 --- agent/CONVENTIONS.md | 4 +- themes/executive-slate.json | 56 +++++----- themes/executive-slate/rofi.rasi | 100 ++++++++++++------ themes/executive-slate/waybar.css | 134 +++++++++++++++++++----- themes/executive-slate/waybar.jsonc | 157 ++++++++++++++++++++++++++++ 5 files changed, 362 insertions(+), 89 deletions(-) create mode 100644 themes/executive-slate/waybar.jsonc diff --git a/agent/CONVENTIONS.md b/agent/CONVENTIONS.md index 0250779..d0c5cbd 100644 --- a/agent/CONVENTIONS.md +++ b/agent/CONVENTIONS.md @@ -43,8 +43,8 @@ those, it probably shouldn't exist. - **Waybar:** new indicators self-gate (hidden when irrelevant), use named `writeShellScriptBin`s on PATH (so static configs can exec them by bare name), and are added to **both** the generated `waybar.nix` - config **and** the summer-day/night `waybar.jsonc` whole-swaps (the - parity rule). + config **and** every `waybar.jsonc` whole-swap — summer-day, summer-night + and executive-slate (the parity rule). - **Theming:** every new visual surface consumes the palette from the state JSON. There is no second renderer to keep in sync — add the key to the JSON, consume it in the module. diff --git a/themes/executive-slate.json b/themes/executive-slate.json index 5ef6d79..0b6f66c 100644 --- a/themes/executive-slate.json +++ b/themes/executive-slate.json @@ -5,39 +5,39 @@ "mode": "dark", "wallpaper": "", "colors": { - "base": "#1A1D24", - "mantle": "#14161C", - "surface": "#232731", - "overlay": "#333947", - "text": "#D4DAE8", - "subtext": "#8792A6", - "muted": "#525A6E", - "accent": "#4D78CC", - "accentAlt": "#6C92D9", - "good": "#68A06B", - "warn": "#D19C4C", - "bad": "#C95F65" + "base": "#191C24", + "mantle": "#111319", + "surface": "#2A3040", + "overlay": "#3E4759", + "text": "#D7DDEA", + "subtext": "#9AA5BB", + "muted": "#5C6578", + "accent": "#5A8AE6", + "accentAlt": "#86A9F2", + "good": "#6FAE72", + "warn": "#DBA85A", + "bad": "#D06B71" }, "border": { "active": "accent", "inactive": "overlay" }, "ansi": [ - "#232731", - "#C95F65", - "#68A06B", - "#D19C4C", - "#4D78CC", - "#836CC9", - "#4C99A0", - "#D4DAE8", - "#525A6E", - "#D67177", - "#7BB57F", - "#E0AF5E", - "#6C92D9", - "#9A84E0", - "#5FABB3", - "#FFFFFF" + "#2A3040", + "#D06B71", + "#6FAE72", + "#DBA85A", + "#5A8AE6", + "#8C79D6", + "#55A5AE", + "#D7DDEA", + "#5C6578", + "#DE7C82", + "#82BE85", + "#E7BB6E", + "#86A9F2", + "#A594E8", + "#6FBEC6", + "#F0F3F9" ] } diff --git a/themes/executive-slate/rofi.rasi b/themes/executive-slate/rofi.rasi index 0759722..03ba9a9 100644 --- a/themes/executive-slate/rofi.rasi +++ b/themes/executive-slate/rofi.rasi @@ -1,81 +1,119 @@ -/* - * Executive Slate Rofi Theme - * Clean, flat, functional, professional. +/** + * Executive Slate — rofi whole-swap (themes//rofi.rasi). + * Identity: a flat, sharp-cornered "control panel". No zebra, no glow — + * a solid slate window, a recessed surface inputbar, and one accent edge + * marking the selected row. The `configuration {}` block is omitted on + * purpose — modi/terminal/show-icons come from modules/home/rofi.nix. + * Element structure mirrors the generated theme so the theme-grid + * picker's per-invocation -theme-str (vertical cards) still lays out. */ * { + base: #191C24; + surface: #2A3040; + overlay: #3E4759; + text: #D7DDEA; + subtext: #9AA5BB; + muted: #5C6578; + accent: #5A8AE6; + accentAlt: #86A9F2; + + font: "Inter 13"; background-color: transparent; - text-color: var(text, #D4DAE8); - font: "Inter 15"; + text-color: @text; } window { - width: 600px; - background-color: var(base, #1A1D24); + background-color: @base; border: 1px; - border-color: var(overlay, #333947); + border-color: @overlay; border-radius: 4px; - padding: 24px; + width: 40%; + location: center; + anchor: center; + padding: 12px; } mainbox { - spacing: 12px; + background-color: transparent; + children: [ inputbar, message, listview ]; + spacing: 10px; } inputbar { - background-color: var(surface, #232731); + children: [ prompt, entry ]; + background-color: @surface; + text-color: @text; border: 1px; - border-color: var(overlay, #333947); + border-color: @overlay; border-radius: 4px; padding: 10px 14px; spacing: 10px; - children: [ prompt, entry ]; } -prompt { - text-color: var(accent, #4D78CC); -} +prompt { text-color: @accent; } entry { - placeholder: "Type to filter..."; - placeholder-color: var(subtext, #8792A6); + text-color: @text; + cursor: text; + placeholder: "Type to filter…"; + placeholder-color: @muted; } listview { - lines: 7; - spacing: 4px; + background-color: transparent; + columns: 1; + lines: 8; + dynamic: true; scrollbar: false; - border: 0px; + spacing: 4px; + padding: 4px 0px 0px 0px; } element { - padding: 8px 14px; + background-color: transparent; + text-color: @text; + orientation: horizontal; border-radius: 4px; + padding: 9px 14px; spacing: 12px; } -element normal.normal, element alternate.normal { +element normal.normal, +element alternate.normal { background-color: transparent; } -/* Functional highlight: solid surface color with a subtle accent edge */ +/* The one highlight: a solid surface row with an accent edge. */ element selected.normal { - background-color: var(surface, #232731); - text-color: var(text, #D4DAE8); - border: 0 0 0 4px; - border-color: var(accent, #4D78CC); + background-color: @surface; + text-color: @text; + border: 0px 0px 0px 3px; + border-color: @accent; } element-icon { - size: 24px; background-color: transparent; + size: 36px; + cursor: inherit; } element-text { background-color: transparent; + text-color: inherit; + highlight: bold; vertical-align: 0.5; } -element-text selected { - text-color: var(text, #D4DAE8); +message { padding: 0px; } + +textbox { + background-color: @surface; + text-color: @text; + border: 1px; + border-color: @overlay; + border-radius: 4px; + padding: 9px 14px; } + +scrollbar { width: 0px; } diff --git a/themes/executive-slate/waybar.css b/themes/executive-slate/waybar.css index a350dfa..bc77640 100644 --- a/themes/executive-slate/waybar.css +++ b/themes/executive-slate/waybar.css @@ -1,21 +1,42 @@ -/* Executive Slate Waybar — Solid, functional, professional */ +/* + * Executive Slate — Waybar whole-swap (themes//waybar.css). + * + * A theme waybar.css replaces the ENTIRE generated stylesheet and is read + * raw (no palette is prepended), so this file MUST define its own colors. + * Identity: a solid, non-transparent, edge-to-edge status strip — no + * floating island, sharp corners, tight modules, and a single accent + * underline marking the active workspace. Companion: waybar.jsonc. + */ + +@define-color base #191C24; +@define-color mantle #111319; +@define-color surface #2A3040; +@define-color overlay #3E4759; +@define-color text #D7DDEA; +@define-color subtext #9AA5BB; +@define-color muted #5C6578; +@define-color accent #5A8AE6; +@define-color accentAlt #86A9F2; +@define-color good #6FAE72; +@define-color warn #DBA85A; +@define-color bad #D06B71; * { - font-family: "Inter", "Symbols Nerd Font", sans-serif; - font-size: 15px; + font-family: "Inter", "Symbols Nerd Font", "JetBrainsMono Nerd Font", sans-serif; + font-size: 13px; font-weight: 500; border: none; border-radius: 0; min-height: 0; } +/* Solid bar, crisp edge against the content below. */ window#waybar { background: @base; - border-bottom: 1px solid @mantle; + border-bottom: 1px solid @overlay; color: @text; } -/* Tightly packed modules for a functional feel */ .modules-left, .modules-center, .modules-right { @@ -23,35 +44,62 @@ window#waybar { padding: 0; } -#workspaces button, -#window, +/* Uniform module rhythm; groups get a wider breath below. */ +#custom-nomarchy, #clock, -#tray, -#pulseaudio, -#battery, -#custom-notification, -#custom-vpn, -#custom-powerprofile, +#custom-recording, +#idle_inhibitor, +#custom-nightlight, +#language, #custom-updates, -#custom-doctor { - padding: 0 12px; +#pulseaudio, +#custom-powerprofile, +#battery, +#custom-vpn, +#custom-doctor, +#custom-notification, +#tray, +#custom-powermenu { + color: @text; + padding: 0 11px; margin: 0; - border-radius: 0; } -/* Subtle hover effects */ +/* Group heads — a wider gap before each functional cluster of the + right side (media/toggles · stats · status). Self-hiding heads + (e.g. no battery on a desktop) degrade to the uniform spacing. */ +#pulseaudio, +#battery, +#custom-vpn { margin-left: 6px; } + +/* ── Menu launcher — the one branded accent glyph ────────────────── */ +#custom-nomarchy { + color: @accent; + font-size: 15px; + padding: 0 14px; +} +#custom-nomarchy:hover { + background: @surface; + color: @accentAlt; +} + +/* ── Workspaces — flat cells, active carries an accent underline ──── */ +#workspaces { padding: 0; margin: 0; } + #workspaces button { color: @subtext; - transition: all 0.2s ease-in-out; + padding: 0 11px; + margin: 0; border-bottom: 2px solid transparent; + transition: color 0.15s ease-in-out, background 0.15s ease-in-out; } #workspaces button:hover { background: @surface; color: @text; + border-bottom: 2px solid @overlay; } -/* Active gets a strong underline instead of a glowing background */ #workspaces button.active { color: @text; background: @surface; @@ -63,23 +111,53 @@ window#waybar { border-bottom: 2px solid @bad; } -/* Minimal indicators */ +/* ── Clock — time (center) and date (left) share the #clock id ───── */ +#clock { + color: @text; + font-weight: 600; +} + +/* ── Status cluster: neutral by default, semantic on state ───────── */ +#custom-recording.recording { color: @bad; } + +#idle_inhibitor.activated { color: @warn; } + +#custom-nightlight.on { color: @warn; } + +#custom-updates.available { color: @accent; } + +/* Power-profile glyph renders small in its em box — size it up. */ +#custom-powerprofile { font-size: 16px; } + +#pulseaudio.muted { color: @muted; } + #battery.charging { color: @good; } -#battery.warning:not(.charging) { color: @warn; } +#battery.warning:not(.charging) { color: @warn; } #battery.critical:not(.charging) { color: @bad; - background: @surface; border-bottom: 2px solid @bad; } -#pulseaudio.muted { color: @muted; } -#custom-updates { color: @accentAlt; } -#custom-doctor { color: @bad; } -#custom-vpn { color: @accent; } +#custom-vpn.on { color: @good; } -/* Functional tooltip */ +/* Doctor is only rendered when something is ✖, so always the alert. */ +#custom-doctor { color: @bad; } + +#custom-notification.notification { color: @accent; } +#custom-notification.dnd-none, +#custom-notification.dnd-notification { color: @muted; } + +#tray { color: @text; } + +#custom-powermenu { color: @subtext; } +#custom-powermenu:hover { + background: @surface; + color: @bad; +} + +/* ── Tooltips ─────────────────────────────────────────────────────── */ tooltip { - background: @base; + background: @surface; border: 1px solid @overlay; border-radius: 4px; } diff --git a/themes/executive-slate/waybar.jsonc b/themes/executive-slate/waybar.jsonc new file mode 100644 index 0000000..1d7d9a8 --- /dev/null +++ b/themes/executive-slate/waybar.jsonc @@ -0,0 +1,157 @@ +{ + "margin-top": 0, + "margin-left": 0, + "margin-bottom": 0, + "margin-right": 0, + "height": 34, + "layer": "top", + "position": "top", + "spacing": 0, + "reload_style_on_change": true, + + "modules-left": ["custom/nomarchy", "hyprland/workspaces", "clock#date"], + "modules-center": ["clock"], + "modules-right": ["custom/recording", "idle_inhibitor", "custom/nightlight", "hyprland/language", "custom/updates", "pulseaudio", "custom/powerprofile", "battery", "custom/vpn", "custom/doctor", "custom/notification", "tray", "custom/powermenu"], + + "custom/nomarchy": { + "interval": "once", + "format": "", + "on-click": "nomarchy-menu", + "tooltip-format": "Nomarchy menu" + }, + + "hyprland/workspaces": { + "disable-scroll": true, + "all-outputs": true, + "on-click": "activate", + "on-scroll-up": "hyprctl dispatch workspace r+1", + "on-scroll-down": "hyprctl dispatch workspace r-1" + }, + + "clock": { + "format": "{:%H:%M}", + "tooltip": true, + "tooltip-format": "{:%Z (UTC%z)}\n{calendar}" + }, + + "clock#date": { + "format": "{:%a %d %b}", + "tooltip": false + }, + + "custom/recording": { + "return-type": "json", + "interval": 10, + "signal": 8, + "exec": "nomarchy-record status", + "on-click": "nomarchy-record stop" + }, + + "idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "󰅶", + "deactivated": "󰾪" + } + }, + + "custom/nightlight": { + "return-type": "json", + "interval": 3, + "exec": "nomarchy-nightlight status", + "on-click": "nomarchy-nightlight toggle" + }, + + "hyprland/language": { + "format": "󰌌 {short}", + "tooltip": false + }, + + "custom/updates": { + "return-type": "json", + "interval": 1800, + "signal": 9, + "exec": "nomarchy-updates status", + "on-click": "sh -c '$TERMINAL -e nomarchy-updates upgrade'" + }, + + "pulseaudio": { + "scroll-step": 5, + "format": "{icon} {volume}%", + "format-bluetooth": "{icon} {volume}%", + "format-bluetooth-muted": "{icon} muted", + "format-muted": " muted", + "format-icons": { + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] + }, + "on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", + "tooltip-format": "{desc} | {volume}%" + }, + + "custom/powerprofile": { + "return-type": "json", + "interval": 5, + "exec": "nomarchy-powerprofile-status", + "on-click": "nomarchy-powerprofile-cycle" + }, + + "battery": { + "interval": 30, + "states": { "warning": 25, "critical": 10 }, + "format": "{icon} {capacity}%", + "format-charging": " {capacity}%", + "format-plugged": " {capacity}%", + "format-icons": ["", "", "", "", ""], + "on-click": "nomarchy-menu power", + "tooltip-format": "Battery status" + }, + + "custom/vpn": { + "return-type": "json", + "interval": 5, + "exec": "nomarchy-vpn-status", + "on-click": "nomarchy-vpn" + }, + + "custom/doctor": { + "return-type": "json", + "interval": 300, + "exec": "nomarchy-doctor-status", + "on-click": "nomarchy-menu doctor" + }, + + "custom/notification": { + "format": "{icon}", + "return-type": "json", + "exec": "swaync-client -swb", + "exec-if": "which swaync-client", + "escape": true, + "tooltip": true, + "on-click": "swaync-client -t -sw", + "on-click-right": "swaync-client -d -sw", + "format-icons": { + "none": "", + "notification": "", + "dnd-none": "", + "dnd-notification": "", + "inhibited-none": "", + "inhibited-notification": "", + "dnd-inhibited-none": "", + "dnd-inhibited-notification": "" + } + }, + + "tray": { "spacing": 8 }, + + "custom/powermenu": { + "format": "", + "on-click": "nomarchy-menu power", + "tooltip": false + } +}