diff --git a/src/App.svelte b/src/App.svelte index 6dd6fdb..e871891 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -98,6 +98,7 @@ import type { AiSettings, + AppTheme, AnalyticsSettings, CommitAiPhase, ConflictFile, @@ -165,6 +166,7 @@ const REPO_STATUS_CACHE_KEY = "gitlite.repoStatusCache.v1"; const AI_SETTINGS_KEY = "gitlite.aiSettings.v1"; const ANALYTICS_SETTINGS_KEY = "gitlite.analyticsSettings.v1"; + const APP_THEME_KEY = "gitlite.theme.v1"; const COMMIT_PANEL_HEIGHT_KEY = "gitlite.commitPanelHeight.v1"; const LEFT_SIDEBAR_WIDTH_KEY = "gitlite.leftSidebarWidth.v1"; const LEFT_BRANCH_PANEL_HEIGHT_KEY = "gitlite.leftBranchPanelHeight.v1"; @@ -239,6 +241,7 @@ let appSettingsOpen = false; let analyticsNoticeOpen = false; let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings(); + let appTheme: AppTheme = loadThemePreference(); let localModelOptions: LocalModelOption[] = []; let errorMessage = ""; let operation = ""; @@ -337,6 +340,7 @@ let fileHistoryResizeStartX = 0; let fileHistoryResizeStartWidth = 0; let fileHistoryCollapsed = true; + let themeMediaQuery: MediaQueryList | undefined; // ── Derived ──────────────────────────────────────────────────────────────── @@ -389,13 +393,18 @@ $: leftSidebarRows = buildLeftSidebarRows(branchPanelCollapsed, stashPanelCollapsed, explorerPanelCollapsed); $: allLeftPanelsCollapsed = branchPanelCollapsed && stashPanelCollapsed && explorerPanelCollapsed; + $: applyThemePreference(appTheme); + // ── Lifecycle ────────────────────────────────────────────────────────────── onMount(() => { + themeMediaQuery = window.matchMedia("(prefers-color-scheme: light)"); + themeMediaQuery.addEventListener("change", handleSystemThemeChange); void runStartupSequence(); }); onDestroy(() => { + themeMediaQuery?.removeEventListener("change", handleSystemThemeChange); if (autoRefreshTimer) clearInterval(autoRefreshTimer); if (backgroundRepoStatusTimer) clearInterval(backgroundRepoStatusTimer); if (backgroundFetchTimer) clearInterval(backgroundFetchTimer); @@ -754,11 +763,46 @@ trackEvent("app_started", { first_run: 1 }); } - function saveAppSettings(next: AnalyticsSettings) { + function loadThemePreference(): AppTheme { + try { + const stored = localStorage.getItem(APP_THEME_KEY); + if (stored === "system" || stored === "light" || stored === "dark") return stored; + } catch { + // Local storage is optional; system theme is a sane default. + } + return "system"; + } + + function persistThemePreference(next: AppTheme) { + try { + localStorage.setItem(APP_THEME_KEY, next); + } catch { + // Ignore storage quota/private-mode errors. + } + } + + function applyThemePreference(next: AppTheme) { + const prefersLight = themeMediaQuery?.matches ?? window.matchMedia("(prefers-color-scheme: light)").matches; + const resolved = next === "system" + ? prefersLight + ? "light" + : "dark" + : next; + document.documentElement.dataset.themePreference = next; + document.documentElement.dataset.theme = resolved; + } + + function handleSystemThemeChange() { + if (appTheme === "system") applyThemePreference(appTheme); + } + + function saveAppSettings(next: AnalyticsSettings, nextTheme: AppTheme) { analyticsSettings = next; + appTheme = nextTheme; persistAnalyticsSettings(next); + persistThemePreference(nextTheme); appSettingsOpen = false; - if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1 }); + if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme }); } function updateCommitMessage(message: string) { @@ -3932,6 +3976,7 @@ {#if appSettingsOpen} { appSettingsOpen = false; }} /> diff --git a/src/app.css b/src/app.css index 438e44a..71fcbe3 100644 --- a/src/app.css +++ b/src/app.css @@ -1,54 +1,112 @@ @import "tailwindcss"; @theme { - --color-ink: #f5f7ff; - --color-ink-muted: #a8b1d8; - --color-ink-faint: #687197; - --color-ink-dim: #838cb8; - --color-ink-quiet: #949bc8; + --color-ink: #edf3ff; + --color-ink-muted: #b1bad0; + --color-ink-faint: #6f7c96; + --color-ink-dim: #8895ad; + --color-ink-quiet: #98a4bb; - --color-surface: rgba(22, 22, 36, 0.82); - --color-surface-alt: #0b0b14; - --color-surface-dim: rgba(18, 18, 30, 0.9); - --color-surface-hover: rgba(47, 48, 78, 0.76); - --color-surface-raised: rgba(28, 29, 48, 0.88); - --color-surface-solid: #1c1d30; + --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-border: rgba(100, 108, 255, 0.28); - --color-border-subtle: rgba(255, 255, 255, 0.08); - --color-border-input: rgba(65, 209, 255, 0.26); + --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-primary: #646cff; - --color-primary-dark: #535bf2; - --color-accent: #41d1ff; + --color-primary: #6f8cff; + --color-primary-dark: #5d74e8; + --color-accent: #4db6d6; - --color-bar: rgba(9, 9, 18, 0.92); - --color-bar-text: #f4f6ff; - --color-bar-muted: #7b84b2; + --color-bar: rgba(7, 10, 16, 0.94); + --color-bar-text: #f1f5ff; + --color-bar-muted: #7f8ba3; --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-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: #070a10; + --app-dialog-bg: #101622; + --app-dialog-chrome: #141b29; + --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; +} + +:root[data-theme="light"] { + --color-ink: #172033; + --color-ink-muted: #475569; + --color-ink-faint: #728098; + --color-ink-dim: #5f6f89; + --color-ink-quiet: #66758d; + + --color-surface: rgba(255, 255, 255, 0.86); + --color-surface-alt: #f3f6fb; + --color-surface-dim: rgba(244, 247, 252, 0.92); + --color-surface-hover: rgba(226, 233, 245, 0.82); + --color-surface-raised: rgba(255, 255, 255, 0.94); + --color-surface-solid: #ffffff; + + --color-border: rgba(61, 89, 142, 0.24); + --color-border-subtle: rgba(34, 49, 78, 0.11); + --color-border-input: rgba(46, 117, 182, 0.28); + + --color-primary: #315fd6; + --color-primary-dark: #284cb4; + --color-accent: #0f8fb5; + + --color-bar: rgba(248, 251, 255, 0.94); + --color-bar-text: #172033; + --color-bar-muted: #64728a; + + --app-color-scheme: light; + --app-bg: + radial-gradient(circle at 12% 0%, rgba(49, 95, 214, 0.12), transparent 30%), + radial-gradient(circle at 82% 4%, rgba(15, 143, 181, 0.12), transparent 28%), + linear-gradient(135deg, #f7f9fd 0%, #eef3f9 48%, #f8fafc 100%); + --app-button-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(235, 240, 249, 0.95)); + --app-input-bg: rgba(255, 255, 255, 0.9); + --app-select-option-bg: #ffffff; + --app-panel-highlight: linear-gradient(180deg, rgba(255,255,255,0.72), transparent 70%); + --app-panel-shadow: 0 18px 48px rgba(28, 44, 74, 0.12), inset 0 1px 0 rgba(255,255,255,0.86); + --app-dialog-backdrop: rgba(232, 238, 248, 0.96); + --app-dialog-bg: #ffffff; + --app-dialog-chrome: #f4f7fc; + --app-dialog-shadow: 0 24px 72px rgba(28, 44, 74, 0.2), 0 2px 12px rgba(28,44,74,0.1); + --app-settings-row-bg: #f8fafd; + --app-scrollbar-thumb: #c2cada; + --app-scrollbar-thumb-hover: #aeb8cb; +} + @layer base { *, *::before, *::after { box-sizing: border-box; } html, body, #app { width: 100%; height: 100%; margin: 0; } - * { scrollbar-width: thin; scrollbar-color: #2a2f45 transparent; } + * { scrollbar-width: thin; scrollbar-color: var(--app-scrollbar-thumb) transparent; } ::-webkit-scrollbar { width: 5px; height: 5px; } ::-webkit-scrollbar-track { background: transparent; } - ::-webkit-scrollbar-thumb { background: #2a2f45; border-radius: 3px; } - ::-webkit-scrollbar-thumb:hover { background: #3a3f58; } + ::-webkit-scrollbar-thumb { background: var(--app-scrollbar-thumb); border-radius: 3px; } + ::-webkit-scrollbar-thumb:hover { background: var(--app-scrollbar-thumb-hover); } - html { color-scheme: dark; } + html { color-scheme: var(--app-color-scheme); } body { overflow: hidden; color: var(--color-ink); - background: - radial-gradient(circle at 22% 8%, rgba(189, 52, 254, 0.28), transparent 34%), - radial-gradient(circle at 74% 10%, rgba(65, 209, 255, 0.22), transparent 30%), - radial-gradient(circle at 88% 78%, rgba(255, 211, 67, 0.1), transparent 26%), - linear-gradient(135deg, #090912 0%, #111122 45%, #0b0b14 100%); + background: var(--app-bg); font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-synthesis: none; text-rendering: optimizeLegibility; @@ -68,7 +126,7 @@ border: 1px solid var(--color-border); border-radius: 8px; color: var(--color-ink-muted); - background: linear-gradient(180deg, rgba(42, 43, 70, 0.9), rgba(27, 28, 48, 0.92)); + background: var(--app-button-bg); cursor: pointer; transition: background 120ms ease, border-color 120ms ease, color 120ms ease; font-size: 13px; @@ -85,7 +143,7 @@ border: 1px solid var(--color-border-input); border-radius: 8px; color: var(--color-ink); - background: rgba(13, 14, 26, 0.78); + background: var(--app-input-bg); outline: none; } input:focus, textarea:focus, select:focus { @@ -117,7 +175,7 @@ border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(90, 140, 248, 0.18); } - select option { background: #1c2033; color: var(--color-ink); } + select option { background: var(--app-select-option-bg); color: var(--color-ink); } } @layer components { @@ -126,28 +184,28 @@ .btn-sm { min-height: 26px; padding: 0 8px; font-size: 12px; } .btn-primary { - border-color: rgba(100, 108, 255, 0.75); + border-color: rgba(111, 140, 255, 0.72); color: #ffffff; - background: linear-gradient(135deg, #646cff 0%, #bd34fe 100%); - box-shadow: 0 0 24px rgba(100, 108, 255, 0.2); + background: linear-gradient(135deg, #5f7df2 0%, #238eb4 100%); + box-shadow: 0 10px 24px rgba(77, 182, 214, 0.16); font-weight: 600; } .btn-primary:hover:not(:disabled) { - border-color: rgba(65, 209, 255, 0.82); - background: linear-gradient(135deg, #747bff 0%, #c966ff 100%); + border-color: rgba(77, 182, 214, 0.78); + background: linear-gradient(135deg, #6f8cff 0%, #2da0c7 100%); color: #ffffff; } .btn-secondary { - border-color: rgba(65, 209, 255, 0.32); - color: #b9e9ff; - background: linear-gradient(180deg, rgba(65, 209, 255, 0.12), rgba(100, 108, 255, 0.1)); + border-color: rgba(77, 182, 214, 0.3); + color: #b8e7f6; + background: linear-gradient(180deg, rgba(77, 182, 214, 0.1), rgba(111, 140, 255, 0.07)); font-weight: 700; } .btn-secondary:hover:not(:disabled) { - border-color: rgba(65, 209, 255, 0.64); + border-color: rgba(77, 182, 214, 0.58); color: #ffffff; - background: linear-gradient(180deg, rgba(65, 209, 255, 0.18), rgba(100, 108, 255, 0.16)); + background: linear-gradient(180deg, rgba(77, 182, 214, 0.16), rgba(111, 140, 255, 0.12)); } .btn-danger { @@ -169,9 +227,9 @@ border: 1px solid var(--color-border); border-radius: 12px; background: - linear-gradient(180deg, rgba(255,255,255,0.045), transparent 72%), + var(--app-panel-highlight), var(--color-surface); - box-shadow: 0 18px 60px rgba(0,0,0,0.24), inset 0 1px 0 rgba(255,255,255,0.05); + box-shadow: var(--app-panel-shadow); backdrop-filter: blur(16px); } @@ -183,7 +241,7 @@ min-height: 46px; padding: 8px 12px; border-bottom: 1px solid var(--color-border-subtle); - background: linear-gradient(90deg, rgba(100,108,255,0.09), rgba(65,209,255,0.025)); + background: linear-gradient(90deg, rgba(111,140,255,0.07), rgba(77,182,214,0.025)); } .section-head h2 { margin: 1px 0 0; color: var(--color-ink); font-size: 14px; line-height: 1.2; font-weight: 600; } @@ -466,7 +524,7 @@ align-items: center; height: var(--app-titlebar-height); background: - linear-gradient(90deg, rgba(100,108,255,0.16), rgba(65,209,255,0.08), rgba(255,211,67,0.035)), + linear-gradient(90deg, rgba(111,140,255,0.08), rgba(77,182,214,0.045), rgba(255,255,255,0.012)), var(--color-bar); color: var(--color-bar-text); user-select: none; @@ -518,9 +576,9 @@ height: 100%; overflow: hidden; } - .titlebar-info svg { color: #41d1ff; flex-shrink: 0; } + .titlebar-info svg { color: var(--color-accent); flex-shrink: 0; } - .tb-repo { color: #8e95c9; font-size: 12px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; } + .tb-repo { color: var(--color-bar-muted); font-size: 12px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; } .tb-sep { color: rgba(255,255,255,0.22); font-size: 13px; } .tb-branch { color: #f5f7ff; font-size: 12px; font-weight: 700; font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; } @@ -528,7 +586,7 @@ .tb-sync.ahead { color: #e0a040; background: rgba(224,160,64,0.13); } .tb-sync.behind { color: #7aacff; background: rgba(122,172,255,0.13); } - .tb-no-repo { color: #7b84b2; font-size: 12px; font-style: italic; } + .tb-no-repo { color: var(--color-bar-muted); font-size: 12px; font-style: italic; } .titlebar-right { display: flex; align-items: stretch; height: 100%; border-left: 1px solid rgba(255,255,255,0.06); } .titlebar-actions { display: flex; align-items: stretch; } @@ -549,7 +607,7 @@ transition: background 120ms ease, color 120ms ease; -webkit-app-region: no-drag; } - .tb-action:hover:not(:disabled) { background: rgba(255,255,255,0.07); border-color: transparent; color: #c0cce0; } + .tb-action:hover:not(:disabled) { background: rgba(255,255,255,0.06); border-color: transparent; color: #d6def0; } .tb-action:disabled { opacity: 0.3; cursor: not-allowed; } .tb-action.auto-toggle.active { color: #4eca76; } .tb-action.auto-toggle.active:hover:not(:disabled) { color: #6ee090; background: rgba(78,202,118,0.1); } @@ -570,22 +628,22 @@ border: none; border-radius: 0; background: transparent; - color: #3a4a5c; + color: #536078; cursor: pointer; transition: background 120ms ease, color 120ms ease; -webkit-app-region: no-drag; } - .tb-btn:hover:not(:disabled) { background: rgba(255,255,255,0.09); border-color: transparent; color: #c0cce0; } + .tb-btn:hover:not(:disabled) { background: rgba(255,255,255,0.08); border-color: transparent; color: #d6def0; } .tb-btn.close:hover:not(:disabled) { background: #c0392b; color: #ffffff; } /* --- Top bar (repo form) --- */ .topbar { padding: 9px 10px; - border: 1px solid rgba(100,108,255,0.28); + border: 1px solid var(--color-border); border-radius: 12px; background: - linear-gradient(90deg, rgba(189,52,254,0.1), rgba(65,209,255,0.075), rgba(255,211,67,0.035)), + linear-gradient(90deg, rgba(111,140,255,0.055), rgba(77,182,214,0.045), rgba(255,255,255,0.012)), var(--color-surface); box-shadow: inset 0 1px 0 rgba(255,255,255,0.05); backdrop-filter: blur(16px); @@ -1049,7 +1107,7 @@ inset: 0 auto 0 0; min-width: 0; border-radius: inherit; - background: linear-gradient(90deg, #41d1ff, #646cff, #bd34fe); + background: linear-gradient(90deg, #4db6d6, #6f8cff, #238eb4); transition: width 160ms ease; } .update-progress.indeterminate span { @@ -1077,14 +1135,14 @@ font-weight: 800; } .update-toast-primary { - border-color: rgba(100,108,255,0.72); + border-color: rgba(111,140,255,0.72); color: #ffffff; - background: linear-gradient(135deg, #646cff, #bd34fe); + background: linear-gradient(135deg, #5f7df2, #238eb4); } .update-toast-primary:hover:not(:disabled) { - border-color: rgba(65,209,255,0.74); + border-color: rgba(77,182,214,0.74); color: #ffffff; - background: linear-gradient(135deg, #747bff, #c966ff); + background: linear-gradient(135deg, #6f8cff, #2da0c7); } .update-toast-secondary { border-color: rgba(255,255,255,0.1); @@ -2308,7 +2366,7 @@ padding: 22px; overflow: hidden; background: - radial-gradient(circle at center 42%, rgba(151, 118, 255, 0.12), transparent 52%), + radial-gradient(circle at center 42%, rgba(111, 140, 255, 0.1), transparent 52%), linear-gradient(180deg, rgba(15, 18, 31, 0.55), transparent); } @@ -2324,7 +2382,7 @@ .file-history-loading-halo { position: absolute; inset: 10px; - border: 1px solid rgba(151, 118, 255, 0.24); + border: 1px solid rgba(111, 140, 255, 0.22); border-radius: 18px; transform: rotate(45deg); animation: fhl-halo-breathe 2.4s ease-in-out infinite; @@ -2344,14 +2402,14 @@ stroke-linecap: round; stroke-dasharray: 92; stroke-dashoffset: 92; - filter: drop-shadow(0 0 6px rgba(151, 118, 255, 0.5)); + filter: drop-shadow(0 0 6px rgba(77, 182, 214, 0.3)); animation: fhl-trace-draw 2.4s ease-in-out infinite; } .file-history-loading-traces .fhl-trace-main { - stroke: #9b70ff; + stroke: #6f8cff; } .file-history-loading-traces .fhl-trace-branch { - stroke: #ff6d26; + stroke: #4db6d6; animation-delay: 0.28s; } @@ -2363,7 +2421,7 @@ object-fit: contain; filter: drop-shadow(0 10px 14px rgba(0, 0, 0, 0.45)) - drop-shadow(0 0 12px rgba(151, 118, 255, 0.28)); + drop-shadow(0 0 12px rgba(77, 182, 214, 0.2)); animation: fhl-icon-float 2.4s ease-in-out infinite; } @@ -2379,15 +2437,15 @@ height: 3px; overflow: hidden; border-radius: 999px; - background: rgba(151, 118, 255, 0.14); + background: rgba(111, 140, 255, 0.14); } .file-history-loading-bar span { position: absolute; inset: 0; width: 46%; border-radius: inherit; - background: linear-gradient(90deg, transparent, #9b70ff 42%, #ff6d26 74%, transparent); - box-shadow: 0 0 12px rgba(255, 109, 38, 0.26); + background: linear-gradient(90deg, transparent, #6f8cff 42%, #4db6d6 74%, transparent); + box-shadow: 0 0 12px rgba(77, 182, 214, 0.24); animation: fhl-bar-slide 1.35s ease-in-out infinite; } @@ -2441,8 +2499,10 @@ position: relative; align-self: stretch; min-width: 42px; - border-right: 1px solid rgba(94,110,156,0.12); - background: rgba(7,8,16,0.2); + border-right: 1px solid rgba(226,232,240,0.08); + background: + linear-gradient(90deg, rgba(111,140,255,0.025), transparent), + rgba(7,10,16,0.24); } .graph-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; } .graph-svg path { @@ -2472,8 +2532,8 @@ height: 12px; border-radius: 999px; background: var(--dot-color, #5a8cf8); - border: 2px solid #111321; - box-shadow: 0 0 0 1px rgba(255,255,255,0.07); + border: 2px solid var(--app-dialog-bg); + box-shadow: 0 0 0 1px rgba(255,255,255,0.08), 0 2px 8px rgba(0,0,0,0.22); transform: translate(-50%, -50%); transition: transform 120ms ease, box-shadow 120ms ease; } @@ -2495,7 +2555,7 @@ height: 15px; background: var(--color-surface-alt); border-color: var(--dot-color, #5a8cf8); - box-shadow: 0 0 0 1px rgba(255,255,255,0.08); + box-shadow: 0 0 0 1px rgba(255,255,255,0.1); } .graph-hover-branches { position: absolute; @@ -2525,8 +2585,8 @@ border: 1px solid rgba(91,209,138,0.28); border-radius: 999px; color: #b2f0c2; - background: rgba(20,35,29,0.94); - box-shadow: 0 8px 22px rgba(0,0,0,0.3); + background: rgba(14,36,27,0.94); + box-shadow: 0 8px 22px rgba(0,0,0,0.24); font-family: var(--font-mono); font-size: 9.5px; font-weight: 750; @@ -2559,28 +2619,28 @@ gap: 5px; min-width: 0; padding: 8px 10px; - background: rgba(28,29,48,0.34); + background: rgba(18,24,36,0.5); transition: background 120ms ease; } - .graph-row + .graph-row .commit-body { border-top: 1px solid rgba(94,110,156,0.1); } + .graph-row + .graph-row .commit-body { border-top: 1px solid rgba(226,232,240,0.075); } .graph-row.graph-ahead-row .commit-body { box-shadow: inset 3px 0 0 rgba(224,160,64,0.72); } .graph-row.graph-behind-row .commit-body { box-shadow: inset 3px 0 0 rgba(122,172,255,0.72); } - .graph-row:hover .commit-body { background: rgba(37,40,62,0.54); } + .graph-row:hover .commit-body { background: rgba(30,39,57,0.72); } .graph-row:hover .graph-svg path { opacity: 1; stroke-width: 2.65; } .graph-row:hover .graph-svg path.hidden-branch { opacity: 0.12; stroke-width: 2.2; } .graph-row:hover .graph-dot { transform: translate(-50%, -50%) scale(1.12); } .graph-row:hover .graph-dot.hidden-branch { transform: translate(-50%, -50%) scale(1); } .graph-row.merge-row .commit-body { - background: rgba(36,31,54,0.46); + background: rgba(26,27,43,0.56); } .graph-row.tip-row .commit-body { background: - linear-gradient(90deg, rgba(105,167,255,0.055), transparent 32%), - rgba(28,29,48,0.38); + linear-gradient(90deg, rgba(111,140,255,0.065), transparent 34%), + rgba(20,27,40,0.62); } /* --- Compare panel --- */ @@ -2630,7 +2690,7 @@ display: grid; place-items: center; padding: 24px; - background-color: #050712; + background-color: var(--app-dialog-backdrop); background-image: none; } @@ -2645,9 +2705,9 @@ height: min(760px, 100%); border: 1px solid var(--color-border); border-radius: 12px; - background-color: #111321; + background-color: var(--app-dialog-bg); background-image: none; - box-shadow: 0 24px 72px rgba(0, 0, 0, 0.6), 0 2px 12px rgba(0,0,0,0.4); + box-shadow: var(--app-dialog-shadow); overflow: hidden; } .compare-dialog { @@ -2795,6 +2855,56 @@ font-size: 15px; font-weight: 750; } + .settings-segmented { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6px; + min-width: 0; + padding: 4px; + border: 1px solid var(--color-border-subtle); + border-radius: 8px; + background: var(--app-settings-row-bg); + } + .settings-segmented label { + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + min-height: 32px; + padding: 0 10px; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-ink-dim); + cursor: pointer; + font-size: 12px; + font-weight: 800; + transition: background 120ms ease, border-color 120ms ease, color 120ms ease; + } + .settings-segmented label:hover { + border-color: var(--color-border-subtle); + color: var(--color-ink); + background: var(--color-surface-hover); + } + .settings-segmented label.active { + border-color: var(--color-border-input); + color: var(--color-ink); + background: var(--color-surface-raised); + box-shadow: 0 1px 4px rgba(28,44,74,0.08); + } + .settings-segmented input { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + opacity: 0; + pointer-events: none; + } + .settings-segmented span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .settings-toggle-row { display: grid; grid-template-columns: auto minmax(0, 1fr); @@ -2804,7 +2914,7 @@ padding: 12px; border: 1px solid var(--color-border-subtle); border-radius: 8px; - background: #151827; + background: var(--app-settings-row-bg); color: var(--color-ink-muted); cursor: pointer; } @@ -2970,7 +3080,7 @@ gap: 8px; } - .dialog-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--color-border-subtle); background: #171a2b; } + .dialog-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); } .dialog-header > div:first-child { min-width: 0; } .dialog-header-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex: 0 0 auto; min-width: 0; } .compare-restore { max-width: 170px; min-width: 0; } @@ -2984,7 +3094,7 @@ .dialog-body { display: grid; grid-template-columns: 280px minmax(0, 1fr); min-height: 0; } .compare-dialog .dialog-body { grid-template-columns: minmax(260px, 320px) minmax(0, 1fr); } - .dialog-files { display: grid; align-content: start; gap: 4px; padding: 8px; overflow: auto; border-right: 1px solid var(--color-border-subtle); background: #171a2b; } + .dialog-files { display: grid; align-content: start; gap: 4px; padding: 8px; overflow: auto; border-right: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); } .dialog-file-row { display: grid; @@ -3132,7 +3242,7 @@ letter-spacing: 0; } - .dialog-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; border-top: 1px solid var(--color-border-subtle); background: #171a2b; } + .dialog-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; border-top: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); } .dialog-footer-info { color: var(--color-ink-dim); font-size: 13px; font-weight: 700; } .prepared-tag { display: inline-flex; align-items: center; gap: 4px; margin-right: auto; color: #4eca76; font-size: 12px; font-weight: 700; } @@ -4178,9 +4288,9 @@ gap: 6px; min-height: 36px; padding: 0 16px; - border: 1px solid rgba(100,108,255,0.78); + border: 1px solid rgba(111,140,255,0.72); border-radius: 7px; - background: linear-gradient(135deg, #646cff, #bd34fe); + background: linear-gradient(135deg, #5f7df2, #238eb4); color: #ffffff; font-size: 13px; font-weight: 700; @@ -4188,8 +4298,8 @@ transition: background 0.12s, border-color 0.12s; } .cred-submit:hover:not(:disabled) { - background: linear-gradient(135deg, #747bff, #c966ff); - border-color: rgba(65,209,255,0.7); + background: linear-gradient(135deg, #6f8cff, #2da0c7); + border-color: rgba(77,182,214,0.7); } .cred-submit:disabled { opacity: 0.45; cursor: not-allowed; } @@ -4388,6 +4498,381 @@ .resolve-path { overflow: hidden; color: var(--color-ink-faint); font-family: var(--font-mono); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; } } +:root[data-theme="light"] button:hover:not(:disabled) { + background: var(--color-surface-hover); +} + +:root[data-theme="light"] input:focus, +:root[data-theme="light"] textarea:focus, +:root[data-theme="light"] select:focus { + box-shadow: 0 0 0 3px rgba(49, 95, 214, 0.14), 0 10px 24px rgba(28, 44, 74, 0.06); +} + +:root[data-theme="light"] .btn-primary { + border-color: rgba(49, 95, 214, 0.72); + background: linear-gradient(135deg, #315fd6 0%, #0f8fb5 100%); + box-shadow: 0 10px 24px rgba(49, 95, 214, 0.18); +} + +:root[data-theme="light"] .btn-primary:hover:not(:disabled) { + border-color: rgba(15, 143, 181, 0.72); + background: linear-gradient(135deg, #3d6ee4 0%, #139dc7 100%); +} + +:root[data-theme="light"] .btn-secondary { + 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"] .btn-secondary:hover:not(:disabled) { + color: #0d4f9e; + background: linear-gradient(180deg, rgba(15, 143, 181, 0.13), rgba(49, 95, 214, 0.1)); +} + +:root[data-theme="light"] .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"] .titlebar-brand, +:root[data-theme="light"] .titlebar-right { + border-color: rgba(34,49,78,0.1); +} + +:root[data-theme="light"] .titlebar-brand-icon img { + filter: drop-shadow(0 1px 2px rgba(28,44,74,0.18)); +} + +:root[data-theme="light"] .tb-version { + color: rgba(23,32,51,0.36); +} + +:root[data-theme="light"] .titlebar-info svg { + color: var(--color-accent); +} + +:root[data-theme="light"] .tb-repo, +:root[data-theme="light"] .tb-no-repo { + color: var(--color-bar-muted); +} + +:root[data-theme="light"] .tb-sep { + color: rgba(23,32,51,0.28); +} + +:root[data-theme="light"] .tb-branch { + color: var(--color-bar-text); +} + +:root[data-theme="light"] .tb-action:hover:not(:disabled), +:root[data-theme="light"] .tb-btn:hover:not(:disabled) { + background: rgba(49,95,214,0.08); + color: var(--color-ink); +} + +:root[data-theme="light"] .titlebar-divider { + background: rgba(34,49,78,0.1); +} + +:root[data-theme="light"] .tb-btn { + color: var(--color-bar-muted); +} + +:root[data-theme="light"] .topbar { + border-color: var(--color-border); + background: + linear-gradient(90deg, rgba(49,95,214,0.06), rgba(15,143,181,0.045), rgba(214,156,49,0.035)), + var(--color-surface); +} + +:root[data-theme="light"] .repo-tabbar, +:root[data-theme="light"] .repo-management-tools { + background: rgba(255,255,255,0.78); +} + +:root[data-theme="light"] .repo-tab-wrap { + background: rgba(244,247,252,0.72); +} + +:root[data-theme="light"] .repo-tab-wrap.active { + background: rgba(49,95,214,0.1); +} + +:root[data-theme="light"] .repo-row { + border-bottom-color: rgba(34,49,78,0.08); +} + +:root[data-theme="light"] .repo-row-main:hover:not(:disabled) { + background: rgba(49,95,214,0.07); +} + +:root[data-theme="light"] .repo-row-icon { + border-left-color: rgba(34,49,78,0.08); +} + +:root[data-theme="light"] .notice.error, +:root[data-theme="light"] .cred-error { + color: #b33445; + background: rgba(210, 55, 75, 0.08); +} + +:root[data-theme="light"] .notice.busy { + color: #315fd6; +} + +:root[data-theme="light"] .notice.conflict, +:root[data-theme="light"] .notice-action.conflict, +:root[data-theme="light"] .resolve-status, +:root[data-theme="light"] .resolve-conflict-label { + color: #96620f; +} + +:root[data-theme="light"] .notice.rebase, +:root[data-theme="light"] .notice-action.rebase { + color: #6f3ca8; +} + +:root[data-theme="light"] .update-toast { + box-shadow: 0 22px 56px rgba(28,44,74,0.18), 0 0 0 1px rgba(255,255,255,0.75) inset; +} + +:root[data-theme="light"] .branch-filter-backdrop { + background: rgba(232,238,248,0.96); +} + +:root[data-theme="light"] .branch-filter-dialog { + background: + linear-gradient(180deg, #ffffff 0%, #f4f7fc 72%), + #f4f7fc; + box-shadow: var(--app-dialog-shadow); +} + +:root[data-theme="light"] .graph-branch-dialog-button, +:root[data-theme="light"] .branch-filter-option { + color: #16723b; +} + +:root[data-theme="light"] .graph-branch-dialog-button span { + color: #ffffff; + background: #22834a; +} + +:root[data-theme="light"] .main-panel { + background: var(--color-surface); + box-shadow: var(--app-panel-shadow); +} + +:root[data-theme="light"] .repo-management { + background: var(--color-surface); + box-shadow: var(--app-panel-shadow); +} + +:root[data-theme="light"] .repo-section { + background: rgba(255,255,255,0.62); +} + +:root[data-theme="light"] .repo-section > header { + background: rgba(244,247,252,0.88); +} + +:root[data-theme="light"] .repo-empty { + background: rgba(255,255,255,0.48); +} + +: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: var(--color-surface-dim); +} + +:root[data-theme="light"] .file-row.selected, +:root[data-theme="light"] .branch-row.current, +:root[data-theme="light"] .file-history-row.active { + background: rgba(49,95,214,0.08); +} + +:root[data-theme="light"] .graph-list { + background: rgba(248,250,253,0.9); +} + +:root[data-theme="light"] .graph-gutter { + border-right-color: rgba(34,49,78,0.1); + background: + linear-gradient(90deg, rgba(49,95,214,0.035), rgba(255,255,255,0.18)), + rgba(238,243,249,0.78); +} + +:root[data-theme="light"] .graph-dot { + border-color: #ffffff; + box-shadow: 0 0 0 1px rgba(34,49,78,0.16), 0 2px 6px rgba(28,44,74,0.12); +} + +:root[data-theme="light"] .graph-dot.merge { + background: #ffffff; + box-shadow: 0 0 0 1px rgba(34,49,78,0.14); +} + +:root[data-theme="light"] .commit-body { + background: rgba(255,255,255,0.72); +} + +:root[data-theme="light"] .graph-row + .graph-row .commit-body { + border-top-color: rgba(34,49,78,0.08); +} + +:root[data-theme="light"] .graph-row:hover .commit-body { + background: rgba(235,241,250,0.9); +} + +:root[data-theme="light"] .graph-row.merge-row .commit-body { + background: rgba(248,244,252,0.82); +} + +:root[data-theme="light"] .graph-row.tip-row .commit-body { + background: + linear-gradient(90deg, rgba(49,95,214,0.075), transparent 34%), + rgba(255,255,255,0.82); +} + +:root[data-theme="light"] .commit-summary { + color: var(--color-ink); +} + +:root[data-theme="light"] .commit-avatar { + border-color: rgba(49,95,214,0.18); + color: #315fd6; + background: rgba(232,238,248,0.92); +} + +:root[data-theme="light"] .commit-kind { + background: rgba(244,247,252,0.82); +} + +:root[data-theme="light"] .commit-hash { + color: #315fd6; +} + +:root[data-theme="light"] .commit-branch-chip, +:root[data-theme="light"] .graph-hover-branches span { + border-color: rgba(31,128,76,0.22); + color: #146b3b; + background: rgba(224,246,233,0.92); + box-shadow: 0 8px 22px rgba(28,44,74,0.12); +} + +:root[data-theme="light"] .graph-hover-branches span.remote, +:root[data-theme="light"] .branch-filter-option.remote { + border-color: rgba(49,95,214,0.22); + color: #315fd6; + background: rgba(231,237,255,0.92); +} + +:root[data-theme="light"] .graph-hover-branches span.ahead { + border-color: rgba(150,98,15,0.28); + color: #96620f; + background: rgba(255,244,224,0.95); +} + +:root[data-theme="light"] .graph-hover-branches span.behind { + border-color: rgba(49,95,214,0.26); + color: #315fd6; + background: rgba(231,237,255,0.95); +} + +:root[data-theme="light"] .ref-list .ref-chip.head { + color: #ffffff; + background: linear-gradient(135deg, #0f8fb5, #315fd6); +} + +:root[data-theme="light"] .ref-list .ref-chip.branch { + color: #16723b; + background: rgba(78,202,118,0.12); +} + +:root[data-theme="light"] .ref-list .ref-chip.remote { + color: #315fd6; + background: rgba(49,95,214,0.09); +} + +:root[data-theme="light"] .commit-files, +:root[data-theme="light"] .commit-file-button { + background: rgba(248,250,253,0.78); +} + +:root[data-theme="light"] .file-history-rail-toggle { + background: linear-gradient(180deg, rgba(49,95,214,0.08), rgba(15,143,181,0.04)); +} + +:root[data-theme="light"] .file-history-rail-toggle:hover:not(:disabled) { + background: linear-gradient(180deg, rgba(49,95,214,0.12), rgba(15,143,181,0.08)); +} + +:root[data-theme="light"] .file-history-loading { + background: + radial-gradient(circle at center 42%, rgba(49,95,214,0.1), transparent 52%), + linear-gradient(180deg, rgba(244,247,252,0.88), transparent); +} + +:root[data-theme="light"] .file-history-loading-icon { + filter: + drop-shadow(0 10px 14px rgba(28,44,74,0.18)) + drop-shadow(0 0 12px rgba(49,95,214,0.18)); +} + +:root[data-theme="light"] .branch-context-menu { + box-shadow: 0 18px 50px rgba(28,44,74,0.18); +} + +:root[data-theme="light"] .dialog-file-row:hover, +:root[data-theme="light"] .branch-row:hover, +:root[data-theme="light"] .explorer-row:hover, +:root[data-theme="light"] .commit-files-toggle:hover:not(:disabled) { + background: var(--color-surface-hover); +} + +:root[data-theme="light"] .split-span.split-meta { + background: #e9eef7; +} + +:root[data-theme="light"] .resolve-split, +:root[data-theme="light"] .resolve-num, +:root[data-theme="light"] .resolve-cell.empty, +:root[data-theme="light"] .resolve-num.empty { + background: rgba(234,239,248,0.72); +} + +:root[data-theme="light"] .diff-line.meta, +:root[data-theme="light"] .resolve-split-marker { + color: #64728a; +} + +:root[data-theme="light"] .cred-input input, +:root[data-theme="light"] .cred-expiry input[type="date"] { + background: rgba(255,255,255,0.92); + color-scheme: light; +} + +:root[data-theme="light"] .cred-token-hint code { + color: #0d78a0; +} + +:root[data-theme="light"] .status-panel-overlay { + background: + radial-gradient(circle at 50% 38%, rgba(49, 95, 214, 0.12), transparent 55%), + rgba(244, 247, 252, 0.68); +} + +:root[data-theme="light"] .status-panel-overlay-card { + background: + linear-gradient(180deg, rgba(255,255,255,0.96), rgba(244,247,252,0.94)), + var(--color-surface-raised); + box-shadow: 0 18px 44px rgba(28,44,74,0.16), inset 0 1px 0 rgba(255,255,255,0.9); +} + /* Custom tooltip — appended to body, not clipped by overflow:hidden parents */ .path-tooltip { position: fixed; @@ -4476,6 +4961,10 @@ @media (max-width: 740px) { body { overflow: auto; } .shell-body { min-height: 100%; gap: 6px; } + .titlebar-info { padding: 0 8px; } + .titlebar-actions .tb-action:not(.auto-toggle):not(:last-child) { display: none; } + .tb-action { width: 36px; padding: 0 8px; } + .tb-btn { width: 34px; } .workspace { grid-template-columns: 1fr; gap: 6px; } .left-sidebar-resize-handle { display: none; } .history-aside { grid-template-columns: 1fr; grid-template-rows: minmax(200px, 1fr) minmax(150px, 0.5fr); min-height: 380px; } diff --git a/src/lib/TitleBar.svelte b/src/lib/TitleBar.svelte index e505e47..3d14bea 100644 --- a/src/lib/TitleBar.svelte +++ b/src/lib/TitleBar.svelte @@ -24,22 +24,44 @@ export let onToggleAutoRefresh: () => void = () => {}; export let onOpenSettings: () => void = () => {}; - const win = getCurrentWindow(); + let win: ReturnType | null = null; let isMaximized = false; let unlisten: (() => void) | undefined; let appVersion = ""; onMount(async () => { - isMaximized = await win.isMaximized(); - unlisten = await win.onResized(async () => { + try { + win = getCurrentWindow(); isMaximized = await win.isMaximized(); - }); - appVersion = await getVersion(); + unlisten = await win.onResized(async () => { + isMaximized = await win?.isMaximized() ?? false; + }); + } catch { + win = null; + } + + try { + appVersion = await getVersion(); + } catch { + appVersion = ""; + } }); onDestroy(() => { unlisten?.(); }); + + function minimizeWindow() { + void win?.minimize(); + } + + function toggleMaximizeWindow() { + void win?.toggleMaximize(); + } + + function closeWindow() { + void win?.close(); + }
@@ -200,12 +222,12 @@
- -
diff --git a/src/lib/components/AppSettingsDialog.svelte b/src/lib/components/AppSettingsDialog.svelte index 099f5a4..345d936 100644 --- a/src/lib/components/AppSettingsDialog.svelte +++ b/src/lib/components/AppSettingsDialog.svelte @@ -1,19 +1,22 @@ @@ -38,6 +41,31 @@
{ event.preventDefault(); save(); }}> +
+
+
+ +
+ + + +
+
+