Compare commits

...
6 Commits
Author SHA1 Message Date
Christoph 4533f8aa38 style(theme): add dialog & panel CSS vars and refactor status panel styles
publish / Build and publish Ubuntu AppImage (release) Successful in 19m39s
publish / Build and publish Windows installer (release) Successful in 22m14s
publish / Build and publish AUR packages (release) Successful in 48m41s
Add CSS custom properties for dialog backdrop, shadows, and panel
highlights to centralize visual theming. Refactor the status panel
overlay to consume these tokens and use color-mix instead of hardcoded
rgba values. This improves visual consistency and lets overlays adapt
cleanly to appearance changes and theme variants.

- Centralize dialog and panel visuals with new CSS variables
- Replace fixed rgba values with tokenized colors and color-mix
- Move borders, shadows, and gradients to use the new theme tokens
2026-08-23 23:38:42 +02:00
Christoph f0bd74be4e Merge pull request 'feat(history): make branch filter groups collapsible' (#28) from new-desgin into main
publish / Build and publish Windows installer (release) Failing after 13s
publish / Build and publish AUR packages (release) Canceled after 0s
publish / Build and publish Ubuntu AppImage (release) Canceled after 54s
Reviewed-on: #28
2026-08-23 21:28:46 +00:00
Christoph 79f4ec21e4 feat(history): make branch filter groups collapsible
Add collapsible local and remote branch groups to the branch
visibility dialog, including toggle buttons and selection counts.
Introduce comprehensive dialog styling and responsive rules to match
the app UI. Opening the dialog now defaults to local open and remote
closed for faster access.

- Add CSS for branch-filter dialog layout, theming, and responsiveness
- Implement group toggles with chevrons and visible selected counts
- Default to local group open and remote group closed on dialog open
2026-08-23 23:20:13 +02:00
Christoph 44547f507a Merge pull request 'New desgin' (#27) from new-desgin into main
Reviewed-on: #27
2026-08-23 21:15:39 +00:00
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
6 changed files with 1162 additions and 107 deletions
+110 -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,102 @@
}
}
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-dialog-backdrop", "--app-dialog-shadow", "--app-panel-shadow", "--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-dialog-backdrop": `color-mix(in srgb, ${background} 72%, transparent)`,
"--app-dialog-shadow": `0 24px 68px color-mix(in srgb, ${text} 24%, transparent), 0 2px 12px color-mix(in srgb, ${text} 12%, transparent)`,
"--app-panel-shadow": `0 18px 48px color-mix(in srgb, ${text} 18%, transparent), inset 0 1px 0 color-mix(in srgb, ${surface} 88%, white)`,
"--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 +1260,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 +1279,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 +5705,8 @@
<AppSettingsDialog
analytics={analyticsSettings}
theme={appTheme}
appearance={appAppearance}
customTheme={customTheme}
language={appLanguage}
autoRefresh={autoRefreshEnabled}
externalTools={externalToolsSettings}
+862 -62
View File
File diff suppressed because it is too large Load Diff
+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; }
}
+37 -3
View File
@@ -117,6 +117,8 @@
let customVisibleBranches = $state<Set<string>>(new Set());
let loadedVisibilityRepository = $state("");
let branchDialogOpen = $state(false);
let localBranchGroupOpen = $state(true);
let remoteBranchGroupOpen = $state(false);
let expandedRefsCommitHash = $state("");
let panelElement = $state<HTMLElement | null>(null);
let contextCommit = $state<GitCommit | null>(null);
@@ -428,6 +430,8 @@
}
function openBranchDialog() {
localBranchGroupOpen = true;
remoteBranchGroupOpen = false;
branchDialogOpen = true;
}
@@ -1202,7 +1206,19 @@
<div class="branch-filter-dialog-list">
{#if localBranchNames.length > 0}
<span class="branch-filter-group-label">Local</span>
<section class="branch-filter-group">
<button
class="branch-filter-group-toggle"
type="button"
aria-expanded={localBranchGroupOpen}
onclick={() => { localBranchGroupOpen = !localBranchGroupOpen; }}
>
{#if localBranchGroupOpen}<ChevronDown size={14} aria-hidden="true" />{:else}<ChevronRight size={14} aria-hidden="true" />{/if}
<span>Local</span>
<em>{localBranchNames.filter(branchIsVisible).length}/{localBranchNames.length}</em>
</button>
{#if localBranchGroupOpen}
<div class="branch-filter-group-list">
{#each localBranchNames as branch}
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option" title={branch}>
<input
@@ -1214,11 +1230,26 @@
<span>{branch}</span>
</label>
{/each}
</div>
{/if}
</section>
{/if}
{#if remoteBranchNames.length > 0}
<span class="branch-filter-group-label">Remote</span>
<section class="branch-filter-group">
<button
class="branch-filter-group-toggle"
type="button"
aria-expanded={remoteBranchGroupOpen}
onclick={() => { remoteBranchGroupOpen = !remoteBranchGroupOpen; }}
>
{#if remoteBranchGroupOpen}<ChevronDown size={14} aria-hidden="true" />{:else}<ChevronRight size={14} aria-hidden="true" />{/if}
<span>Remote</span>
<em>{remoteBranchNames.filter(branchIsVisible).length}/{remoteBranchNames.length}</em>
</button>
{#if remoteBranchGroupOpen}
<div class="branch-filter-group-list">
{#each remoteBranchNames as branch}
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option remote" title={branch}>
<label class:muted={!branchIsVisible(branch)} class="branch-filter-option" title={branch}>
<input
type="checkbox"
checked={branchIsVisible(branch)}
@@ -1228,6 +1259,9 @@
<span>{branch}</span>
</label>
{/each}
</div>
{/if}
</section>
{/if}
</div>
</div>
+22 -17
View File
@@ -660,8 +660,12 @@
display: grid;
place-items: center;
background:
radial-gradient(circle at 50% 38%, rgba(111, 140, 255, 0.1), transparent 55%),
rgba(7, 10, 16, 0.62);
radial-gradient(
circle at 50% 38%,
color-mix(in srgb, var(--color-accent) 13%, transparent),
transparent 55%
),
var(--app-dialog-backdrop);
backdrop-filter: blur(4px);
animation: status-panel-overlay-in 120ms ease;
}
@@ -673,13 +677,13 @@
gap: 12px;
width: min(300px, calc(100% - 32px));
padding: 26px 28px 26px;
border: 1px solid rgba(90, 111, 154, 0.28);
border: 1px solid var(--color-border-input);
border-radius: 16px;
background:
linear-gradient(180deg, rgba(23, 29, 43, 0.92), rgba(12, 17, 27, 0.94)),
var(--color-surface-raised);
var(--app-panel-highlight),
var(--app-dialog-bg);
color: var(--color-ink);
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.05);
box-shadow: var(--app-dialog-shadow);
}
.status-panel-overlay-mark {
@@ -694,7 +698,7 @@
.status-panel-overlay-halo {
position: absolute;
inset: 6px;
border: 1px solid rgba(111, 140, 255, 0.22);
border: 1px solid color-mix(in srgb, var(--color-primary) 34%, transparent);
border-radius: 22px;
transform: rotate(45deg);
}
@@ -703,7 +707,7 @@
}
.status-panel-overlay-halo.halo-two {
inset: 16px;
border-color: rgba(77, 182, 214, 0.24);
border-color: color-mix(in srgb, var(--color-accent) 38%, transparent);
animation: status-panel-overlay-halo-breathe 2.4s ease-in-out infinite reverse;
}
@@ -721,16 +725,16 @@
stroke-linecap: round;
stroke-dasharray: 165;
stroke-dashoffset: 165;
filter: drop-shadow(0 0 6px rgba(77, 182, 214, 0.32));
filter: drop-shadow(0 0 6px color-mix(in srgb, var(--color-accent) 38%, transparent));
animation: status-panel-overlay-trace-draw 2.6s ease-in-out infinite;
}
.status-panel-overlay-traces .trace-main { stroke: #6f8cff; }
.status-panel-overlay-traces .trace-main { stroke: var(--color-primary); }
.status-panel-overlay-traces .trace-branch {
stroke: #4db6d6;
stroke: var(--color-accent);
animation-delay: 0.28s;
}
.status-panel-overlay-traces .trace-cut {
stroke: rgba(177, 186, 208, 0.42);
stroke: color-mix(in srgb, var(--color-ink-muted) 52%, transparent);
stroke-dasharray: 128;
stroke-dashoffset: 128;
animation-delay: 0.55s;
@@ -743,8 +747,8 @@
height: 64px;
object-fit: contain;
filter:
drop-shadow(0 8px 12px rgba(0, 0, 0, 0.5))
drop-shadow(0 0 10px rgba(77, 182, 214, 0.2));
drop-shadow(0 8px 12px color-mix(in srgb, var(--color-ink) 24%, transparent))
drop-shadow(0 0 10px color-mix(in srgb, var(--color-accent) 24%, transparent));
animation: status-panel-overlay-icon-float 2.4s ease-in-out infinite;
}
@@ -765,15 +769,16 @@
height: 4px;
overflow: hidden;
border-radius: 999px;
background: rgba(111, 140, 255, 0.14);
border: 1px solid var(--color-border-subtle);
background: color-mix(in srgb, var(--color-primary) 12%, var(--color-surface-dim));
}
.status-panel-overlay-bar span {
position: absolute;
inset: 0;
width: 46%;
border-radius: inherit;
background: linear-gradient(90deg, transparent, #6f8cff 40%, #4db6d6 74%, transparent);
box-shadow: 0 0 12px rgba(77, 182, 214, 0.26);
background: linear-gradient(90deg, transparent, var(--color-primary) 40%, var(--color-accent) 74%, transparent);
box-shadow: 0 0 12px color-mix(in srgb, var(--color-accent) 32%, transparent);
animation: status-panel-overlay-bar-slide 1.35s ease-in-out infinite;
}
+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;