Compare commits

...
2 Commits
Author SHA1 Message Date
Christoph 58027f5e22 feat(settings): add appearance and custom theme support
Introduce an appearance preference with modern, classic, and custom
modes and a persisted custom theme palette. Add helpers to load,
persist, and apply appearance and custom colors, and wire them into the
app settings lifecycle. Implement a comprehensive light theme and
appearance presets using CSS variables so custom palettes are applied
consistently across the UI, and include appearance in analytics when
settings are saved.

- Persist and apply appearance and custom color palette to :root
- Add muted light theme plus classic/modern presets and custom mapping
- Include appearance and customTheme in settings save and analytics payload
2026-08-23 23:14:07 +02:00
Christoph 3477070ec3 style(theme): refresh dark theme palette and control contrast
Update the dark theme tokens and UI surfaces to improve legibility
and clarify interactive boundaries across the app. Controls such as
buttons, inputs, and focus outlines were standardized and simplified
to reduce visual noise while preserving hierarchy.

- Overhaul color tokens and surface backgrounds for consistent tones
- Simplify primary button treatment and adjust focus/outline behavior
- Add targeted dark-mode rules to separate chrome (tabs, toolbars)
2026-08-23 22:20:25 +02:00
4 changed files with 929 additions and 66 deletions
+106 -2
View File
@@ -146,10 +146,12 @@
AiReviewResult,
AiCommitPlan,
AiSettings,
AppAppearance,
AppLanguage,
AppTheme,
AnalyticsSettings,
CommitAiPhase,
CustomThemeColors,
ConflictFile,
DetectedExternalTool,
ExplorerNode,
@@ -253,6 +255,8 @@
const AI_SETTINGS_KEY = "gitlite.aiSettings.v1";
const ANALYTICS_SETTINGS_KEY = "gitlite.analyticsSettings.v1";
const APP_THEME_KEY = "gitlite.theme.v1";
const APP_APPEARANCE_KEY = "gitlite.appearance.v1";
const CUSTOM_THEME_KEY = "gitlite.customTheme.v1";
const APP_LANGUAGE_KEY = "gitlite.language.v1";
const EXTERNAL_TOOLS_SETTINGS_KEY = "gitlite.externalTools.v1";
const AUTO_REFRESH_ENABLED_KEY = "gitlite.autoRefreshEnabled.v1";
@@ -364,6 +368,8 @@
let analyticsNoticeOpen = false;
let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings();
let appTheme: AppTheme = loadThemePreference();
let appAppearance: AppAppearance = loadAppearancePreference();
let customTheme: CustomThemeColors = loadCustomTheme();
let appLanguage: AppLanguage = loadLanguagePreference();
let externalToolsSettings: ExternalToolsSettings = loadExternalToolsSettings();
let externalToolsConfigured = hasStoredExternalToolsSettings();
@@ -555,6 +561,7 @@
$: fileManagerToolName = externalToolDisplayName("fileManager", externalToolsSettings.fileManager, detectedExternalTools);
$: applyThemePreference(appTheme);
$: applyAppearancePreference(appAppearance, customTheme);
$: applyLanguagePreference(appLanguage);
// ── Lifecycle ──────────────────────────────────────────────────────────────
@@ -1119,6 +1126,98 @@
}
}
function defaultCustomTheme(): CustomThemeColors {
return {
background: "#cfd5dc",
surface: "#e2e6ea",
accent: "#2eb5d1",
text: "#222328",
};
}
function isHexColor(value: unknown): value is string {
return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value);
}
function loadAppearancePreference(): AppAppearance {
try {
const stored = localStorage.getItem(APP_APPEARANCE_KEY);
if (stored === "modern" || stored === "classic" || stored === "custom") return stored;
} catch {
// Local storage is optional; the current design remains the default.
}
return "modern";
}
function loadCustomTheme(): CustomThemeColors {
const fallback = defaultCustomTheme();
try {
const stored = JSON.parse(localStorage.getItem(CUSTOM_THEME_KEY) ?? "null") as Partial<CustomThemeColors> | null;
if (!stored) return fallback;
return {
background: isHexColor(stored.background) ? stored.background : fallback.background,
surface: isHexColor(stored.surface) ? stored.surface : fallback.surface,
accent: isHexColor(stored.accent) ? stored.accent : fallback.accent,
text: isHexColor(stored.text) ? stored.text : fallback.text,
};
} catch {
return fallback;
}
}
function persistAppearancePreference(next: AppAppearance, colors: CustomThemeColors) {
try {
localStorage.setItem(APP_APPEARANCE_KEY, next);
localStorage.setItem(CUSTOM_THEME_KEY, JSON.stringify(colors));
} catch {
// Ignore storage quota/private-mode errors.
}
}
function applyAppearancePreference(next: AppAppearance, colors: CustomThemeColors) {
const root = document.documentElement;
root.dataset.appearance = next;
const customProperties = [
"--app-bg", "--app-button-bg", "--app-input-bg", "--app-dialog-bg", "--app-dialog-chrome",
"--app-settings-row-bg", "--color-surface", "--color-surface-alt", "--color-surface-dim",
"--color-surface-hover", "--color-surface-raised", "--color-surface-solid", "--color-border",
"--color-border-subtle", "--color-border-input", "--color-primary", "--color-primary-dark",
"--color-accent", "--color-ink", "--color-ink-muted", "--color-ink-faint", "--color-ink-dim",
"--color-bar-text", "--color-bar-muted",
];
customProperties.forEach((property) => root.style.removeProperty(property));
if (next !== "custom") return;
const { background, surface, accent, text } = colors;
const values: Record<string, string> = {
"--app-bg": background,
"--app-button-bg": surface,
"--app-input-bg": `color-mix(in srgb, ${surface} 88%, white)`,
"--app-dialog-bg": surface,
"--app-dialog-chrome": `color-mix(in srgb, ${surface} 90%, ${background})`,
"--app-settings-row-bg": `color-mix(in srgb, ${surface} 92%, ${background})`,
"--color-surface": surface,
"--color-surface-alt": `color-mix(in srgb, ${surface} 82%, ${background})`,
"--color-surface-dim": `color-mix(in srgb, ${surface} 88%, ${background})`,
"--color-surface-hover": `color-mix(in srgb, ${surface} 88%, ${text})`,
"--color-surface-raised": `color-mix(in srgb, ${surface} 92%, white)`,
"--color-surface-solid": surface,
"--color-border": `color-mix(in srgb, ${text} 32%, ${surface})`,
"--color-border-subtle": `color-mix(in srgb, ${text} 18%, ${surface})`,
"--color-border-input": `color-mix(in srgb, ${text} 38%, ${surface})`,
"--color-primary": accent,
"--color-primary-dark": `color-mix(in srgb, ${accent} 82%, black)`,
"--color-accent": accent,
"--color-ink": text,
"--color-ink-muted": `color-mix(in srgb, ${text} 76%, ${surface})`,
"--color-ink-faint": `color-mix(in srgb, ${text} 58%, ${surface})`,
"--color-ink-dim": `color-mix(in srgb, ${text} 68%, ${surface})`,
"--color-bar-text": text,
"--color-bar-muted": `color-mix(in srgb, ${text} 62%, ${surface})`,
};
Object.entries(values).forEach(([property, value]) => root.style.setProperty(property, value));
}
function loadLanguagePreference(): AppLanguage {
try {
const stored = localStorage.getItem(APP_LANGUAGE_KEY);
@@ -1157,15 +1256,18 @@
if (appTheme === "system") applyThemePreference(appTheme);
}
function saveAppSettings(next: AnalyticsSettings, nextTheme: AppTheme, nextLanguage: AppLanguage, nextAutoRefresh: boolean, nextExternalTools: ExternalToolsSettings) {
function saveAppSettings(next: AnalyticsSettings, nextTheme: AppTheme, nextAppearance: AppAppearance, nextCustomTheme: CustomThemeColors, nextLanguage: AppLanguage, nextAutoRefresh: boolean, nextExternalTools: ExternalToolsSettings) {
const autoRefreshWasEnabled = autoRefreshEnabled;
analyticsSettings = next;
appTheme = nextTheme;
appAppearance = nextAppearance;
customTheme = nextCustomTheme;
appLanguage = nextLanguage;
autoRefreshEnabled = nextAutoRefresh;
externalToolsSettings = nextExternalTools;
persistAnalyticsSettings(next);
persistThemePreference(nextTheme);
persistAppearancePreference(nextAppearance, nextCustomTheme);
persistLanguagePreference(nextLanguage);
persistStoredBoolean(AUTO_REFRESH_ENABLED_KEY, nextAutoRefresh);
persistExternalToolsSettings(nextExternalTools);
@@ -1173,7 +1275,7 @@
setTelemetryEnabled(next.enabled);
appSettingsOpen = false;
if (nextAutoRefresh && !autoRefreshWasEnabled) void autoRefreshTick();
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme, language: nextLanguage, auto_refresh: nextAutoRefresh ? 1 : 0 });
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme, appearance: nextAppearance, language: nextLanguage, auto_refresh: nextAutoRefresh ? 1 : 0 });
}
function updateCommitMessage(message: string) {
@@ -5599,6 +5701,8 @@
<AppSettingsDialog
analytics={analyticsSettings}
theme={appTheme}
appearance={appAppearance}
customTheme={customTheme}
language={appLanguage}
autoRefresh={autoRefreshEnabled}
externalTools={externalToolsSettings}
+713 -62
View File
@@ -1,57 +1,57 @@
@import "tailwindcss";
@theme {
--color-ink: #edf3ff;
--color-ink-muted: #b1bad0;
--color-ink-faint: #6f7c96;
--color-ink-dim: #8895ad;
--color-ink-quiet: #98a4bb;
--color-ink: #f0f1f2;
--color-ink-muted: #c1c3c6;
--color-ink-faint: #7f8389;
--color-ink-dim: #9da1a6;
--color-ink-quiet: #aeb1b5;
--color-surface: rgba(18, 23, 34, 0.88);
--color-surface-alt: #090d14;
--color-surface-dim: rgba(15, 20, 30, 0.94);
--color-surface-hover: rgba(36, 45, 64, 0.78);
--color-surface-raised: rgba(23, 29, 43, 0.92);
--color-surface-solid: #171d2b;
--color-surface: #2b2e34;
--color-surface-alt: #1b1c1e;
--color-surface-dim: #2e3137;
--color-surface-hover: #363a41;
--color-surface-raised: #32363d;
--color-surface-solid: #222328;
--color-border: rgba(90, 111, 154, 0.3);
--color-border-subtle: rgba(226, 232, 240, 0.08);
--color-border-input: rgba(77, 182, 214, 0.26);
--color-border: #3a3e45;
--color-border-subtle: #34383e;
--color-border-input: #465159;
--color-primary: #6f8cff;
--color-primary-dark: #5d74e8;
--color-accent: #4db6d6;
--color-primary: #25a7c7;
--color-primary-dark: #188ca9;
--color-accent: #2eb5d1;
--color-bar: rgba(7, 10, 16, 0.94);
--color-bar-text: #f1f5ff;
--color-bar-muted: #7f8ba3;
--color-bar: #292c30;
--color-bar-text: #f0f1f2;
--color-bar-muted: #8b8f94;
--font-mono: "Cascadia Code", "SFMono-Regular", Consolas, monospace;
}
:root {
--app-color-scheme: dark;
--app-bg: linear-gradient(135deg, #070a10 0%, #0c111a 48%, #090e16 100%);
--app-button-bg: linear-gradient(180deg, rgba(36, 45, 64, 0.92), rgba(22, 28, 42, 0.94));
--app-input-bg: rgba(10, 14, 22, 0.82);
--app-select-option-bg: #171d2b;
--app-bg: #222328;
--app-button-bg: #32363d;
--app-input-bg: #22252a;
--app-select-option-bg: #32363d;
--app-panel-highlight: linear-gradient(180deg, rgba(255,255,255,0.038), transparent 70%);
--app-panel-shadow: 0 18px 48px rgba(0,0,0,0.28), inset 0 1px 0 rgba(255,255,255,0.045);
--app-dialog-backdrop: rgba(4, 7, 12, 0.58);
--app-dialog-bg: #101622;
--app-dialog-chrome: #141b29;
--app-dialog-backdrop: rgba(12, 13, 15, 0.7);
--app-dialog-bg: #22252a;
--app-dialog-chrome: #2b2e34;
--app-dialog-shadow: 0 24px 68px rgba(0, 0, 0, 0.54), 0 2px 12px rgba(0,0,0,0.32);
--app-settings-row-bg: #141b29;
--app-scrollbar-thumb: #303a4f;
--app-scrollbar-thumb-hover: #44506a;
--app-settings-row-bg: #2b2e34;
--app-scrollbar-thumb: #4a4e55;
--app-scrollbar-thumb-hover: #60656d;
--code-surface: #111321;
--code-surface-raised: #171a2b;
--code-surface-subtle: #0d101a;
--code-surface-muted: #10131e;
--code-surface-meta: #0c0e18;
--code-input-bg: #0b0e18;
--code-hover-bg: #151a2b;
--code-surface: #222328;
--code-surface-raised: #2b2e34;
--code-surface-subtle: #1f2024;
--code-surface-muted: #26282d;
--code-surface-meta: #1d1e22;
--code-input-bg: #22252a;
--code-hover-bg: #30343a;
--code-add-text: #5dd88a;
--code-add-strong: #4eca76;
--code-add-bg: rgba(78, 202, 118, 0.09);
@@ -190,7 +190,7 @@
}
input:focus, textarea:focus, select:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(100, 108, 255, 0.22), 0 0 32px rgba(65, 209, 255, 0.08);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 20%, transparent), 0 0 30px color-mix(in srgb, var(--color-accent) 7%, transparent);
}
input { height: 34px; padding: 0 11px; }
textarea { resize: vertical; min-height: 132px; padding: 10px 11px; line-height: 1.45; }
@@ -334,15 +334,15 @@
}
.btn-primary {
border-color: rgba(111, 140, 255, 0.72);
border-color: color-mix(in srgb, var(--color-primary) 82%, white);
color: #ffffff;
background: linear-gradient(135deg, #5f7df2 0%, #238eb4 100%);
box-shadow: 0 10px 24px rgba(77, 182, 214, 0.16);
background: var(--color-primary);
box-shadow: none;
font-weight: 600;
}
.btn-primary:hover:not(:disabled) {
border-color: rgba(77, 182, 214, 0.78);
background: linear-gradient(135deg, #6f8cff 0%, #2da0c7 100%);
border-color: color-mix(in srgb, var(--color-accent) 78%, white);
background: var(--color-accent);
color: #ffffff;
}
@@ -6227,22 +6227,23 @@
--ui-radius-sm: 5px;
--ui-control-height: 32px;
--ui-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
--app-bg: #0d1118;
--app-button-bg: #171d27;
--app-input-bg: #101620;
--app-bg: #222328;
--app-button-bg: #32363d;
--app-input-bg: #22252a;
--app-panel-highlight: none;
--app-panel-shadow: none;
--color-surface: #111720;
--color-surface-alt: #0d121a;
--color-surface-dim: #141b25;
--color-surface-hover: #1b2431;
--color-surface-raised: #151c27;
--color-surface-solid: #111720;
--color-border: #2a3546;
--color-border-subtle: #222c3a;
--color-primary: #4d8dff;
--color-primary-dark: #3477ed;
--color-accent: #65a2ff;
--color-surface: #2b2e34;
--color-surface-alt: #1b1c1e;
--color-surface-dim: #2e3137;
--color-surface-hover: #363a41;
--color-surface-raised: #32363d;
--color-surface-solid: #222328;
--color-border: #3a3e45;
--color-border-subtle: #34383e;
--color-border-input: #465159;
--color-primary: #25a7c7;
--color-primary-dark: #188ca9;
--color-accent: #2eb5d1;
}
:root[data-theme="light"] {
@@ -6279,8 +6280,8 @@ button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
outline: 2px solid color-mix(in srgb, var(--color-primary) 65%, transparent);
outline-offset: 1px;
outline: 2px solid color-mix(in srgb, var(--color-accent) 88%, transparent);
outline-offset: 2px;
}
input, textarea, select { border-radius: var(--ui-radius-sm); }
input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 16%, transparent); }
@@ -6289,7 +6290,7 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
.shell-body { gap: 0; padding: 0; }
.titlebar {
border-bottom: 1px solid var(--color-border-subtle);
background: var(--color-surface-solid);
background: var(--color-bar);
}
.titlebar-brand { min-width: 140px; padding-left: 14px; border-right: 0; font-size: 14px; }
.titlebar-brand-icon,
@@ -6332,7 +6333,7 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
border: 0;
border-bottom: 1px solid var(--color-border);
border-radius: 0;
background: var(--color-surface-solid);
background: var(--color-surface-raised);
}
.repo-toolbar-sync { grid-template-rows: 10px 36px; }
.repo-toolbar-group-label { padding-left: 1px; font-size: 8.5px; letter-spacing: .13em; }
@@ -6348,7 +6349,7 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
background: var(--color-border-subtle);
row-gap: 0;
}
.left-sidebar { background: var(--color-surface-solid); }
.left-sidebar { background: var(--color-surface); }
.main-panel { border: 0; border-radius: 0; background: var(--color-surface-solid); }
.history-aside { background: var(--color-border-subtle); row-gap: 0; }
@@ -6360,6 +6361,7 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
backdrop-filter: none;
}
.left-sidebar .panel + .panel { border-top: 1px solid var(--color-border); }
.left-sidebar .panel { background: var(--color-surface); }
.section-head {
min-height: 50px;
padding: 8px 12px;
@@ -6720,6 +6722,184 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
.repo-section > header { background: var(--color-surface-dim); }
.repo-row-main:hover:not(:disabled) { background: var(--color-surface-hover); }
/* Dark control contrast --------------------------------------------------
Keep GitKraken's restrained graphite palette while making interactive
boundaries and text states unambiguous on every dark surface. */
:root:not([data-theme="light"]) .btn-primary {
border-color: #2eb5d1;
color: #ffffff;
background: #126f84;
}
:root:not([data-theme="light"]) .btn-primary:hover:not(:disabled) {
border-color: #55c5dc;
color: #ffffff;
background: #147c94;
}
:root:not([data-theme="light"]) .btn-secondary {
border-color: #596169;
color: #d7dadc;
background: #32363d;
}
:root:not([data-theme="light"]) .btn-secondary:hover:not(:disabled) {
border-color: #707982;
color: #ffffff;
background: #41464d;
}
:root:not([data-theme="light"]) input,
:root:not([data-theme="light"]) textarea,
:root:not([data-theme="light"]) select,
:root:not([data-theme="light"]) .select-menu-trigger {
border-color: #596169;
}
:root:not([data-theme="light"]) input:hover:not(:disabled),
:root:not([data-theme="light"]) textarea:hover:not(:disabled),
:root:not([data-theme="light"]) select:hover:not(:disabled),
:root:not([data-theme="light"]) .select-menu-trigger:hover:not(:disabled) {
border-color: #707982;
}
:root:not([data-theme="light"]) input:focus,
:root:not([data-theme="light"]) textarea:focus,
:root:not([data-theme="light"]) select:focus,
:root:not([data-theme="light"]) .select-menu.open .select-menu-trigger {
border-color: var(--color-accent);
}
:root:not([data-theme="light"]) .repo-action-group,
:root:not([data-theme="light"]) .dialog-icon-button {
border-color: #535a62;
}
:root:not([data-theme="light"]) .repo-action:hover:not(:disabled),
:root:not([data-theme="light"]) .repo-action.active,
:root:not([data-theme="light"]) .dialog-icon-button:hover:not(:disabled) {
border-color: #707982;
background: #3b4047;
}
/* Dark app chrome separation -------------------------------------------
Tabs, command toolbar and workspace should read as three adjacent
layers instead of one uninterrupted gray strip. */
:root:not([data-theme="light"]) .repo-tabbar {
border-bottom-color: #505760;
background: #26292f;
box-shadow:
inset 0 -1px 0 #191b1f,
0 1px 0 rgba(255, 255, 255, 0.035);
}
:root:not([data-theme="light"]) .repo-tabs-scroll {
border-left-color: #454b53;
}
:root:not([data-theme="light"]) .repo-tab-wrap {
border-right-color: #454b53;
background: #292c32;
}
:root:not([data-theme="light"]) .repo-tab-wrap:hover {
background: #30343a;
}
:root:not([data-theme="light"]) .repo-tab-wrap.active,
:root:not([data-theme="light"]) .repo-tab.management.active {
background: #343941;
box-shadow:
inset 0 -3px 0 var(--color-accent),
inset 1px 0 0 #4c535b,
inset -1px 0 0 #4c535b,
inset 0 1px 0 rgba(255, 255, 255, 0.045);
}
:root:not([data-theme="light"]) .repo-tab-add {
border-left-color: #454b53;
background: #292c32;
}
:root:not([data-theme="light"]) .repo-toolbar {
border-top: 1px solid #424850;
border-bottom-color: #505760;
background: #30343a;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.035),
0 2px 0 #191b1f;
}
:root:not([data-theme="light"]) .repo-action-group {
border-color: #596169;
background: #292d32;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
}
:root:not([data-theme="light"]) .repo-action-group > .repo-action,
:root:not([data-theme="light"]) .repo-action-group > .repo-history-wrap > .repo-action {
border-right-color: #484e55;
}
:root:not([data-theme="light"]) .repo-toolbar-divider {
background: #555c64;
}
:root:not([data-theme="light"]) .workspace {
box-shadow: inset 0 1px 0 #17191c;
}
/* Light app chrome separation ------------------------------------------
Keep the same layered hierarchy as the dark theme with restrained,
cool-gray borders and a clean white active surface. */
:root[data-theme="light"] .repo-tabbar {
border-bottom-color: #aeb8c5;
background: #e9edf2;
box-shadow:
inset 0 -1px 0 #d2d9e2,
0 1px 0 rgba(255, 255, 255, 0.9);
}
:root[data-theme="light"] .repo-tabs-scroll {
border-left-color: #c3cbd5;
}
:root[data-theme="light"] .repo-tab-wrap {
border-right-color: #c3cbd5;
background: #eef1f5;
}
:root[data-theme="light"] .repo-tab-wrap:hover {
background: #e3e8ee;
}
:root[data-theme="light"] .repo-tab-wrap.active,
:root[data-theme="light"] .repo-tab.management.active {
background: #ffffff;
box-shadow:
inset 0 -3px 0 var(--color-primary),
inset 1px 0 0 #b7c1cd,
inset -1px 0 0 #b7c1cd,
inset 0 1px 0 rgba(255, 255, 255, 0.95);
}
:root[data-theme="light"] .repo-tab-add {
border-left-color: #c3cbd5;
background: #eef1f5;
}
:root[data-theme="light"] .repo-toolbar {
border-top: 1px solid #c4ccd6;
border-bottom-color: #aeb8c5;
background: #f4f6f8;
box-shadow:
inset 0 1px 0 #ffffff,
0 2px 0 #d6dce4;
}
:root[data-theme="light"] .repo-action-group {
border-color: #aeb8c5;
background: #ffffff;
box-shadow:
inset 0 1px 0 #ffffff,
0 1px 2px rgba(34, 49, 78, 0.06);
}
:root[data-theme="light"] .repo-action-group > .repo-action,
:root[data-theme="light"] .repo-action-group > .repo-history-wrap > .repo-action {
border-right-color: #c8d0da;
}
:root[data-theme="light"] .repo-toolbar-divider {
background: #b5bec9;
}
:root[data-theme="light"] .workspace {
box-shadow: inset 0 1px 0 #c9d1db;
}
@media (max-width: 1180px) {
.repo-action { padding-inline: 10px; }
.repo-toolbar-divider { margin-inline: 2px; }
@@ -7872,3 +8052,474 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
.status-lane-head { align-items: flex-start; flex-direction: column; }
.status-lane-actions { width: 100%; justify-content: flex-start; }
}
/* Muted light theme -----------------------------------------------------
A lower-luminance light palette for long sessions. The hierarchy stays
light and readable without using pure white for every working surface. */
:root[data-theme="light"] {
--ui-shadow: 0 10px 26px rgba(29, 39, 52, 0.12);
--app-bg: #cfd5dc;
--app-button-bg: #e2e7eb;
--app-input-bg: #e9edf0;
--app-select-option-bg: #e7ebef;
--app-dialog-backdrop: rgba(91, 103, 117, 0.38);
--app-dialog-bg: #e2e6ea;
--app-dialog-chrome: #d7dde3;
--app-settings-row-bg: #dce2e7;
--app-scrollbar-thumb: #aab4be;
--app-scrollbar-thumb-hover: #929eaa;
--color-ink: #202832;
--color-ink-muted: #4d5a68;
--color-ink-faint: #687583;
--color-ink-dim: #596674;
--color-ink-quiet: #62707e;
--color-surface: #e2e6ea;
--color-surface-alt: #d8dde3;
--color-surface-dim: #dce1e6;
--color-surface-hover: #cfd6de;
--color-surface-raised: #e7ebee;
--color-surface-solid: #e1e5e9;
--color-border: #9faab5;
--color-border-subtle: #bbc3cc;
--color-border-input: #98a5b2;
--color-bar: #d6dce2;
--color-bar-text: #202832;
--color-bar-muted: #596674;
--code-surface: #e1e5e9;
--code-surface-raised: #d9dfe5;
--code-surface-subtle: #dce2e7;
--code-surface-muted: #d4dae0;
--code-surface-meta: #ced5dc;
--code-input-bg: #e8ecef;
--code-hover-bg: #cfd7df;
}
:root[data-theme="light"] .titlebar {
background:
linear-gradient(90deg, rgba(49, 95, 214, 0.035), rgba(15, 143, 181, 0.025)),
var(--color-bar);
}
:root[data-theme="light"] .repo-tabbar {
border-bottom-color: #929da8;
background: #cbd2d9;
box-shadow: inset 0 -1px 0 #aeb7c0;
}
:root[data-theme="light"] .repo-tab-wrap,
:root[data-theme="light"] .repo-tab-add {
border-color: #aeb8c2;
background: #d3d9df;
}
:root[data-theme="light"] .repo-tab-wrap:hover,
:root[data-theme="light"] .repo-tab.management:hover:not(:disabled) {
background: #c8d0d8;
}
:root[data-theme="light"] .repo-tab-wrap.active,
:root[data-theme="light"] .repo-tab.management.active {
background: #e3e7eb;
box-shadow:
inset 0 -3px 0 var(--color-primary),
inset 1px 0 0 #9faab5,
inset -1px 0 0 #9faab5;
}
:root[data-theme="light"] .repo-toolbar {
border-top-color: #aeb7c0;
border-bottom-color: #929da8;
background: #d5dbe1;
box-shadow: 0 2px 0 #b9c1c9;
}
:root[data-theme="light"] .repo-action-group {
border-color: #98a4af;
background: #e0e5e9;
box-shadow: 0 1px 2px rgba(29, 39, 52, 0.1);
}
:root[data-theme="light"] .repo-action-group > .repo-action,
:root[data-theme="light"] .repo-action-group > .repo-history-wrap > .repo-action {
border-right-color: #aeb8c2;
}
:root[data-theme="light"] .workspace {
box-shadow: inset 0 1px 0 #aab4be;
}
:root[data-theme="light"] .repo-management,
:root[data-theme="light"] .main-panel {
background: var(--color-surface);
}
:root[data-theme="light"] .repo-section {
background: #dbe0e5;
}
:root[data-theme="light"] .repo-section > header,
:root[data-theme="light"] .repo-summary,
:root[data-theme="light"] .lane-header,
:root[data-theme="light"] .stash-toolbar,
:root[data-theme="light"] .dialog-footer,
:root[data-theme="light"] .cred-header {
background: #d2d8de;
}
:root[data-theme="light"] .repo-empty,
:root[data-theme="light"] .graph-list,
:root[data-theme="light"] .commit-body,
:root[data-theme="light"] .commit-files,
:root[data-theme="light"] .commit-file-button,
:root[data-theme="light"] .commit-ref-detail-item,
:root[data-theme="light"] .cred-card,
:root[data-theme="light"] .cred-body {
background: #e0e4e8;
}
:root[data-theme="light"] .graph-gutter {
background: #d2d8de;
}
:root[data-theme="light"] .graph-row:hover .commit-body {
background: #d3dae1;
}
:root[data-theme="light"] .branch-filter-dialog,
:root[data-theme="light"] .status-panel-overlay-card {
background: #e2e6ea;
}
:root[data-theme="light"] .cred-input input,
:root[data-theme="light"] .cred-expiry input[type="date"] {
background: var(--app-input-bg);
}
/* Shared dark-mode accent language in the muted light theme. */
:root[data-theme="light"] {
--color-primary: #25a7c7;
--color-primary-dark: #188ca9;
--color-accent: #2eb5d1;
--color-ink: #222328;
--color-ink-muted: #3f4852;
--color-ink-faint: #66707a;
--color-ink-dim: #515b65;
--color-ink-quiet: #5c6670;
--color-bar-text: #222328;
--color-bar-muted: #59636d;
}
:root[data-theme="light"] .btn-primary {
border-color: #2eb5d1;
color: #f0f1f2;
background: #126f84;
box-shadow: none;
}
:root[data-theme="light"] .btn-primary:hover:not(:disabled) {
border-color: #55c5dc;
color: #ffffff;
background: #147c94;
}
:root[data-theme="light"] .btn-secondary,
:root[data-theme="light"] .dialog-icon-button {
border-color: #909ca8;
color: #2f3943;
background: #d2d8de;
}
:root[data-theme="light"] .btn-secondary:hover:not(:disabled),
:root[data-theme="light"] .dialog-icon-button:hover:not(:disabled) {
border-color: #778591;
color: #222328;
background: #c3cbd3;
}
:root[data-theme="light"] .repo-action-group {
border-color: #909ca8;
background: #d2d8de;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.32);
}
:root[data-theme="light"] .repo-action {
color: #35404a;
}
:root[data-theme="light"] .repo-action-group > .repo-action,
:root[data-theme="light"] .repo-action-group > .repo-history-wrap > .repo-action {
border-right-color: #aab4be;
}
:root[data-theme="light"] .repo-action:hover:not(:disabled),
:root[data-theme="light"] .repo-action.active {
color: #222328;
background: #c3cbd3;
}
:root[data-theme="light"] .repo-action.sync-primary,
:root[data-theme="light"] .repo-action.sync-primary svg {
color: #2eb5d1;
}
/* Repository tabs stay light while sharing the cyan dark-theme accent. */
:root[data-theme="light"] .repo-tabbar {
border-bottom-color: #929da8;
background: #cbd2d9;
box-shadow: inset 0 -1px 0 #aeb7c0;
}
:root[data-theme="light"] .repo-tabs-scroll {
border-left-color: #aeb8c2;
}
:root[data-theme="light"] .repo-tab-wrap,
:root[data-theme="light"] .repo-tab.management,
:root[data-theme="light"] .repo-tab-add {
border-color: #aeb8c2;
color: #3f4852;
background: #d3d9df;
}
:root[data-theme="light"] .repo-tab-wrap:hover,
:root[data-theme="light"] .repo-tab.management:hover:not(:disabled),
:root[data-theme="light"] .repo-tab-add:hover:not(:disabled) {
color: #222328;
background: #c5ced6;
}
:root[data-theme="light"] .repo-tab-wrap.active,
:root[data-theme="light"] .repo-tab.management.active {
color: #222328;
background: #e3e7eb;
box-shadow:
inset 0 -3px 0 var(--color-accent),
inset 1px 0 0 #9faab5,
inset -1px 0 0 #9faab5;
}
:root[data-theme="light"] .repo-tab > svg,
:root[data-theme="light"] .repo-tab-close,
:root[data-theme="light"] .repo-tab-add {
color: #66707a;
}
:root[data-theme="light"] .repo-tab-wrap.active .repo-tab,
:root[data-theme="light"] .repo-tab-wrap.active .repo-tab > svg,
:root[data-theme="light"] .repo-tab.management.active > svg,
:root[data-theme="light"] .repo-tab-add:hover:not(:disabled) {
color: #2eb5d1;
}
:root[data-theme="light"] .repo-tab-wrap.active .repo-tab span,
:root[data-theme="light"] .repo-tab.management.active span {
color: #222328;
}
:root[data-theme="light"] .repo-tab-close:hover:not(:disabled),
:root[data-theme="light"] .repo-tab-close:focus-visible:not(:disabled) {
color: #222328;
background: #c3cbd3;
}
/* Appearance presets --------------------------------------------------- */
:root[data-theme="light"][data-appearance="classic"] {
--ui-shadow: 0 10px 28px rgba(23, 40, 72, 0.09);
--app-bg: #f4f6f9;
--app-button-bg: #ffffff;
--app-input-bg: #ffffff;
--app-dialog-bg: #ffffff;
--app-dialog-chrome: #f4f7fc;
--app-settings-row-bg: #f8fafd;
--color-ink: #172033;
--color-ink-muted: #475569;
--color-ink-faint: #728098;
--color-ink-dim: #5f6f89;
--color-surface: #ffffff;
--color-surface-alt: #f7f8fa;
--color-surface-dim: #f8f9fb;
--color-surface-hover: #f0f4fa;
--color-surface-raised: #ffffff;
--color-surface-solid: #ffffff;
--color-border: #cfd6e1;
--color-border-subtle: #e3e7ed;
--color-border-input: #c9d1dd;
--color-primary: #0b63e6;
--color-primary-dark: #0755c8;
--color-accent: #0b63e6;
}
:root[data-theme="light"][data-appearance="classic"] .titlebar {
background:
linear-gradient(90deg, rgba(49, 95, 214, 0.08), rgba(15, 143, 181, 0.05), rgba(214, 156, 49, 0.04)),
var(--color-bar);
}
:root[data-theme="light"][data-appearance="classic"] .repo-tabbar,
:root[data-theme="light"][data-appearance="classic"] .repo-management-tools {
border-color: var(--color-border);
background: rgba(247, 249, 252, 0.96);
box-shadow: none;
}
:root[data-theme="light"][data-appearance="classic"] .repo-tab-wrap,
:root[data-theme="light"][data-appearance="classic"] .repo-tab.management,
:root[data-theme="light"][data-appearance="classic"] .repo-tab-add {
border-color: var(--color-border-subtle);
color: var(--color-ink-muted);
background: rgba(255, 255, 255, 0.5);
}
:root[data-theme="light"][data-appearance="classic"] .repo-tab-wrap:hover,
:root[data-theme="light"][data-appearance="classic"] .repo-tab.management:hover:not(:disabled),
:root[data-theme="light"][data-appearance="classic"] .repo-tab-add:hover:not(:disabled) {
color: var(--color-ink);
background: rgba(255, 255, 255, 0.9);
}
:root[data-theme="light"][data-appearance="classic"] .repo-tab-wrap.active,
:root[data-theme="light"][data-appearance="classic"] .repo-tab.management.active {
color: var(--color-ink);
background: #ffffff;
box-shadow: inset 0 -3px 0 var(--color-primary);
}
:root[data-theme="light"][data-appearance="classic"] .repo-tab-wrap.active .repo-tab span,
:root[data-theme="light"][data-appearance="classic"] .repo-tab.management.active span {
color: var(--color-ink);
}
:root[data-theme="light"][data-appearance="classic"] .repo-toolbar {
border-color: var(--color-border);
background: rgba(255, 255, 255, 0.78);
box-shadow: none;
}
:root[data-theme="light"][data-appearance="classic"] .repo-action-group {
border-color: var(--color-border);
background: var(--color-surface-raised);
box-shadow: none;
}
:root[data-theme="light"][data-appearance="classic"] .repo-action {
color: var(--color-ink-muted);
}
:root[data-theme="light"][data-appearance="classic"] .repo-action:hover:not(:disabled),
:root[data-theme="light"][data-appearance="classic"] .repo-action.active {
color: var(--color-ink);
background: var(--color-surface-hover);
}
:root[data-theme="light"][data-appearance="classic"] .btn-primary {
border-color: rgba(49, 95, 214, 0.72);
color: #ffffff;
background: linear-gradient(135deg, #315fd6 0%, #0f8fb5 100%);
box-shadow: 0 10px 24px rgba(49, 95, 214, 0.18);
}
:root[data-theme="light"][data-appearance="classic"] .btn-secondary,
:root[data-theme="light"][data-appearance="classic"] .dialog-icon-button {
border-color: rgba(15, 143, 181, 0.28);
color: #0c6683;
background: linear-gradient(180deg, rgba(15, 143, 181, 0.08), rgba(49, 95, 214, 0.06));
}
:root[data-theme="light"][data-appearance="classic"] .repo-management,
:root[data-theme="light"][data-appearance="classic"] .main-panel,
:root[data-theme="light"][data-appearance="classic"] .cred-card,
:root[data-theme="light"][data-appearance="classic"] .cred-body {
background: var(--color-surface);
}
:root[data-theme="light"][data-appearance="classic"] .repo-section {
background: rgba(255, 255, 255, 0.62);
}
:root[data-theme="light"][data-appearance="classic"] .repo-section > header,
:root[data-theme="light"][data-appearance="classic"] .repo-summary,
:root[data-theme="light"][data-appearance="classic"] .lane-header,
:root[data-theme="light"][data-appearance="classic"] .stash-toolbar,
:root[data-theme="light"][data-appearance="classic"] .dialog-footer,
:root[data-theme="light"][data-appearance="classic"] .cred-header {
background: var(--color-surface-dim);
}
:root[data-theme="light"][data-appearance="classic"] .repo-empty {
background: rgba(255, 255, 255, 0.48);
}
:root[data-theme="light"][data-appearance="classic"] .graph-list {
background: rgba(248, 250, 253, 0.9);
}
:root[data-theme="light"][data-appearance="classic"] .commit-body,
:root[data-theme="light"][data-appearance="classic"] .commit-files,
:root[data-theme="light"][data-appearance="classic"] .commit-file-button {
background: rgba(255, 255, 255, 0.72);
}
:root[data-appearance="custom"] .titlebar,
:root[data-appearance="custom"] .repo-tabbar,
:root[data-appearance="custom"] .repo-toolbar {
border-color: var(--color-border);
background: var(--color-surface-alt);
box-shadow: none;
}
:root[data-appearance="custom"] .repo-tab-wrap,
:root[data-appearance="custom"] .repo-tab.management,
:root[data-appearance="custom"] .repo-tab-add,
:root[data-appearance="custom"] .repo-action-group,
:root[data-appearance="custom"] .btn-secondary,
:root[data-appearance="custom"] .dialog-icon-button {
border-color: var(--color-border);
color: var(--color-ink-muted);
background: var(--color-surface-raised);
}
:root[data-appearance="custom"] .repo-tab-wrap.active,
:root[data-appearance="custom"] .repo-tab.management.active {
color: var(--color-ink);
background: var(--color-surface-raised);
box-shadow: inset 0 -3px 0 var(--color-accent);
}
:root[data-appearance="custom"] .repo-tab-wrap.active .repo-tab span,
:root[data-appearance="custom"] .repo-tab.management.active span {
color: var(--color-ink);
}
:root[data-appearance="custom"] .btn-primary {
border-color: var(--color-accent);
color: var(--color-surface-raised);
background: var(--color-primary-dark);
box-shadow: none;
}
:root[data-appearance="custom"] .repo-management,
:root[data-appearance="custom"] .main-panel,
:root[data-appearance="custom"] .repo-section,
:root[data-appearance="custom"] .repo-empty,
:root[data-appearance="custom"] .graph-list,
:root[data-appearance="custom"] .commit-body,
:root[data-appearance="custom"] .commit-files,
:root[data-appearance="custom"] .commit-file-button,
:root[data-appearance="custom"] .cred-card,
:root[data-appearance="custom"] .cred-body,
:root[data-appearance="custom"] .branch-filter-dialog,
:root[data-appearance="custom"] .status-panel-overlay-card {
background: var(--color-surface);
}
:root[data-appearance="custom"] .repo-section > header,
:root[data-appearance="custom"] .repo-summary,
:root[data-appearance="custom"] .lane-header,
:root[data-appearance="custom"] .stash-toolbar,
:root[data-appearance="custom"] .dialog-footer,
:root[data-appearance="custom"] .cred-header,
:root[data-appearance="custom"] .graph-gutter {
background: var(--color-surface-dim);
}
+102 -2
View File
@@ -31,8 +31,10 @@
} from "../externalTools";
import type {
AnalyticsSettings,
AppAppearance,
AppLanguage,
AppTheme,
CustomThemeColors,
DetectedExternalTool,
ExternalToolsSettings,
ToolOpenMode,
@@ -44,6 +46,8 @@
interface Props {
analytics: AnalyticsSettings;
theme: AppTheme;
appearance: AppAppearance;
customTheme: CustomThemeColors;
language: AppLanguage;
autoRefresh: boolean;
externalTools: ExternalToolsSettings;
@@ -51,13 +55,15 @@
detectionPending: boolean;
detectionUnavailable: boolean;
onRefreshDetectedTools: () => void | Promise<void>;
onSave: (settings: AnalyticsSettings, theme: AppTheme, language: AppLanguage, autoRefresh: boolean, externalTools: ExternalToolsSettings) => void;
onSave: (settings: AnalyticsSettings, theme: AppTheme, appearance: AppAppearance, customTheme: CustomThemeColors, language: AppLanguage, autoRefresh: boolean, externalTools: ExternalToolsSettings) => void;
onClose: () => void;
}
let {
analytics,
theme = "system",
appearance = "modern",
customTheme = { background: "#cfd5dc", surface: "#e2e6ea", accent: "#2eb5d1", text: "#222328" },
language = "en",
autoRefresh = true,
externalTools,
@@ -76,6 +82,8 @@
let advancedOpen = $state(false);
let analyticsEnabled = $state(true);
let selectedTheme = $state<AppTheme>("system");
let selectedAppearance = $state<AppAppearance>("modern");
let customColors = $state<CustomThemeColors>({ background: "#cfd5dc", surface: "#e2e6ea", accent: "#2eb5d1", text: "#222328" });
let selectedLanguage = $state<AppLanguage>("en");
let autoRefreshEnabled = $state(true);
let tools = $state<ExternalToolsSettings>(defaultExternalToolsSettings());
@@ -84,6 +92,8 @@
$effect(() => {
analyticsEnabled = analytics.enabled;
selectedTheme = theme;
selectedAppearance = appearance;
customColors = structuredClone(customTheme);
selectedLanguage = language;
autoRefreshEnabled = autoRefresh;
tools = structuredClone(externalTools);
@@ -94,7 +104,46 @@
...analytics,
enabled: analyticsEnabled,
noticeSeen: true,
}, selectedTheme, selectedLanguage, autoRefreshEnabled, $state.snapshot(tools));
}, selectedTheme, selectedAppearance, $state.snapshot(customColors), selectedLanguage, autoRefreshEnabled, $state.snapshot(tools));
}
function resetCustomColors() {
customColors = selectedTheme === "dark"
? { background: "#222328", surface: "#2b2e34", accent: "#2eb5d1", text: "#f0f1f2" }
: { background: "#cfd5dc", surface: "#e2e6ea", accent: "#2eb5d1", text: "#222328" };
}
function cssColorToHex(value: string, fallback: string): string {
const color = value.trim();
if (/^#[0-9a-f]{6}$/i.test(color)) return color.toLowerCase();
if (!color || typeof document === "undefined") return fallback;
const probe = document.createElement("span");
probe.style.color = color;
if (!probe.style.color) return fallback;
probe.style.display = "none";
document.body.appendChild(probe);
const match = getComputedStyle(probe).color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
probe.remove();
if (!match) return fallback;
return `#${match.slice(1, 4).map((part) => Number(part).toString(16).padStart(2, "0")).join("")}`;
}
function currentThemeColors(): CustomThemeColors {
const styles = getComputedStyle(document.documentElement);
const fallback = selectedTheme === "dark"
? { background: "#222328", surface: "#2b2e34", accent: "#2eb5d1", text: "#f0f1f2" }
: { background: "#cfd5dc", surface: "#e2e6ea", accent: "#2eb5d1", text: "#222328" };
return {
background: cssColorToHex(styles.getPropertyValue("--app-bg"), fallback.background),
surface: cssColorToHex(styles.getPropertyValue("--color-surface"), fallback.surface),
accent: cssColorToHex(styles.getPropertyValue("--color-accent"), fallback.accent),
text: cssColorToHex(styles.getPropertyValue("--color-ink"), fallback.text),
};
}
function selectAppearance(next: AppAppearance) {
if (next === "custom" && selectedAppearance !== "custom") customColors = currentThemeColors();
selectedAppearance = next;
}
function toolLabel(kind: ExternalToolKind): string {
@@ -292,6 +341,40 @@
</div>
</section>
<section class="general-setting-panel">
<header><SlidersHorizontal size={16} /><div><h4>{isGerman ? "Darstellungsstil" : "Design style"}</h4><p>{isGerman ? "Aktuell, klassisch oder selbst gestaltet." : "Current, classic, or designed by you."}</p></div></header>
<div class="settings-segmented" role="radiogroup" aria-label={isGerman ? "Darstellungsstil" : "Design style"}>
<label class:active={selectedAppearance === "modern"}><input type="radio" name="appearance" value="modern" checked={selectedAppearance === "modern"} onchange={() => selectAppearance("modern")} /><span>{isGerman ? "Aktuell" : "Modern"}</span></label>
<label class:active={selectedAppearance === "classic"}><input type="radio" name="appearance" value="classic" checked={selectedAppearance === "classic"} onchange={() => selectAppearance("classic")} /><span>{isGerman ? "Klassisch" : "Classic"}</span></label>
<label class:active={selectedAppearance === "custom"}><input type="radio" name="appearance" value="custom" checked={selectedAppearance === "custom"} onchange={() => selectAppearance("custom")} /><span>{isGerman ? "Eigene" : "Custom"}</span></label>
</div>
</section>
{#if selectedAppearance === "custom"}
<section class="general-setting-panel general-setting-wide custom-theme-panel">
<header>
<Palette size={16} />
<div><h4>{isGerman ? "Theme-Generator" : "Theme generator"}</h4><p>{isGerman ? "Erstelle dein eigenes Farbprofil." : "Create your own color profile."}</p></div>
<button class="theme-reset-button" type="button" onclick={resetCustomColors}><RotateCw size={13} />{isGerman ? "Zurücksetzen" : "Reset"}</button>
</header>
<div
class="theme-preview"
style={`--preview-bg:${customColors.background};--preview-surface:${customColors.surface};--preview-accent:${customColors.accent};--preview-text:${customColors.text};`}
aria-label={isGerman ? "Vorschau des eigenen Themes" : "Custom theme preview"}
>
<span class="theme-preview-sidebar"></span>
<span class="theme-preview-content"><i></i><b></b><em></em></span>
</div>
<div class="theme-color-grid">
<label><span>{isGerman ? "Hintergrund" : "Background"}</span><input type="color" bind:value={customColors.background} aria-label={isGerman ? "Hintergrundfarbe" : "Background color"} /><code>{customColors.background}</code></label>
<label><span>{isGerman ? "Fläche" : "Surface"}</span><input type="color" bind:value={customColors.surface} aria-label={isGerman ? "Flächenfarbe" : "Surface color"} /><code>{customColors.surface}</code></label>
<label><span>{isGerman ? "Akzent" : "Accent"}</span><input type="color" bind:value={customColors.accent} aria-label={isGerman ? "Akzentfarbe" : "Accent color"} /><code>{customColors.accent}</code></label>
<label><span>{isGerman ? "Schrift" : "Text"}</span><input type="color" bind:value={customColors.text} aria-label={isGerman ? "Schriftfarbe" : "Text color"} /><code>{customColors.text}</code></label>
</div>
<p class="theme-generator-note">{isGerman ? "Die Farben werden beim Speichern auf die gesamte Oberfläche angewendet." : "The colors are applied across the interface when you save."}</p>
</section>
{/if}
<section class="general-setting-panel">
<header><Languages size={16} /><div><h4>{isGerman ? "Sprache" : "Language"}</h4><p>{isGerman ? "Sprache der Oberfläche." : "Language used by the interface."}</p></div></header>
<div class="settings-segmented settings-language" role="radiogroup" aria-label={isGerman ? "App-Sprache" : "App language"}>
@@ -534,6 +617,22 @@
.settings-segmented label { display: flex; align-items: center; justify-content: center; min-height: 31px; border: 1px solid transparent; border-radius: 6px; color: var(--color-ink-dim); font-size: 10.5px; font-weight: 750; }
.settings-segmented label.active { border-color: var(--color-border-input); color: var(--color-ink); background: var(--color-surface-hover); }
.settings-segmented input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.custom-theme-panel > header { align-items: center; }
.custom-theme-panel > header > div { min-width: 0; }
.theme-reset-button { display: inline-flex; align-items: center; gap: 5px; min-height: 27px; margin-left: auto; padding: 0 8px; border: 1px solid var(--color-border); color: var(--color-ink-muted); background: var(--color-surface-raised); font-size: 9.5px; font-weight: 750; }
.theme-reset-button:hover:not(:disabled) { border-color: var(--color-border-input); color: var(--color-ink); background: var(--color-surface-hover); }
.theme-preview { display: grid; grid-template-columns: 28% 1fr; min-height: 78px; overflow: hidden; border: 1px solid color-mix(in srgb, var(--preview-text) 30%, var(--preview-surface)); background: var(--preview-bg); }
.theme-preview-sidebar { border-right: 1px solid color-mix(in srgb, var(--preview-text) 24%, var(--preview-surface)); background: color-mix(in srgb, var(--preview-surface) 86%, var(--preview-bg)); }
.theme-preview-content { display: grid; grid-template-columns: 1fr auto; align-content: start; gap: 8px; margin: 10px; padding: 10px; border: 1px solid color-mix(in srgb, var(--preview-text) 22%, var(--preview-surface)); color: var(--preview-text); background: var(--preview-surface); }
.theme-preview-content i { display: block; width: 54%; height: 7px; background: var(--preview-text); opacity: .82; }
.theme-preview-content b { display: block; width: 34px; height: 18px; grid-row: 1 / 3; grid-column: 2; background: var(--preview-accent); }
.theme-preview-content em { display: block; width: 76%; height: 5px; background: var(--preview-text); opacity: .32; }
.theme-color-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
.theme-color-grid label { display: grid; grid-template-columns: minmax(0, 1fr) 32px auto; align-items: center; gap: 8px; min-width: 0; min-height: 38px; padding: 5px 7px; border: 1px solid var(--color-border-subtle); background: var(--color-surface-raised); }
.theme-color-grid label > span { color: var(--color-ink-muted); font-size: 10px; font-weight: 750; }
.theme-color-grid input[type="color"] { width: 32px; height: 25px; padding: 2px; border: 1px solid var(--color-border-input); background: transparent; cursor: pointer; }
.theme-color-grid code { color: var(--color-ink-faint); font: 9px var(--font-mono); text-transform: uppercase; }
.theme-generator-note { margin: -5px 0 0 !important; color: var(--color-ink-faint) !important; }
.settings-switch-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 14px; }
.settings-switch-row span { display: grid; gap: 3px; }
.settings-switch-row strong { color: var(--color-ink); font-size: 11px; }
@@ -569,6 +668,7 @@
.tool-rescan-button { align-self: flex-start; }
.general-settings-grid { grid-template-columns: 1fr; }
.general-setting-panel.general-setting-wide { grid-column: auto; }
.theme-color-grid { grid-template-columns: 1fr; }
.tool-config-panel { padding: 13px; }
.tool-usage-callout { grid-template-columns: 1fr; gap: 4px; }
}
+8
View File
@@ -13,8 +13,16 @@ export type CommitAiPhase = "idle" | "loading" | "ready" | "error";
export type CommitAiProvider = "local" | "openai" | "anthropic" | "custom";
export type CommitAiLocalProfile = "fast" | "balanced" | "detailed";
export type AppTheme = "system" | "light" | "dark";
export type AppAppearance = "modern" | "classic" | "custom";
export type AppLanguage = "en" | "de";
export interface CustomThemeColors {
background: string;
surface: string;
accent: string;
text: string;
}
export interface CommitAiStatus {
phase: CommitAiPhase;
model_id: string | null;