Add full disk access checks and settings
Add background themes and custom images Add shell command logging toggle Add UTF-8 locale guarantee for PTY Add Claude hook settings injection Add hotkey system for GUI Add glass panel styling Add search disabled state for agent panels Add zoom toggle command Add device report filtering Add entitlements for notarization Update version to 0.1.27
This commit is contained in:
+95
-7
@@ -7,6 +7,11 @@ export const COLORS = {
|
||||
bgElevated: "var(--c-bg-elevated)",
|
||||
bgHover: "var(--c-bg-hover)",
|
||||
bgPanel: "var(--c-bg-panel)",
|
||||
panelGlass: "var(--c-panel-glass, var(--c-bg-panel))",
|
||||
panelBlur: "var(--c-panel-blur, none)",
|
||||
appBg: "var(--app-bg, var(--c-bg-app))",
|
||||
elevatedGlass: "var(--c-elevated-glass, var(--c-bg-elevated))",
|
||||
sidebarGlass: "var(--c-sidebar-glass, var(--c-bg-sidebar))",
|
||||
bgSidebar: "var(--c-bg-sidebar)",
|
||||
borderStrong: "var(--c-border-strong)",
|
||||
borderSubtle: "var(--c-border-subtle)",
|
||||
@@ -92,15 +97,98 @@ export const ACCENTS: Record<string, string> = {
|
||||
|
||||
export type ThemeName = "dark" | "light";
|
||||
|
||||
/** Real color values for consumers that can't use var() (xterm). Keys are the kebab tokens plus "accent". */
|
||||
export function resolvePalette(theme: ThemeName, accent: string): Record<string, string> {
|
||||
const base = theme === "light" ? LIGHT : DARK;
|
||||
return { ...base, accent: ACCENTS[accent] ?? ACCENTS.blue };
|
||||
// ---------------------------------------------------------------------------
|
||||
// Background themes (Warp-style full-window fills)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A background theme paints the WHOLE window behind every panel, instead of each
|
||||
* terminal owning an opaque tile. Panels become semi-transparent glass (rgba over
|
||||
* a backdrop blur) so the shared fill shows through uniformly across the grid.
|
||||
*
|
||||
* `css` is the CSS `background` value for the app root: a gradient, or `""` for
|
||||
* the classic solid look ("none"), or the sentinel "custom" handled at apply time
|
||||
* via a user-supplied image. `panelAlpha`/`blur` tune the glass over the fill.
|
||||
*/
|
||||
export interface BackgroundTheme {
|
||||
label: string;
|
||||
css: string; // app-root `background` value ("" = solid bg-app)
|
||||
swatch: string; // gallery preview (gradient/color)
|
||||
panelAlpha: number; // 0..1 — panel glass opacity over the fill
|
||||
blur: number; // px — panel backdrop blur
|
||||
}
|
||||
|
||||
/** Write the active palette to :root as --c-* custom properties. */
|
||||
export function applyTheme(theme: ThemeName, accent: string): void {
|
||||
const p = resolvePalette(theme, accent);
|
||||
export const CUSTOM_BACKGROUND = "custom";
|
||||
|
||||
export const BACKGROUNDS: Record<string, BackgroundTheme> = {
|
||||
none: { label: "None", css: "", swatch: DARK["bg-panel"], panelAlpha: 1, blur: 0 },
|
||||
cyberwave: { label: "Cyber Wave", css: "linear-gradient(135deg,#06121f 0%,#0a2a3f 45%,#10183a 100%)", swatch: "linear-gradient(135deg,#06121f,#0a2a3f,#10183a)", panelAlpha: 0.46, blur: 9 },
|
||||
phenomenon: { label: "Phenomenon", css: "radial-gradient(120% 120% at 80% 0%,#241a2e 0%,#15121d 45%,#0a0910 100%)", swatch: "radial-gradient(120% 120% at 80% 0%,#241a2e,#15121d,#0a0910)", panelAlpha: 0.5, blur: 8 },
|
||||
dracula: { label: "Dracula", css: "linear-gradient(160deg,#282a36 0%,#21222c 60%,#1a1b23 100%)", swatch: "linear-gradient(160deg,#282a36,#21222c,#1a1b23)", panelAlpha: 0.58, blur: 6 },
|
||||
aurora: { label: "Aurora", css: "linear-gradient(135deg,#0b3d2e 0%,#0a2c3a 40%,#241147 100%)", swatch: "linear-gradient(135deg,#0b3d2e,#0a2c3a,#241147)", panelAlpha: 0.44, blur: 10 },
|
||||
ember: { label: "Ember", css: "linear-gradient(135deg,#2a0f12 0%,#3a1410 45%,#160a14 100%)", swatch: "linear-gradient(135deg,#2a0f12,#3a1410,#160a14)", panelAlpha: 0.5, blur: 8 },
|
||||
referred: { label: "Referred", css: "linear-gradient(120deg,#b9c6ff 0%,#cdb6ff 40%,#ffd6e7 100%)", swatch: "linear-gradient(120deg,#b9c6ff,#cdb6ff,#ffd6e7)", panelAlpha: 0.34, blur: 12 },
|
||||
};
|
||||
|
||||
/** Resolve a background name to its theme, falling back to "none". */
|
||||
export function backgroundFor(name: string): BackgroundTheme {
|
||||
return BACKGROUNDS[name] ?? BACKGROUNDS.none;
|
||||
}
|
||||
|
||||
/** Hex (#rrggbb) → rgba() string at the given alpha. */
|
||||
function hexToRgba(hex: string, alpha: number): string {
|
||||
const h = hex.replace("#", "");
|
||||
const r = parseInt(h.slice(0, 2), 16);
|
||||
const g = parseInt(h.slice(2, 4), 16);
|
||||
const b = parseInt(h.slice(4, 6), 16);
|
||||
return `rgba(${r},${g},${b},${alpha})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Real color values for consumers that can't use var() (xterm). Keys are the
|
||||
* kebab tokens plus "accent" and "term-bg". When a background theme is active the
|
||||
* terminal renders on transparent glass, so "term-bg" is fully transparent and
|
||||
* the panel container supplies the visible tint.
|
||||
*/
|
||||
export function resolvePalette(theme: ThemeName, accent: string, background: string = "none"): Record<string, string> {
|
||||
const base = theme === "light" ? LIGHT : DARK;
|
||||
const active = background !== "none";
|
||||
return {
|
||||
...base,
|
||||
accent: ACCENTS[accent] ?? ACCENTS.blue,
|
||||
"term-bg": active ? "rgba(0,0,0,0)" : base["bg-panel"],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the active palette + background fill to :root as --c-* custom properties.
|
||||
* `imageDataUrl` is only consulted when `background === "custom"`.
|
||||
*/
|
||||
export function applyTheme(theme: ThemeName, accent: string, background: string = "none", imageDataUrl: string | null = null): void {
|
||||
const p = resolvePalette(theme, accent, background);
|
||||
const root = document.documentElement.style;
|
||||
for (const [k, v] of Object.entries(p)) root.setProperty(`--c-${k}`, v);
|
||||
|
||||
const bg = backgroundFor(background);
|
||||
const base = theme === "light" ? LIGHT : DARK;
|
||||
const active = background !== "none";
|
||||
|
||||
// App-root fill: custom image (cover) > gradient > solid bg-app.
|
||||
const appBg = background === CUSTOM_BACKGROUND && imageDataUrl
|
||||
? `center / cover no-repeat url("${imageDataUrl}")`
|
||||
: bg.css || base["bg-app"];
|
||||
root.setProperty("--app-bg", appBg);
|
||||
|
||||
// Panel glass: rgba(bg-panel, alpha) over the fill, plus optional backdrop blur.
|
||||
// When inactive this is the solid bg-panel so the classic look is byte-identical.
|
||||
const alpha = active ? (background === CUSTOM_BACKGROUND ? 0.5 : bg.panelAlpha) : 1;
|
||||
const blur = active ? (background === CUSTOM_BACKGROUND ? 8 : bg.blur) : 0;
|
||||
root.setProperty("--c-panel-glass", alpha < 1 ? hexToRgba(base["bg-panel"], alpha) : base["bg-panel"]);
|
||||
root.setProperty("--c-panel-blur", blur > 0 ? `blur(${blur}px)` : "none");
|
||||
|
||||
// Chrome glass (TopBar / toolbar / sidebar / panel headers) — a touch more
|
||||
// opaque than the panels so labels and controls stay legible over the fill.
|
||||
const chromeAlpha = active ? Math.min(alpha + 0.22, 0.92) : 1;
|
||||
root.setProperty("--c-elevated-glass", chromeAlpha < 1 ? hexToRgba(base["bg-elevated"], chromeAlpha) : base["bg-elevated"]);
|
||||
root.setProperty("--c-sidebar-glass", chromeAlpha < 1 ? hexToRgba(base["bg-sidebar"], chromeAlpha) : base["bg-sidebar"]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user