FN-7401: add Glass Silver dashboard theme

Adds a frosted silver/gray dashboard color theme across app, desktop, and release metadata.

- Register glass-silver in shared theme types, selector options, and startup validation hooks.
- Add Glass Silver CSS tokens, swatch styling, and dashboard/desktop stylesheet preload coverage.
- Cover theme registration, dropdown/selector rendering, theme persistence, and CSS token invariants with tests.
- Document the new dashboard theme and publish a minor changeset for @runfusion/fusion.

Files changed:
 .changeset/fn-7401-glass-silver-theme.md           |   7 +
 docs/dashboard-guide.md                            |   3 +-
 packages/core/src/types.ts                         |   2 +
 .../app/__tests__/glass-silver-theme.test.ts       | 155 ++++++++++++++++++++
 .../dashboard/app/components/ThemeSelector.css     |  14 ++
 .../components/__tests__/ThemeDropdown.test.tsx    |  13 ++
 .../components/__tests__/ThemeSelector.test.tsx    |  16 +++
 packages/dashboard/app/components/themeOptions.ts  |   1 +
 .../dashboard/app/hooks/__tests__/useTheme.test.ts |  15 ++
 packages/dashboard/app/index.html                  |   2 +-
 packages/dashboard/app/public/theme-data.css       | 156 +++++++++++++++++++++
 packages/desktop/src/renderer/index.html           |   1 +
 12 files changed, 383 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7401

Fusion-Task-Lineage: e64f14cc-590e-458b-adf6-7c9baa488157

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-01 18:01:17 -07:00
parent 615431b1fb
commit a4049971e5
12 changed files with 383 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": minor
---
summary: Add a Glass Silver dashboard theme.
category: feature
dev: Registers glass-silver across core theme metadata, dashboard/desktop startup validators, CSS tokens, swatches, and tests.

View File

@@ -1584,8 +1584,9 @@ Non-Command-Center dashboard CSS uses `--text` as the canonical primary text tok
### Theme system
<!-- FNXC:DashboardTheming 2026-06-21-00:00: FN-6840 synced the user-facing theme docs to the shipped expanded Shadcn family, the Shadcn Custom color-picker preset, and the sidebar accent behavior that follows each theme's --accent token. -->
<!-- FNXC:DashboardTheming 2026-07-01-00:00: Glass Silver is the silver/gray frosted sibling of Glass and is selectable anywhere color themes are listed, so keep the inventory count and theme description in this guide aligned with COLOR_THEMES. -->
Dark/light modes via `data-theme`; 75 color themes via `data-color-theme` (lazy-loaded from `app/public/theme-data.css`), including the Shadcn zinc-neutral theme with an orange default highlight/accent, Shadcn Custom (the same base with sanitized per-token color-picker overrides), and its color family: Shadcn Blue/Green/Red/Purple/Pink/Orange/Yellow, Shadcn Mono Red/Blue/Green/Purple/Pink/Orange/Yellow (grayscale surfaces with color-specific accents; legacy `shadcn-mono` selections migrate to Shadcn Mono Red), Shadcn Black (pure black and white), Shadcn Gray (fully neutral zinc-gray accent), and Shadcn Gray Blue (blue-gray slate neutral surfaces with a muted slate-blue accent). Air is the minimal, borderless, paper-like preset with near-monochrome tokens and CSS-only chrome flattening.
Dark/light modes via `data-theme`; 76 color themes via `data-color-theme` (lazy-loaded from `app/public/theme-data.css`), including the Shadcn zinc-neutral theme with an orange default highlight/accent, Shadcn Custom (the same base with sanitized per-token color-picker overrides), and its color family: Shadcn Blue/Green/Red/Purple/Pink/Orange/Yellow, Shadcn Mono Red/Blue/Green/Purple/Pink/Orange/Yellow (grayscale surfaces with color-specific accents; legacy `shadcn-mono` selections migrate to Shadcn Mono Red), Shadcn Black (pure black and white), Shadcn Gray (fully neutral zinc-gray accent), and Shadcn Gray Blue (blue-gray slate neutral surfaces with a muted slate-blue accent). Air is the minimal, borderless, paper-like preset with near-monochrome tokens and CSS-only chrome flattening. Glass Silver preserves the Glass theme's frosted translucent surfaces and transparent modal overlay behavior while using silver and graphite accents instead of purple/pink.
Choose Shadcn variants from **Settings → Appearance** or from the Command Center **Overview** theme card; both selectors use the same `themeOptions.ts` labels and color-chip swatches. The left sidebar active-item highlight and resize accent use the active theme's `--accent`, so they follow the selected Shadcn accent instead of staying fixed blue.

View File

@@ -313,6 +313,8 @@ export const COLOR_THEMES = [
"parchment",
"terminal",
"glass",
// FNXC:DashboardTheming 2026-07-01-00:00: Glass Silver is the silver/gray frosted sibling of Glass; keep this id in lockstep with dashboard/desktop validators and selector metadata so persisted explicit choices survive startup.
"glass-silver",
"horizon",
"vitesse",
"outrun",

View File

@@ -0,0 +1,155 @@
import { readFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { COLOR_THEMES as CORE_COLOR_THEMES } from "@fusion/core";
import { COLOR_THEMES as DASHBOARD_COLOR_THEMES } from "../components/themeOptions";
const themeDataPath = path.resolve(__dirname, "../public/theme-data.css");
const dashboardIndexPath = path.resolve(__dirname, "../index.html");
const desktopIndexPath = path.resolve(__dirname, "../../../desktop/src/renderer/index.html");
/*
FNXC:DashboardTheming 2026-07-01-00:00:
Glass Silver is a first-class color theme, so this contract keeps the core union, selector metadata, dashboard/desktop pre-hydration validators, and frosted CSS blocks synchronized.
*/
describe("Glass Silver color theme", () => {
const themeData = readFileSync(themeDataPath, "utf-8");
const dashboardIndexHtml = readFileSync(dashboardIndexPath, "utf-8");
const desktopIndexHtml = readFileSync(desktopIndexPath, "utf-8");
it("registers the theme in core, dashboard metadata, and both bootstrap validators without duplicates", () => {
expect(CORE_COLOR_THEMES).toContain("glass-silver");
expect(DASHBOARD_COLOR_THEMES).toContainEqual({
value: "glass-silver",
label: "Glass Silver",
className: "theme-swatch-glass-silver",
});
expect(DASHBOARD_COLOR_THEMES.map((theme) => theme.value)).toEqual([...CORE_COLOR_THEMES]);
const coreIds = [...CORE_COLOR_THEMES];
expect(new Set(coreIds).size).toBe(coreIds.length);
const dashboardValidThemes = extractValidThemes(dashboardIndexHtml);
const desktopValidThemes = extractValidThemes(desktopIndexHtml);
expect(dashboardValidThemes).toEqual(coreIds);
expect(desktopValidThemes).toEqual(coreIds);
expect(new Set(dashboardValidThemes).size).toBe(dashboardValidThemes.length);
expect(new Set(desktopValidThemes).size).toBe(desktopValidThemes.length);
expect(dashboardIndexHtml).toContain("colorTheme = 'shadcn-ember'");
expect(desktopIndexHtml).toContain('colorTheme = "shadcn-ember"');
});
it("defines dark and light frosted silver/gray token blocks", () => {
const darkBlock = extractSelectorBlock(themeData, '[data-color-theme="glass-silver"]');
const lightBlock = extractSelectorBlock(themeData, '[data-color-theme="glass-silver"][data-theme="light"]');
const combined = `${darkBlock}\n${lightBlock}`;
for (const block of [darkBlock, lightBlock]) {
expect(block).toContain("--surface-hover:");
expect(block).toContain("--surface: color-mix(in srgb,");
expect(block).toContain("--card: color-mix(in srgb,");
expect(block).toContain("transparent);");
expect(block).toContain("--cta-bg:");
expect(block).toContain("--cta-border:");
expect(block).toContain("--cta-text:");
expect(block).toContain("--accent:");
expect(block).toContain("--accent-text:");
expect(block).toContain("--color-info:");
expect(block).toContain("--shadow-glow:");
expect(block).toContain("--focus-ring:");
}
expect(darkBlock).toContain("--font-primary:");
expect(darkBlock).toContain("--radius:");
expect(darkBlock).toContain("--bg: #101216;");
expect(darkBlock).toContain("--accent: #d7dde6;");
expect(darkBlock).toContain("--accent-text: #111318;");
expect(lightBlock).toContain("--bg: #eef0f3;");
expect(lightBlock).toContain("--accent: #5f6875;");
expect(lightBlock).toContain("--accent-text: #ffffff;");
expect(combined).not.toContain("#c86bff");
expect(combined).not.toContain("#ff7aa8");
expect(combined).not.toContain("#9d40cf");
expect(combined).not.toContain("#c74b7a");
});
it("mirrors Glass frosted component overrides and transparent modal overlays", () => {
const cardBlock = extractGroupedRuleBlock(themeData, '[data-color-theme="glass-silver"] .card,');
const lightCardBlock = extractGroupedRuleBlock(themeData, '[data-color-theme="glass-silver"][data-theme="light"] .card,');
const buttonBlock = extractSelectorBlock(themeData, '[data-color-theme="glass-silver"] .btn');
const primaryBlock = extractGroupedRuleBlock(themeData, '[data-color-theme="glass-silver"] .btn-primary,');
const hoverBlock = extractGroupedRuleBlock(themeData, '[data-color-theme="glass-silver"] .btn-primary:hover,');
const lightButtonBlock = extractSelectorBlock(themeData, '[data-color-theme="glass-silver"][data-theme="light"] .btn');
const overlayBlock = extractSelectorBlock(themeData, '[data-color-theme="glass-silver"] .modal-overlay');
for (const block of [cardBlock, buttonBlock]) {
expect(block).toContain("backdrop-filter: blur(");
expect(block).toContain("-webkit-backdrop-filter: blur(");
expect(block).toContain("color-mix(in srgb,");
expect(block).toContain("transparent);");
}
expect(lightCardBlock).toContain("color-mix(in srgb,");
expect(lightCardBlock).toContain("transparent);");
expect(primaryBlock).toContain("linear-gradient(135deg");
expect(primaryBlock).toContain("#d7dde6");
expect(hoverBlock).toContain("linear-gradient(135deg");
expect(hoverBlock).toContain("box-shadow:");
expect(lightButtonBlock).toContain("color-mix(in srgb, #ffffff 52%, transparent)");
expect(overlayBlock).toContain("background: transparent;");
expect(overlayBlock).toContain("backdrop-filter: none;");
expect(overlayBlock).toContain("-webkit-backdrop-filter: none;");
expect(overlayBlock).not.toContain("blur(");
});
});
function extractValidThemes(html: string): string[] {
const match = html.match(/var validThemes = \[([\s\S]*?)\];/);
if (!match) {
throw new Error("Could not find pre-hydration validThemes array");
}
return [...match[1].matchAll(/["']([^"']+)["']/g)].map((themeMatch) => themeMatch[1]);
}
function extractSelectorBlock(css: string, selector: string): string {
const startIdx = css.indexOf(`${selector} {`);
if (startIdx === -1) {
throw new Error(`Could not find selector block: ${selector}`);
}
const openBraceIdx = css.indexOf("{", startIdx);
let depth = 1;
let end = openBraceIdx;
for (let i = openBraceIdx + 1; i < css.length; i++) {
if (css[i] === "{") depth++;
if (css[i] === "}") depth--;
if (depth === 0) {
end = i;
break;
}
}
return css.slice(startIdx, end + 1);
}
function extractGroupedRuleBlock(css: string, selector: string): string {
const selectorIdx = css.indexOf(selector);
if (selectorIdx === -1) {
throw new Error(`Could not find selector in grouped block: ${selector}`);
}
const openBraceIdx = css.indexOf("{", selectorIdx);
let depth = 1;
let end = openBraceIdx;
for (let i = openBraceIdx + 1; i < css.length; i++) {
if (css[i] === "{") depth++;
if (css[i] === "}") depth--;
if (depth === 0) {
end = i;
break;
}
}
const priorCloseIdx = css.lastIndexOf("}", selectorIdx);
return css.slice(priorCloseIdx + 1, end + 1);
}

View File

@@ -665,6 +665,13 @@
--swatch-sample-4: #ff7aa8;
}
.theme-swatch-glass-silver {
--swatch-sample-1: #101216;
--swatch-sample-2: color-mix(in srgb, #252a32 78%, transparent);
--swatch-sample-3: #d7dde6;
--swatch-sample-4: #9ca6b4;
}
.theme-swatch-horizon {
--swatch-sample-1: #1c1e26;
--swatch-sample-2: #16161c;
@@ -1024,6 +1031,13 @@
--swatch-sample-4: #c74b7a;
}
[data-theme="light"] .theme-swatch-glass-silver {
--swatch-sample-1: #eef0f3;
--swatch-sample-2: color-mix(in srgb, #ffffff 75%, transparent);
--swatch-sample-3: #5f6875;
--swatch-sample-4: #747f8c;
}
[data-theme="light"] .theme-swatch-slate {
--swatch-sample-1: #f1f5f9;
--swatch-sample-2: #e2e8f0;

View File

@@ -41,6 +41,19 @@ describe("ThemeDropdown", () => {
expect(defaultOptions[0]).toHaveTextContent("Shadcn Ember (Default)");
});
it("renders Glass Silver as a non-empty compact dropdown option", () => {
render(<ThemeDropdown colorTheme="glass-silver" onColorThemeChange={vi.fn()} />);
const trigger = screen.getByRole("button", { name: /glass silver/i });
expect(trigger).toHaveTextContent("Glass Silver");
expect(trigger.querySelector(".theme-swatch-glass-silver")).toBeTruthy();
fireEvent.click(trigger);
const glassSilverOption = screen.getByRole("option", { name: /glass silver/i });
expect(glassSilverOption).toHaveTextContent("Glass Silver");
expect(glassSilverOption.querySelector(".theme-swatch-glass-silver")).toBeTruthy();
});
it("selects themes and closes from click, escape, and outside click", () => {
const onColorThemeChange = vi.fn();
render(<ThemeDropdown colorTheme="default" onColorThemeChange={onColorThemeChange} />);

View File

@@ -132,6 +132,22 @@ describe("ThemeSelector", () => {
expect(THEME_OPTIONS.map((theme) => theme.value)).toEqual([...COLOR_THEMES]);
});
it("renders the Glass Silver affordance with a non-empty label and swatch", () => {
render(
<ThemeSelector
themeMode="dark"
colorTheme="glass-silver"
onThemeModeChange={vi.fn()}
onColorThemeChange={vi.fn()}
/>
);
const option = screen.getByLabelText("Glass Silver theme");
expect(option).toHaveTextContent("Glass Silver");
expect(option.querySelector(".theme-swatch-glass-silver")).toBeTruthy();
expect(option.getAttribute("aria-pressed")).toBe("true");
});
it("marks current color theme as active", () => {
render(
<ThemeSelector

View File

@@ -52,6 +52,7 @@ export const COLOR_THEMES: { value: ColorTheme; label: string; className: string
{ value: "parchment", label: "Parchment", className: "theme-swatch-parchment" },
{ value: "terminal", label: "Terminal", className: "theme-swatch-terminal" },
{ value: "glass", label: "Glass", className: "theme-swatch-glass" },
{ value: "glass-silver", label: "Glass Silver", className: "theme-swatch-glass-silver" },
{ value: "horizon", label: "Horizon", className: "theme-swatch-horizon" },
{ value: "vitesse", label: "Vitesse", className: "theme-swatch-vitesse" },
{ value: "outrun", label: "Outrun", className: "theme-swatch-outrun" },

View File

@@ -772,6 +772,21 @@ describe("useTheme", () => {
expect(document.documentElement.getAttribute("data-color-theme")).toBe("shadcn-mono-red");
});
it("preserves explicit Glass and Glass Silver color themes from localStorage", () => {
localStorageMock[COLOR_THEME_STORAGE_KEY] = "glass-silver";
const { result, rerender } = renderHook(() => useTheme());
expect(result.current.colorTheme).toBe("glass-silver");
expect(document.documentElement.getAttribute("data-color-theme")).toBe("glass-silver");
act(() => result.current.setColorTheme("glass"));
rerender();
expect(result.current.colorTheme).toBe("glass");
expect(document.documentElement.getAttribute("data-color-theme")).toBe("glass");
});
it("ignores invalid color theme in localStorage", () => {
localStorageMock[COLOR_THEME_STORAGE_KEY] = "invalid-theme";

View File

@@ -160,7 +160,7 @@
try {
var mode = localStorage.getItem('kb-dashboard-theme-mode') || 'dark';
var colorTheme = localStorage.getItem('kb-dashboard-color-theme') || 'shadcn-ember';
var validThemes = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'];
var validThemes = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'];
// FNXC:DashboardTheming 2026-06-30-00:00: Unset startup theme is Shadcn Ember; explicit stored legacy ids such as "default" and "ocean" remain valid and must not be migrated.
// FNXC:DashboardTheming 2026-06-20-00:00: FN-6813 remaps the legacy mono id before pre-hydration validation so persisted users keep the red mono accent.
if (colorTheme === 'shadcn-mono') colorTheme = 'shadcn-mono-red';

View File

@@ -5795,6 +5795,162 @@ body[data-color-theme="terminal"][data-theme="light"]::before {
-webkit-backdrop-filter: none;
}
/* GLASS SILVER - Frosted translucent silver/gray surfaces */
[data-color-theme="glass-silver"] {
/*
FNXC:DashboardTheming 2026-07-01-00:00:
Glass Silver must preserve the same frosted, translucent surface contract as Glass while replacing the purple/pink accents with silver and graphite tones across dark and light modes.
*/
--bg: #101216;
--surface: color-mix(in srgb, #252a32 78%, transparent);
--card: color-mix(in srgb, #3a414c 55%, transparent);
--card-hover: color-mix(in srgb, #49515d 65%, transparent);
--surface-hover: color-mix(in srgb, var(--surface) 90%, var(--text) 10%);
--border: color-mix(in srgb, #f5f7fa 22%, transparent);
--text: #f3f4f6;
--text-muted: #c5cad3;
--text-dim: #8f97a3;
--todo: #d7dde6;
--in-progress: #b9c2cf;
--in-progress-rgb: 185, 194, 207;
--in-review: #a7b4c2;
--triage: #c7b88f;
--done: #8f97a3;
--color-success: #a7b4c2;
--color-error: #ef8f8f;
--font-primary: "Inter", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-mono: "SF Mono", "JetBrains Mono", "Menlo", monospace;
--space-xs: 5px;
--space-sm: 10px;
--space-md: 14px;
--space-lg: 20px;
--space-xl: 28px;
--space-2xl: 38px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 20px;
--radius-xl: 26px;
--radius-pill: 999px;
--radius: var(--radius-md);
--btn-padding: 9px 16px;
--btn-border-width: 1px;
--card-padding: 12px 14px;
--modal-padding: var(--space-lg) var(--space-xl);
--shadow-sm: 0 8px 16px color-mix(in srgb, #020305 24%, transparent);
--shadow-md: 0 14px 30px color-mix(in srgb, #030407 30%, transparent);
--shadow-lg: 0 22px 42px color-mix(in srgb, #04060a 35%, transparent);
--shadow-glow: 0 0 12px color-mix(in srgb, var(--todo) 22%, transparent);
--focus-ring: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent);
--focus-ring-strong: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
--shadow: var(--shadow-lg);
--transition-instant: 0.08s ease;
--transition-fast: 0.14s ease;
--transition-normal: 0.22s ease;
--transition-slow: 0.34s ease;
--cta-bg: color-mix(in srgb, var(--accent) 32%, transparent);
--cta-border: color-mix(in srgb, #f5f7fa 38%, transparent);
--cta-text: #f7f8fa;
--cta-bg-hover: color-mix(in srgb, var(--accent) 46%, transparent);
--cta-border-hover: color-mix(in srgb, #f5f7fa 50%, transparent);
--cta-glow: 0 0 12px color-mix(in srgb, var(--accent) 24%, transparent);
--logo-accent: var(--todo);
--color-info: #b9c2cf;
--accent: #d7dde6;
--accent-text: #111318;
}
[data-color-theme="glass-silver"][data-theme="light"] {
--bg: #eef0f3;
--surface: color-mix(in srgb, #ffffff 75%, transparent);
--card: color-mix(in srgb, #ffffff 60%, transparent);
--card-hover: color-mix(in srgb, #ffffff 75%, transparent);
--surface-hover: color-mix(in srgb, var(--surface) 92%, var(--text) 8%);
--border: color-mix(in srgb, #5d6673 22%, transparent);
--text: #252a31;
--text-muted: #58616d;
--text-dim: #8a929d;
--todo: #5f6875;
--in-progress: #747f8c;
--in-progress-rgb: 116, 127, 140;
--in-review: #667383;
--triage: #907d53;
--done: #8a929d;
--color-success: #667383;
--color-error: #b24f4f;
--shadow-sm: 0 8px 16px color-mix(in srgb, #1f2933 8%, transparent);
--shadow-md: 0 14px 30px color-mix(in srgb, #1f2933 10%, transparent);
--shadow-lg: 0 22px 42px color-mix(in srgb, #1f2933 14%, transparent);
--shadow-glow: 0 0 10px color-mix(in srgb, var(--todo) 20%, transparent);
--focus-ring: 0 0 0 2px color-mix(in srgb, var(--accent) 15%, transparent);
--focus-ring-strong: 0 0 0 2px color-mix(in srgb, var(--accent) 24%, transparent);
--cta-bg: color-mix(in srgb, var(--accent) 24%, transparent);
--cta-border: color-mix(in srgb, var(--accent) 38%, transparent);
--cta-text: #252a31;
--cta-bg-hover: color-mix(in srgb, var(--accent) 34%, transparent);
--cta-border-hover: color-mix(in srgb, var(--accent) 48%, transparent);
--cta-glow: 0 0 10px color-mix(in srgb, var(--accent) 22%, transparent);
--logo-accent: var(--todo);
--color-info: #747f8c;
--accent: #5f6875;
--accent-text: #ffffff;
}
[data-color-theme="glass-silver"] .card,
[data-color-theme="glass-silver"] .column {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
background: color-mix(in srgb, #f5f7fa 8%, transparent);
border-color: color-mix(in srgb, #f5f7fa 24%, transparent);
}
[data-color-theme="glass-silver"][data-theme="light"] .card,
[data-color-theme="glass-silver"][data-theme="light"] .column {
background: color-mix(in srgb, #ffffff 60%, transparent);
border-color: color-mix(in srgb, #5d6673 24%, transparent);
}
[data-color-theme="glass-silver"] .btn {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-color: color-mix(in srgb, #f5f7fa 34%, transparent);
background: color-mix(in srgb, #f5f7fa 10%, transparent);
}
[data-color-theme="glass-silver"] .btn-primary,
[data-color-theme="glass-silver"] .btn-task-create {
background: linear-gradient(135deg, color-mix(in srgb, #d7dde6 35%, transparent), color-mix(in srgb, #9ca6b4 30%, transparent));
border-color: color-mix(in srgb, #f5f7fa 42%, transparent);
}
[data-color-theme="glass-silver"] .btn-primary:hover,
[data-color-theme="glass-silver"] .btn-task-create:hover {
background: linear-gradient(135deg, color-mix(in srgb, #d7dde6 50%, transparent), color-mix(in srgb, #9ca6b4 42%, transparent));
box-shadow: 0 12px 24px color-mix(in srgb, #030407 28%, transparent);
}
[data-color-theme="glass-silver"][data-theme="light"] .btn {
background: color-mix(in srgb, #ffffff 52%, transparent);
border-color: color-mix(in srgb, #5d6673 28%, transparent);
}
[data-color-theme="glass-silver"] .modal-overlay {
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* HORIZON - Warm sunset-inspired palette */
[data-color-theme="horizon"] {
--bg: #1c1e26;

View File

@@ -48,6 +48,7 @@
"parchment",
"terminal",
"glass",
"glass-silver",
"horizon",
"vitesse",
"outrun",