Polish Buzz sidebar theme (#1671)

Signed-off-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
klopez4212
2026-07-09 08:10:24 -07:00
committed by GitHub
co-authored by npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5
parent e0f76b0e9c
commit cdba2a08e1
11 changed files with 364 additions and 60 deletions
+6 -4
View File
@@ -205,7 +205,7 @@ dependencies = [
"objc2-foundation",
"parking_lot",
"percent-encoding",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
"x11rb",
]
@@ -969,6 +969,7 @@ dependencies = [
"toml 0.8.2",
"url",
"uuid",
"window-vibrancy",
"windows-sys 0.61.2",
"zeroize",
"zip 8.6.0",
@@ -1943,7 +1944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
dependencies = [
"data-encoding",
"syn 2.0.118",
"syn 1.0.109",
]
[[package]]
@@ -2833,7 +2834,7 @@ dependencies = [
"libc",
"log",
"rustversion",
"windows-link 0.2.1",
"windows-link 0.1.3",
"windows-result 0.4.1",
]
@@ -7003,7 +7004,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -8780,6 +8781,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
+2 -1
View File
@@ -45,6 +45,7 @@ notify-rust = "4"
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSHapticFeedback"] }
keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "vendored"], optional = true }
security-framework = { version = "3.7.0", features = ["OSX_10_15"] }
window-vibrancy = "0.6"
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = ["Win32_Security", "Win32_Storage_FileSystem", "Win32_System_JobObjects", "Win32_System_Threading", "Win32_Foundation"] }
@@ -54,7 +55,7 @@ keyring = { version = "3.6.3", default-features = false, features = ["windows-na
atomic-write-file = "0.3"
anyhow = "1"
dirs = "6"
tauri = { version = "2", features = [] }
tauri = { version = "2", features = ["macos-private-api"] }
tauri-plugin-deep-link = "2"
tauri-plugin-opener = "2"
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
+2
View File
@@ -39,6 +39,7 @@ mod relay_reconnect;
mod social;
mod teams;
mod updater;
mod window_vibrancy;
mod workflows;
mod workspace;
@@ -79,5 +80,6 @@ pub use relay_reconnect::*;
pub use social::*;
pub use teams::*;
pub use updater::*;
pub use window_vibrancy::*;
pub use workflows::*;
pub use workspace::*;
@@ -0,0 +1,69 @@
//! Runtime macOS window vibrancy (blur-behind) toggle.
//!
//! Vibrancy applies an `NSVisualEffectView` behind the webview so the desktop
//! (and windows behind Buzz) blur through wherever the app's CSS is
//! transparent. It is a native, macOS-only effect: there is no "intensity"
//! setting at the OS level, only a set of material presets. The frontend tunes
//! perceived intensity by changing CSS surface opacity while this command
//! handles the native material.
//!
//! This is fully reversible at runtime: enabling applies the chosen material,
//! disabling clears it. On non-macOS platforms the command is a no-op so the
//! shared frontend can call it unconditionally.
#[cfg(target_os = "macos")]
use tauri::Manager;
/// Apply or clear macOS window vibrancy for the main window.
///
/// `material` accepts the common `NSVisualEffectMaterial` names
/// (`sidebar`, `hud-window`, `under-window-background`, `fullscreen-ui`,
/// `header-view`, `popover`, `menu`, `titlebar`). Unknown values fall back to
/// `sidebar`.
#[tauri::command]
pub fn set_window_vibrancy(
#[allow(unused_variables)] enabled: bool,
#[allow(unused_variables)] material: Option<String>,
#[allow(unused_variables)] app_handle: tauri::AppHandle,
) -> Result<(), String> {
#[cfg(target_os = "macos")]
{
use window_vibrancy::{apply_vibrancy, clear_vibrancy, NSVisualEffectMaterial};
let window = app_handle
.get_webview_window("main")
.ok_or_else(|| "main window not found".to_string())?;
if !enabled {
clear_vibrancy(&window).map_err(|e| e.to_string())?;
return Ok(());
}
let material = match material.as_deref() {
Some("hud-window") => NSVisualEffectMaterial::HudWindow,
Some("under-window-background") => NSVisualEffectMaterial::UnderWindowBackground,
Some("fullscreen-ui") => NSVisualEffectMaterial::FullScreenUI,
Some("header-view") => NSVisualEffectMaterial::HeaderView,
Some("popover") => NSVisualEffectMaterial::Popover,
Some("menu") => NSVisualEffectMaterial::Menu,
Some("titlebar") => NSVisualEffectMaterial::Titlebar,
_ => NSVisualEffectMaterial::Sidebar,
};
// `apply_vibrancy` appends a new tagged `NSVisualEffectView` each call,
// while `clear_vibrancy` only removes one. Repeated enables (theme
// switches, follow-system flips) would otherwise stack blur views and
// leave a stale one behind on the next non-Buzz theme. Clear any
// existing view first so exactly one material is ever installed. The
// clear is a no-op (returns `false`) when none is present.
let _ = clear_vibrancy(&window);
apply_vibrancy(&window, material, None, None).map_err(|e| e.to_string())?;
Ok(())
}
#[cfg(not(target_os = "macos"))]
{
Ok(())
}
}
+1
View File
@@ -642,6 +642,7 @@ pub fn run() {
archive::index_observer_channel_id,
archive::read_unindexed_observer_rows,
is_auto_update_supported,
set_window_vibrancy,
])
.build(tauri::generate_context!())
.expect("error while building tauri application");
+2 -1
View File
@@ -21,7 +21,7 @@
"height": 600,
"maximized": true,
"visible": true,
"backgroundColor": "#000000",
"transparent": true,
"titleBarStyle": "Overlay",
"hiddenTitle": true,
"dragDropEnabled": false,
@@ -34,6 +34,7 @@
"minHeight": 500
}
],
"macOSPrivateApi": true,
"security": {
"csp": null
}
+1
View File
@@ -860,6 +860,7 @@ export function AppShell() {
<SidebarInset
ref={mainInsetRef}
className="isolate min-h-0 min-w-0 overflow-hidden bg-sidebar"
data-buzz-glass-inset
style={chromeCssVarDefaults as React.CSSProperties}
>
<div className="relative z-10 mb-2 ml-px mr-2 mt-px flex min-h-0 flex-1 flex-col overflow-hidden rounded-2xl bg-background shadow-[-1px_-1px_0_0_hsl(var(--sidebar-border)/0.45)]">
@@ -829,7 +829,7 @@ export function TopbarSearch({
<>
<span
className={cn(
"min-w-0 flex-1 translate-y-px truncate transition-colors duration-150 ease-out",
"min-w-0 flex-1 truncate transition-colors duration-150 ease-out",
query
? "text-sidebar-foreground"
: "text-sidebar-foreground/55",
@@ -803,7 +803,7 @@ export function AppSidebar({
</SidebarContent>
</div>
<div className="relative z-30 shrink-0">
<div className="relative z-30 shrink-0" data-buzz-glass-footer-wrap>
{unreadBelowCount > 0 ? (
<MoreUnreadButton
bottomClassName="bottom-full"
+230 -50
View File
@@ -165,7 +165,7 @@
}
/*
* Buzz theme sidebar gradient.
* Buzz theme - sidebar gradient.
*
* Buzz reuses the GitHub Light palette for every base color; its one
* distinguishing feature is this gradient painted across the sidebar/nav
@@ -176,16 +176,14 @@
* lives in Tailwind's `utilities` layer, and unlayered declarations win over
* any layered ones, so this reliably overrides it without an `!important`.
*
* The gradient is applied to every `bg-sidebar` canvas surface INSIDE the app
* shell (`.group\/sidebar-wrapper` the top chrome bar, the sidebar column,
* and the inset margin behind the white content card), PLUS two chrome
* surfaces rendered OUTSIDE that wrapper: the portaled mobile sidebar (the
* `SheetContent` rendered by the offcanvas branch on narrow viewports, marked
* `[data-sidebar="sidebar"][data-mobile="true"]`) and the workspace rail (the
* Discord-style relay column on the far left, `[data-testid="workspace-rail"]`,
* a sibling of the wrapper gated behind the `workspaceRail` feature flag).
* Both live outside `.group\/sidebar-wrapper`, so each needs its own precise
* selector rather than re-broadening to every portal.
* The gradient is applied to the shell wrapper plus every `bg-sidebar` canvas
* surface inside it (top chrome, sidebar column, and the inset margin behind
* the white content card). A few panes that do not literally carry
* `bg-sidebar` are marked explicitly (`data-buzz-glass-inset` /
* `data-buzz-glass-footer-wrap`) so they join the same visual layer. Two
* chrome surfaces rendered outside the wrapper also get precise selectors: the
* portaled mobile sidebar (`[data-sidebar="sidebar"][data-mobile="true"]`) and
* the workspace rail (`[data-testid="workspace-rail"]`).
* Scoping this way keeps the branding on the app chrome and off unrelated
* `bg-sidebar` consumers rendered in portals outside the shell (e.g. the
* persona catalog dialog). `background-attachment: fixed` anchors the gradient
@@ -194,7 +192,7 @@
* grey `--sidebar-background` never shows through.
*
* Small transient `bg-sidebar` chips inside the shell that are NOT canvas
* surfaces (the drag-overlay pills) opt out via `[data-buzz-flat]` a
* surfaces (the drag-overlay pills) opt out via `[data-buzz-flat]` - a
* viewport-fixed gradient behind a small floating chip would show an arbitrary
* slice of the ramp.
*
@@ -204,6 +202,18 @@
:root[data-buzz-sidebar] {
--buzz-gradient-top: #e6e6b6;
--buzz-gradient-bottom: #c4d0da;
--buzz-channel-fg: inherit;
--buzz-dm-fg: inherit;
--buzz-nav-fg: inherit;
--buzz-hover-fill: #ffffff;
--buzz-hover-alpha: 48%;
--buzz-hover-surface: color-mix(
in srgb,
var(--buzz-hover-fill) var(--buzz-hover-alpha),
transparent
);
--buzz-active-fill: 0 0% 100%;
--buzz-active-foreground: var(--foreground);
}
/*
@@ -215,8 +225,21 @@
:root[data-buzz-sidebar].dark {
--buzz-gradient-top: #2b2b18;
--buzz-gradient-bottom: #1b2530;
--buzz-hover-alpha: 9.6%;
--buzz-active-fill: 0 0% 100%;
--buzz-active-foreground: 0 0% 100%;
}
:root[data-buzz-sidebar]
[data-testid="app-sidebar"]
[data-testid="sidebar-pinned-header"] {
padding-top: 0.75rem;
}
:root[data-buzz-sidebar] .group\/sidebar-wrapper,
:root[data-buzz-sidebar] [data-testid="app-sidebar"],
:root[data-buzz-sidebar] [data-buzz-glass-inset],
:root[data-buzz-sidebar] [data-buzz-glass-footer-wrap],
:root[data-buzz-sidebar]
.group\/sidebar-wrapper
.bg-sidebar:not([data-buzz-flat]),
@@ -267,35 +290,18 @@
background: none;
}
/*
* Shared translucent-white fill for sidebar hover states and the search box.
* Only the sidebar rules below consume it, but it is safe at :root and other
* Buzz sidebar rules reference it, so it stays here.
*/
:root[data-buzz-sidebar] {
--buzz-hover-surface: rgb(255 255 255 / 0.6);
}
/*
* Dark variant: 60%-white fills are too harsh on the dark gradient, so use a
* lower-opacity white for hover / search surfaces.
*/
:root[data-buzz-sidebar].dark {
--buzz-hover-surface: rgb(255 255 255 / 0.12);
}
/*
* Active nav item. The accent system (`applyAccentColor` in ThemeProvider)
* sets `--sidebar-active` to the theme foreground as an INLINE style on
* :root, which renders the selected page as a solid black pill under GitHub
* Light. For the Buzz theme we want a white pill instead. Override the two
* active tokens `!important` is required because an important stylesheet
* declaration is what beats the non-important inline style set by the accent
* system. Text flips to the theme foreground so it stays legible on the white
* pill; the derived `bg-sidebar-active-foreground/20` badge on active items
* follows automatically.
* active tokens on the sidebar element itself; element-local custom properties
* win over the inherited inline values from :root. Text flips to the theme
* foreground so it stays legible on the white pill; the derived
* `bg-sidebar-active-foreground/20` badge on active items follows
* automatically.
*
* SCOPED to the app sidebar container, NOT :root the `bg-sidebar-active` /
* SCOPED to the app sidebar container, NOT :root - the `bg-sidebar-active` /
* `text-sidebar-active-foreground` tokens are also consumed by non-sidebar
* controls (avatar edit buttons in ProfileSettingsCard / AgentCreationPreview,
* the selected persona row in PersonaCatalogDialog). A root-level override
@@ -303,8 +309,8 @@
* the normal accent-driven active colors.
*/
:root[data-buzz-sidebar] [data-testid="app-sidebar"] {
--sidebar-active: 0 0% 100% !important;
--sidebar-active-foreground: var(--foreground) !important;
--sidebar-active: var(--buzz-active-fill);
--sidebar-active-foreground: var(--buzz-active-foreground);
}
/*
@@ -312,17 +318,8 @@
* (The pill itself is repainted to a translucent tint by the rule below.)
*/
:root[data-buzz-sidebar].dark [data-testid="app-sidebar"] {
--sidebar-active: 0 0% 100% !important;
--sidebar-active-foreground: 0 0% 100% !important;
}
/*
* On dark the active pill is a translucent white tint rather than a solid
* fill, so it sits on the gradient without blowing out. The `!important`
* background overrides the `bg-sidebar-active` utility for this state only.
*/
:root[data-buzz-sidebar].dark [data-testid="app-sidebar"] [data-active="true"] {
background-color: rgb(255 255 255 / 0.16) !important;
--sidebar-active: var(--buzz-active-fill);
--sidebar-active-foreground: var(--buzz-active-foreground);
}
/*
@@ -334,10 +331,22 @@
box-shadow: none;
}
/*
* On dark the active pill is a translucent white tint rather than a solid
* fill, so it sits on the gradient without blowing out.
*/
:root[data-buzz-sidebar].dark [data-testid="app-sidebar"] [data-active="true"] {
background-color: color-mix(
in srgb,
hsl(var(--buzz-active-fill)) 16%,
transparent
);
}
/*
* Hover on non-active nav items uses a translucent white fill (see
* `--buzz-hover-surface`) instead of the grey `--sidebar-accent`. Active
* items are excluded they stay solid on hover (their
* items are excluded - they stay solid on hover (their
* `data-[active=true]:hover:bg-sidebar-active` rule). Covers both top-level
* menu buttons and channel sub-buttons.
*/
@@ -368,7 +377,7 @@
}
/*
* Section action buttons (the "+" and "" at the right edge of each sidebar
* Section action buttons (the "+" and "..." at the right edge of each sidebar
* section header): match the same translucent white fill (see
* `--buzz-hover-surface`) on hover/focus instead of the grey
* `bg-sidebar-border/35` fill. Unlayered + `[data-buzz-sidebar]` prefix beats
@@ -382,3 +391,174 @@
.sidebar-section-action:focus-visible {
background-color: var(--buzz-hover-surface);
}
/*
* Per-row-type text colors (Buzz theme).
*
* By default the whole sidebar inherits a single `--sidebar-foreground`, so
* channels, direct messages, and the Inbox/Agents nav rows all share one text
* color. These variables let each row type carry its own foreground while
* keeping the default value unchanged.
*
* Each var defaults to `inherit`, so with nothing set the sidebar looks
* exactly as before. Scoped to the Buzz sidebar container so other themes are
* untouched.
*/
/* Channels: rows live inside the `stream-list` / `starred-list` menus. */
:root[data-buzz-sidebar]
[data-testid="app-sidebar"]
[data-testid="stream-list"]
[data-sidebar="menu-button"]:not([data-active="true"]),
:root[data-buzz-sidebar]
[data-testid="app-sidebar"]
[data-testid="stream-list"]
[data-sidebar="menu-sub-button"]:not([data-active="true"]),
:root[data-buzz-sidebar]
[data-testid="app-sidebar"]
[data-testid="starred-list"]
[data-sidebar="menu-button"]:not([data-active="true"]),
:root[data-buzz-sidebar]
[data-testid="app-sidebar"]
[data-testid="starred-list"]
[data-sidebar="menu-sub-button"]:not([data-active="true"]) {
color: var(--buzz-channel-fg);
}
/* Direct messages: rows live inside the `dm-list` menu. */
:root[data-buzz-sidebar]
[data-testid="app-sidebar"]
[data-testid="dm-list"]
[data-sidebar="menu-button"]:not([data-active="true"]),
:root[data-buzz-sidebar]
[data-testid="app-sidebar"]
[data-testid="dm-list"]
[data-sidebar="menu-sub-button"]:not([data-active="true"]) {
color: var(--buzz-dm-fg);
}
/*
* Inbox + Agents nav rows: the pinned-header nav buttons (Inbox, Pulse,
* Projects, Agents, Workflows). Excludes the active row so the active-pill
* foreground still wins.
*/
:root[data-buzz-sidebar]
[data-testid="app-sidebar"]
[data-testid="sidebar-pinned-header"]
[data-sidebar="menu-button"]:not([data-active="true"]) {
color: var(--buzz-nav-fg);
}
/*
* Translucency (Buzz theme).
*
* ThemeProvider toggles `data-buzz-translucent` for Buzz themes. Keep the
* frosted layer on the outer canvases only; nested sidebar/header/footer
* surfaces pass through so the nav reads as one continuous pane.
*/
:root[data-buzz-translucent] {
/* Neutral frosted wash; alpha applied via color-mix below. */
--buzz-glass-tint: #ffffff;
--buzz-glass-intensity: 1;
--buzz-translucency-gradient-alpha: 70%;
--buzz-translucency-wash-alpha: 2.4%;
}
:root[data-buzz-translucent].dark {
--buzz-glass-tint: #12161d;
}
:root[data-buzz-translucent] .group\/sidebar-wrapper,
:root[data-buzz-translucent]
[data-sidebar="sidebar"][data-mobile="true"].bg-sidebar,
:root[data-buzz-translucent] [data-testid="workspace-rail"].bg-sidebar {
background-color: transparent;
/* Buzz color wash above a neutral frost wash. */
background-image:
linear-gradient(
to bottom,
color-mix(
in srgb,
var(--buzz-gradient-top) var(--buzz-translucency-gradient-alpha, 70%),
transparent
),
color-mix(
in srgb,
var(--buzz-gradient-bottom) var(--buzz-translucency-gradient-alpha, 70%),
transparent
)
),
linear-gradient(
to bottom,
color-mix(
in srgb,
var(--buzz-glass-tint) var(--buzz-translucency-wash-alpha, 2.4%),
transparent
),
color-mix(
in srgb,
var(--buzz-glass-tint) var(--buzz-translucency-wash-alpha, 2.4%),
transparent
)
);
background-attachment: fixed, fixed;
background-size:
100vw 100vh,
100vw 100vh;
background-repeat: no-repeat, no-repeat;
}
:root[data-buzz-translucent] [data-testid="app-sidebar"],
:root[data-buzz-translucent] [data-buzz-glass-inset],
:root[data-buzz-translucent] [data-buzz-glass-footer-wrap],
:root[data-buzz-translucent]
.group\/sidebar-wrapper
.bg-sidebar:not([data-buzz-flat]),
:root[data-buzz-translucent]
[data-testid="app-sidebar"]
[data-testid="sidebar-pinned-header"],
:root[data-buzz-translucent]
[data-testid="app-sidebar"]
[data-sidebar="footer"] {
background-color: transparent;
background-image: none;
}
/* Remove edge fades and stacking contexts that made header/footer look separate. */
:root[data-buzz-translucent]
[data-testid="app-sidebar"]
[data-testid="sidebar-pinned-header"],
:root[data-buzz-translucent] [data-buzz-glass-inset],
:root[data-buzz-translucent]
[data-testid="app-sidebar"]
[data-buzz-glass-footer-wrap],
:root[data-buzz-translucent]
[data-testid="app-sidebar"]
[data-sidebar="footer"] {
isolation: auto;
z-index: auto;
}
:root[data-buzz-translucent]
[data-testid="app-sidebar"]
[data-testid="sidebar-pinned-header"]::before,
:root[data-buzz-translucent]
[data-testid="app-sidebar"]
[data-sidebar="footer"]::before {
background: none;
}
/* Let the native transparent window show through behind the sidebar glass. */
:root[data-buzz-translucent],
:root[data-buzz-translucent] body {
background-color: transparent;
background-image: none;
}
/* Shell wrappers stay clear; the main content card remains opaque. */
:root[data-buzz-translucent] .buzz-huddle-shell,
:root[data-buzz-translucent] .buzz-huddle-app-surface {
background: transparent;
}
:root[data-buzz-translucent] .buzz-huddle-app-surface::after {
background: transparent;
}
+49 -2
View File
@@ -7,6 +7,9 @@ import {
useRef,
useState,
} from "react";
import { isTauri } from "@tauri-apps/api/core";
import { invokeTauri } from "@/shared/api/tauri";
import { isMacPlatform } from "@/shared/lib/platform";
import { createThemeVars, hexToHsl } from "./adaptive-theme";
import {
SYNTAX_THEMES,
@@ -26,6 +29,7 @@ const VIDEO_REVIEW_NEUTRAL_ACCENT = "0 0% 98%";
const VIDEO_REVIEW_CHIP_SURFACE = "#161616";
const VIDEO_REVIEW_TEXT_CONTRAST = 4.5;
const VIDEO_REVIEW_CHIP_BACKGROUND_ALPHAS = [0.15, 0.3] as const;
const BUZZ_VIBRANCY_MATERIAL = "sidebar";
export const ACCENT_COLORS = [
{ name: "Neutral", value: NEUTRAL_ACCENT },
@@ -206,13 +210,51 @@ function applyAccentColor(value: string) {
root.style.setProperty("--sidebar-active-foreground", fgHsl);
}
/** Toggle the Buzz sidebar-gradient marker on the document root. */
function isBuzzTheme(themeName: string): boolean {
return themeName === "buzz" || themeName === "buzz-dark";
}
/** Toggle the Buzz sidebar-gradient and translucency markers on the root. */
function applyBuzzSidebar(themeName: string) {
const root = document.documentElement;
if (themeName === "buzz" || themeName === "buzz-dark") {
if (isBuzzTheme(themeName)) {
root.setAttribute("data-buzz-sidebar", "");
// The translucent treatment (transparent root/body + semi-transparent
// sidebar gradient) relies on the native macOS `NSVisualEffectView`
// vibrancy layer painting behind the webview. On Windows/Linux
// `set_window_vibrancy` is a no-op, but the window is transparent
// globally (tauri.conf.json), so a transparent root would show raw
// desktop content through the UI. Gate the translucent marker and
// transparent root background to macOS; other platforms fall back to the
// opaque Buzz gradient (`data-buzz-sidebar` paints solid colors) with the
// normal `bg-background` body fill.
if (isMacPlatform()) {
root.setAttribute("data-buzz-translucent", "");
root.style.setProperty("background-color", "transparent");
root.style.setProperty("background-image", "none");
} else {
root.removeAttribute("data-buzz-translucent");
root.style.removeProperty("background-color");
root.style.removeProperty("background-image");
}
} else {
root.removeAttribute("data-buzz-sidebar");
root.removeAttribute("data-buzz-translucent");
root.style.removeProperty("background-color");
root.style.removeProperty("background-image");
}
}
async function applyBuzzVibrancy(themeName: string) {
if (!isTauri()) return;
try {
await invokeTauri<void>("set_window_vibrancy", {
enabled: isBuzzTheme(themeName),
material: BUZZ_VIBRANCY_MATERIAL,
});
} catch (error) {
console.warn("set_window_vibrancy failed", error);
}
}
@@ -328,6 +370,11 @@ export function ThemeProvider({
});
}, [effectiveTheme]);
useEffect(() => {
if (!isValidThemeName(effectiveTheme)) return;
void applyBuzzVibrancy(effectiveTheme);
}, [effectiveTheme]);
// Listen for system color scheme changes when followSystem is enabled
useEffect(() => {
if (!followSystem) return;