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
This commit is contained in:
2026-08-23 23:14:07 +02:00
parent 3477070ec3
commit 58027f5e22
4 changed files with 756 additions and 4 deletions
+540
View File
@@ -6831,6 +6831,75 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s
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; }
@@ -7983,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);
}