mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-19 08:52:05 -03:00
feat(theme): add 5 preset color themes (Nord/Gruvbox/Monokai/Dracula/Solarized) with popover selector
Implements Approach C (dual-attribute: data-theme + data-theme-preset), keeping all 106 existing [data-theme="dark"] overrides unchanged. - Colors: 5 professionally designed oklch palettes in tokens/colors.css - UI: popover theme selector with mode (Light/Dark/Auto) + preset grid - JS: cycleTheme(), setPreset(), localStorage persistence - Locale: 12 new translation keys across 10 languages - Polish: solid accent swatches matching flat token-driven aesthetic
This commit is contained in:
@@ -251,7 +251,18 @@
|
||||
"toggle": "Theme wechseln",
|
||||
"switchToLight": "Zu hellem Theme wechseln",
|
||||
"switchToDark": "Zu dunklem Theme wechseln",
|
||||
"switchToAuto": "Zu automatischem Theme wechseln"
|
||||
"switchToAuto": "Zu automatischem Theme wechseln",
|
||||
"presets": "Theme-Voreinstellungen",
|
||||
"default": "Standard",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "Modus",
|
||||
"light": "Hell",
|
||||
"dark": "Dunkel",
|
||||
"auto": "Auto"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "Updates prüfen",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "Toggle theme",
|
||||
"switchToLight": "Switch to light theme",
|
||||
"switchToDark": "Switch to dark theme",
|
||||
"switchToAuto": "Switch to auto theme"
|
||||
"switchToAuto": "Switch to auto theme",
|
||||
"presets": "Theme Presets",
|
||||
"default": "Default",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "Mode",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"auto": "Auto"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "Check Updates",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "Cambiar tema",
|
||||
"switchToLight": "Cambiar a tema claro",
|
||||
"switchToDark": "Cambiar a tema oscuro",
|
||||
"switchToAuto": "Cambiar a tema automático"
|
||||
"switchToAuto": "Cambiar a tema automático",
|
||||
"presets": "Preajustes de tema",
|
||||
"default": "Predeterminado",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "Modo",
|
||||
"light": "Claro",
|
||||
"dark": "Oscuro",
|
||||
"auto": "Auto"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "Comprobar actualizaciones",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "Basculer le thème",
|
||||
"switchToLight": "Passer au thème clair",
|
||||
"switchToDark": "Passer au thème sombre",
|
||||
"switchToAuto": "Passer au thème automatique"
|
||||
"switchToAuto": "Passer au thème automatique",
|
||||
"presets": "Préréglages de thème",
|
||||
"default": "Par défaut",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "Mode",
|
||||
"light": "Clair",
|
||||
"dark": "Sombre",
|
||||
"auto": "Auto"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "Vérifier les mises à jour",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "החלף ערכת נושא",
|
||||
"switchToLight": "עבור לערכת נושא בהירה",
|
||||
"switchToDark": "עבור לערכת נושא כהה",
|
||||
"switchToAuto": "עבור לערכת נושא אוטומטית"
|
||||
"switchToAuto": "עבור לערכת נושא אוטומטית",
|
||||
"presets": "ערכות נושא מוגדרות",
|
||||
"default": "ברירת מחדל",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "מצב",
|
||||
"light": "בהיר",
|
||||
"dark": "כהה",
|
||||
"auto": "אוטומטי"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "בדוק עדכונים",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "テーマの切り替え",
|
||||
"switchToLight": "ライトテーマに切り替え",
|
||||
"switchToDark": "ダークテーマに切り替え",
|
||||
"switchToAuto": "自動テーマに切り替え"
|
||||
"switchToAuto": "自動テーマに切り替え",
|
||||
"presets": "テーマプリセット",
|
||||
"default": "デフォルト",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "モード",
|
||||
"light": "ライト",
|
||||
"dark": "ダーク",
|
||||
"auto": "自動"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "更新確認",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "테마 토글",
|
||||
"switchToLight": "라이트 테마로 전환",
|
||||
"switchToDark": "다크 테마로 전환",
|
||||
"switchToAuto": "자동 테마로 전환"
|
||||
"switchToAuto": "자동 테마로 전환",
|
||||
"presets": "테마 프리셋",
|
||||
"default": "기본",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "모드",
|
||||
"light": "라이트",
|
||||
"dark": "다크",
|
||||
"auto": "자동"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "업데이트 확인",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "Переключить тему",
|
||||
"switchToLight": "Переключить на светлую тему",
|
||||
"switchToDark": "Переключить на тёмную тему",
|
||||
"switchToAuto": "Переключить на автоматическую тему"
|
||||
"switchToAuto": "Переключить на автоматическую тему",
|
||||
"presets": "Предустановки тем",
|
||||
"default": "По умолчанию",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "Режим",
|
||||
"light": "Светлый",
|
||||
"dark": "Тёмный",
|
||||
"auto": "Авто"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "Проверить обновления",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "切换主题",
|
||||
"switchToLight": "切换到浅色主题",
|
||||
"switchToDark": "切换到深色主题",
|
||||
"switchToAuto": "切换到自动主题"
|
||||
"switchToAuto": "切换到自动主题",
|
||||
"presets": "主题预设",
|
||||
"default": "默认",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "模式",
|
||||
"light": "浅色",
|
||||
"dark": "深色",
|
||||
"auto": "自动"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "检查更新",
|
||||
|
||||
@@ -251,7 +251,18 @@
|
||||
"toggle": "切換主題",
|
||||
"switchToLight": "切換至淺色主題",
|
||||
"switchToDark": "切換至深色主題",
|
||||
"switchToAuto": "自動主題"
|
||||
"switchToAuto": "自動主題",
|
||||
"presets": "主題預設",
|
||||
"default": "預設",
|
||||
"nord": "Nord",
|
||||
"gruvbox": "Gruvbox",
|
||||
"monokai": "Monokai",
|
||||
"dracula": "Dracula",
|
||||
"solarized": "Solarized",
|
||||
"mode": "模式",
|
||||
"light": "淺色",
|
||||
"dark": "深色",
|
||||
"auto": "自動"
|
||||
},
|
||||
"actions": {
|
||||
"checkUpdates": "檢查更新",
|
||||
|
||||
@@ -283,7 +283,6 @@
|
||||
|
||||
.theme-toggle {
|
||||
position: relative;
|
||||
/* Ensure relative positioning for the container */
|
||||
}
|
||||
|
||||
.theme-toggle .light-icon,
|
||||
@@ -293,17 +292,14 @@
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
/* Center perfectly */
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* Default state shows dark icon */
|
||||
.theme-toggle .dark-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Light theme shows light icon */
|
||||
.theme-toggle.theme-light .light-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -313,7 +309,6 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Dark theme shows dark icon */
|
||||
.theme-toggle.theme-dark .dark-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -323,7 +318,6 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Auto theme shows auto icon */
|
||||
.theme-toggle.theme-auto .auto-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -333,6 +327,203 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.theme-popover {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: -8px;
|
||||
background: var(--surface-base, #ffffff);
|
||||
border: 1px solid var(--border-base, #e0e0e0);
|
||||
border-radius: var(--radius-md, 8px);
|
||||
box-shadow: var(--shadow-xl, 0 4px 16px rgba(0, 0, 0, 0.15));
|
||||
padding: 12px;
|
||||
min-width: 220px;
|
||||
z-index: var(--z-dropdown, 200);
|
||||
animation: theme-popover-in 0.15s ease-out;
|
||||
}
|
||||
|
||||
.theme-popover.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes theme-popover-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-popover-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.theme-popover-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-secondary, #6c757d);
|
||||
}
|
||||
|
||||
.theme-popover-divider {
|
||||
height: 1px;
|
||||
background: var(--border-base, #e0e0e0);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.theme-popover-modes {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.theme-mode-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 4px;
|
||||
border: 1px solid var(--border-base, #e0e0e0);
|
||||
border-radius: var(--radius-sm, 6px);
|
||||
background: var(--surface-elevated, #ffffff);
|
||||
color: var(--text-primary, #333333);
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
transition: background-color var(--transition-base, 200ms ease),
|
||||
border-color var(--transition-base, 200ms ease),
|
||||
color var(--transition-base, 200ms ease);
|
||||
}
|
||||
|
||||
.theme-mode-btn i {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.theme-mode-btn:hover {
|
||||
background: var(--surface-hover, oklch(95% 0.02 256));
|
||||
border-color: var(--color-accent, oklch(68% 0.28 256));
|
||||
}
|
||||
|
||||
.theme-mode-btn.active {
|
||||
background: var(--color-accent-subtle, oklch(68% 0.28 256 / 0.12));
|
||||
border-color: var(--color-accent, oklch(68% 0.28 256));
|
||||
color: var(--color-accent, oklch(68% 0.28 256));
|
||||
}
|
||||
|
||||
.theme-popover-presets {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.theme-preset-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 4px;
|
||||
border: 1px solid var(--border-base, #e0e0e0);
|
||||
border-radius: var(--radius-sm, 6px);
|
||||
background: var(--surface-elevated, #ffffff);
|
||||
color: var(--text-primary, #333333);
|
||||
cursor: pointer;
|
||||
font-size: 0.7rem;
|
||||
transition: background-color var(--transition-base, 200ms ease),
|
||||
border-color var(--transition-base, 200ms ease),
|
||||
color var(--transition-base, 200ms ease);
|
||||
}
|
||||
|
||||
.theme-preset-btn:hover {
|
||||
background: var(--surface-hover, oklch(95% 0.02 256));
|
||||
border-color: var(--color-accent, oklch(68% 0.28 256));
|
||||
}
|
||||
|
||||
.theme-preset-btn.active {
|
||||
background: var(--color-accent-subtle, oklch(68% 0.28 256 / 0.12));
|
||||
border-color: var(--color-accent, oklch(68% 0.28 256));
|
||||
color: var(--color-accent, oklch(68% 0.28 256));
|
||||
}
|
||||
|
||||
.preset-swatch {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: var(--radius-xs, 4px);
|
||||
border: 1px solid var(--border-subtle, oklch(72% 0.03 256 / 0.45));
|
||||
flex-shrink: 0;
|
||||
transition: transform var(--transition-base, 200ms ease),
|
||||
box-shadow var(--transition-base, 200ms ease);
|
||||
}
|
||||
|
||||
/* Solid accent colors — each swatch shows the theme's accent color directly.
|
||||
This matches the app's flat, token-driven design language instead of using
|
||||
decorative gradients that clash with the matte aesthetic. */
|
||||
|
||||
.preset-swatch-default {
|
||||
background: oklch(68% 0.28 256);
|
||||
}
|
||||
|
||||
.preset-swatch-nord {
|
||||
background: oklch(62% 0.18 213);
|
||||
}
|
||||
|
||||
.preset-swatch-gruvbox {
|
||||
background: oklch(58% 0.22 25);
|
||||
}
|
||||
|
||||
.preset-swatch-monokai {
|
||||
background: oklch(72% 0.24 190);
|
||||
}
|
||||
|
||||
.preset-swatch-dracula {
|
||||
background: oklch(68% 0.24 265);
|
||||
}
|
||||
|
||||
.preset-swatch-solarized {
|
||||
background: oklch(55% 0.18 175);
|
||||
}
|
||||
|
||||
.theme-preset-btn.active .preset-swatch {
|
||||
box-shadow: 0 0 0 2px var(--color-accent, oklch(68% 0.28 256));
|
||||
}
|
||||
|
||||
.theme-preset-btn:hover .preset-swatch {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
/* Dark mode: use each preset's dark-mode accent lightness for visibility.
|
||||
These match the --color-accent-l values from [data-theme="dark"][data-theme-preset="..."]
|
||||
in tokens/colors.css so the swatch accurately previews what the theme looks like. */
|
||||
|
||||
[data-theme="dark"] .preset-swatch-default {
|
||||
background: oklch(68% 0.28 256);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .preset-swatch-nord {
|
||||
background: oklch(68% 0.18 213);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .preset-swatch-gruvbox {
|
||||
background: oklch(62% 0.22 25);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .preset-swatch-monokai {
|
||||
background: oklch(72% 0.24 190);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .preset-swatch-dracula {
|
||||
background: oklch(72% 0.24 265);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .preset-swatch-solarized {
|
||||
background: oklch(60% 0.18 175);
|
||||
}
|
||||
|
||||
/* Badge styling */
|
||||
.update-badge {
|
||||
position: absolute;
|
||||
|
||||
@@ -115,3 +115,313 @@
|
||||
|
||||
--favorite-color: #ffc107;
|
||||
}
|
||||
|
||||
/* ── Preset: Nord ──────────────────────────────────────────── */
|
||||
|
||||
[data-theme-preset="nord"] {
|
||||
--color-accent-h: 213;
|
||||
--color-accent-c: 0.18;
|
||||
--color-accent-l: 62%;
|
||||
--color-warning-h: 35;
|
||||
--color-warning-c: 0.18;
|
||||
--color-success-h: 130;
|
||||
--color-error-h: 5;
|
||||
|
||||
--bg-base: oklch(96% 0.01 240);
|
||||
--bg-elevated: oklch(98% 0.008 240 / 0.95);
|
||||
--bg-hover: oklch(93% 0.02 240);
|
||||
--bg-disabled: oklch(92% 0.01 240);
|
||||
|
||||
--text-primary: oklch(22% 0.03 260);
|
||||
--text-secondary: oklch(48% 0.03 260);
|
||||
--text-inverse: oklch(97% 0.01 240);
|
||||
|
||||
--surface-base: oklch(97% 0.01 240);
|
||||
--surface-elevated: oklch(98% 0.008 240 / 0.95);
|
||||
--surface-hover: oklch(93% 0.02 240);
|
||||
--surface-subtle: oklch(0% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(82% 0.03 240);
|
||||
--border-subtle: oklch(82% 0.03 240 / 0.45);
|
||||
|
||||
--favorite-color: oklch(72% 0.14 85);
|
||||
--favorite-glow: oklch(72% 0.14 85 / 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"][data-theme-preset="nord"] {
|
||||
--color-accent-h: 213;
|
||||
--color-accent-c: 0.18;
|
||||
--color-accent-l: 68%;
|
||||
--color-warning-h: 35;
|
||||
--color-warning-c: 0.18;
|
||||
--color-success-h: 130;
|
||||
--color-error-h: 5;
|
||||
|
||||
--bg-base: oklch(20% 0.03 260);
|
||||
--bg-elevated: oklch(24% 0.03 260 / 0.98);
|
||||
--bg-hover: oklch(30% 0.03 260);
|
||||
--bg-disabled: oklch(30% 0.02 260);
|
||||
|
||||
--text-primary: oklch(87% 0.02 240);
|
||||
--text-secondary: oklch(68% 0.02 240);
|
||||
--text-inverse: oklch(20% 0.03 260);
|
||||
|
||||
--surface-base: oklch(26% 0.03 260);
|
||||
--surface-elevated: oklch(24% 0.03 260 / 0.98);
|
||||
--surface-hover: oklch(30% 0.03 260);
|
||||
--surface-subtle: oklch(100% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(38% 0.03 260);
|
||||
--border-subtle: oklch(87% 0.02 240 / 0.15);
|
||||
|
||||
--favorite-color: oklch(78% 0.15 85);
|
||||
--favorite-glow: oklch(78% 0.15 85 / 0.5);
|
||||
}
|
||||
|
||||
/* ── Preset: Gruvbox ───────────────────────────────────────── */
|
||||
|
||||
[data-theme-preset="gruvbox"] {
|
||||
--color-accent-h: 25;
|
||||
--color-accent-c: 0.22;
|
||||
--color-accent-l: 58%;
|
||||
--color-warning-h: 45;
|
||||
--color-warning-c: 0.22;
|
||||
--color-success-h: 120;
|
||||
--color-error-h: 4;
|
||||
|
||||
--bg-base: oklch(95% 0.02 80);
|
||||
--bg-elevated: oklch(97% 0.015 80 / 0.95);
|
||||
--bg-hover: oklch(91% 0.03 80);
|
||||
--bg-disabled: oklch(90% 0.02 80);
|
||||
|
||||
--text-primary: oklch(28% 0.03 55);
|
||||
--text-secondary: oklch(48% 0.03 55);
|
||||
--text-inverse: oklch(95% 0.02 80);
|
||||
|
||||
--surface-base: oklch(96% 0.015 80);
|
||||
--surface-elevated: oklch(97% 0.015 80 / 0.95);
|
||||
--surface-hover: oklch(91% 0.03 80);
|
||||
--surface-subtle: oklch(0% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(78% 0.04 75);
|
||||
--border-subtle: oklch(78% 0.04 75 / 0.45);
|
||||
|
||||
--favorite-color: oklch(72% 0.16 75);
|
||||
--favorite-glow: oklch(72% 0.16 75 / 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"][data-theme-preset="gruvbox"] {
|
||||
--color-accent-h: 25;
|
||||
--color-accent-c: 0.22;
|
||||
--color-accent-l: 62%;
|
||||
--color-warning-h: 45;
|
||||
--color-warning-c: 0.22;
|
||||
--color-success-h: 120;
|
||||
--color-error-h: 4;
|
||||
|
||||
--bg-base: oklch(22% 0.02 55);
|
||||
--bg-elevated: oklch(26% 0.025 55 / 0.98);
|
||||
--bg-hover: oklch(32% 0.03 55);
|
||||
--bg-disabled: oklch(30% 0.02 55);
|
||||
|
||||
--text-primary: oklch(85% 0.03 75);
|
||||
--text-secondary: oklch(68% 0.03 75);
|
||||
--text-inverse: oklch(22% 0.02 55);
|
||||
|
||||
--surface-base: oklch(28% 0.025 55);
|
||||
--surface-elevated: oklch(26% 0.025 55 / 0.98);
|
||||
--surface-hover: oklch(32% 0.03 55);
|
||||
--surface-subtle: oklch(100% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(38% 0.03 55);
|
||||
--border-subtle: oklch(85% 0.03 75 / 0.15);
|
||||
|
||||
--favorite-color: oklch(78% 0.16 75);
|
||||
--favorite-glow: oklch(78% 0.16 75 / 0.5);
|
||||
}
|
||||
|
||||
/* ── Preset: Monokai ───────────────────────────────────────── */
|
||||
|
||||
[data-theme-preset="monokai"] {
|
||||
--color-accent-h: 190;
|
||||
--color-accent-c: 0.24;
|
||||
--color-accent-l: 72%;
|
||||
--color-warning-h: 50;
|
||||
--color-warning-c: 0.22;
|
||||
--color-success-h: 140;
|
||||
--color-error-h: 340;
|
||||
|
||||
--bg-base: oklch(96% 0.01 80);
|
||||
--bg-elevated: oklch(98% 0.005 80 / 0.95);
|
||||
--bg-hover: oklch(93% 0.015 80);
|
||||
--bg-disabled: oklch(92% 0.01 80);
|
||||
|
||||
--text-primary: oklch(20% 0.02 100);
|
||||
--text-secondary: oklch(45% 0.02 100);
|
||||
--text-inverse: oklch(97% 0.01 80);
|
||||
|
||||
--surface-base: oklch(97% 0.008 80);
|
||||
--surface-elevated: oklch(98% 0.005 80 / 0.95);
|
||||
--surface-hover: oklch(93% 0.015 80);
|
||||
--surface-subtle: oklch(0% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(80% 0.02 80);
|
||||
--border-subtle: oklch(80% 0.02 80 / 0.45);
|
||||
|
||||
--favorite-color: oklch(72% 0.16 85);
|
||||
--favorite-glow: oklch(72% 0.16 85 / 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"][data-theme-preset="monokai"] {
|
||||
--color-accent-h: 190;
|
||||
--color-accent-c: 0.24;
|
||||
--color-accent-l: 72%;
|
||||
--color-warning-h: 50;
|
||||
--color-warning-c: 0.22;
|
||||
--color-success-h: 140;
|
||||
--color-error-h: 340;
|
||||
|
||||
--bg-base: oklch(18% 0.02 100);
|
||||
--bg-elevated: oklch(22% 0.02 100 / 0.98);
|
||||
--bg-hover: oklch(28% 0.025 100);
|
||||
--bg-disabled: oklch(28% 0.015 100);
|
||||
|
||||
--text-primary: oklch(90% 0.02 80);
|
||||
--text-secondary: oklch(70% 0.02 80);
|
||||
--text-inverse: oklch(18% 0.02 100);
|
||||
|
||||
--surface-base: oklch(24% 0.02 100);
|
||||
--surface-elevated: oklch(22% 0.02 100 / 0.98);
|
||||
--surface-hover: oklch(28% 0.025 100);
|
||||
--surface-subtle: oklch(100% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(36% 0.02 100);
|
||||
--border-subtle: oklch(90% 0.02 80 / 0.15);
|
||||
|
||||
--favorite-color: oklch(78% 0.16 85);
|
||||
--favorite-glow: oklch(78% 0.16 85 / 0.5);
|
||||
}
|
||||
|
||||
/* ── Preset: Dracula ───────────────────────────────────────── */
|
||||
|
||||
[data-theme-preset="dracula"] {
|
||||
--color-accent-h: 265;
|
||||
--color-accent-c: 0.24;
|
||||
--color-accent-l: 68%;
|
||||
--color-warning-h: 45;
|
||||
--color-warning-c: 0.22;
|
||||
--color-success-h: 135;
|
||||
--color-error-h: 350;
|
||||
|
||||
--bg-base: oklch(96% 0.01 290);
|
||||
--bg-elevated: oklch(98% 0.008 290 / 0.95);
|
||||
--bg-hover: oklch(93% 0.02 290);
|
||||
--bg-disabled: oklch(92% 0.01 290);
|
||||
|
||||
--text-primary: oklch(22% 0.04 290);
|
||||
--text-secondary: oklch(48% 0.04 290);
|
||||
--text-inverse: oklch(97% 0.01 290);
|
||||
|
||||
--surface-base: oklch(97% 0.01 290);
|
||||
--surface-elevated: oklch(98% 0.008 290 / 0.95);
|
||||
--surface-hover: oklch(93% 0.02 290);
|
||||
--surface-subtle: oklch(0% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(80% 0.04 290);
|
||||
--border-subtle: oklch(80% 0.04 290 / 0.45);
|
||||
|
||||
--favorite-color: oklch(72% 0.16 85);
|
||||
--favorite-glow: oklch(72% 0.16 85 / 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"][data-theme-preset="dracula"] {
|
||||
--color-accent-h: 265;
|
||||
--color-accent-c: 0.24;
|
||||
--color-accent-l: 72%;
|
||||
--color-warning-h: 45;
|
||||
--color-warning-c: 0.22;
|
||||
--color-success-h: 135;
|
||||
--color-error-h: 350;
|
||||
|
||||
--bg-base: oklch(18% 0.04 290);
|
||||
--bg-elevated: oklch(22% 0.04 290 / 0.98);
|
||||
--bg-hover: oklch(28% 0.04 290);
|
||||
--bg-disabled: oklch(28% 0.03 290);
|
||||
|
||||
--text-primary: oklch(90% 0.02 290);
|
||||
--text-secondary: oklch(70% 0.03 290);
|
||||
--text-inverse: oklch(18% 0.04 290);
|
||||
|
||||
--surface-base: oklch(24% 0.04 290);
|
||||
--surface-elevated: oklch(22% 0.04 290 / 0.98);
|
||||
--surface-hover: oklch(28% 0.04 290);
|
||||
--surface-subtle: oklch(100% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(36% 0.04 290);
|
||||
--border-subtle: oklch(90% 0.02 290 / 0.15);
|
||||
|
||||
--favorite-color: oklch(78% 0.16 85);
|
||||
--favorite-glow: oklch(78% 0.16 85 / 0.5);
|
||||
}
|
||||
|
||||
/* ── Preset: Solarized ─────────────────────────────────────── */
|
||||
|
||||
[data-theme-preset="solarized"] {
|
||||
--color-accent-h: 175;
|
||||
--color-accent-c: 0.18;
|
||||
--color-accent-l: 55%;
|
||||
--color-warning-h: 45;
|
||||
--color-warning-c: 0.20;
|
||||
--color-success-h: 68;
|
||||
--color-error-h: 25;
|
||||
|
||||
--bg-base: oklch(95% 0.03 85);
|
||||
--bg-elevated: oklch(97% 0.025 85 / 0.95);
|
||||
--bg-hover: oklch(91% 0.035 85);
|
||||
--bg-disabled: oklch(90% 0.025 85);
|
||||
|
||||
--text-primary: oklch(30% 0.06 200);
|
||||
--text-secondary: oklch(50% 0.04 200);
|
||||
--text-inverse: oklch(95% 0.03 85);
|
||||
|
||||
--surface-base: oklch(96% 0.025 85);
|
||||
--surface-elevated: oklch(97% 0.025 85 / 0.95);
|
||||
--surface-hover: oklch(91% 0.035 85);
|
||||
--surface-subtle: oklch(0% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(78% 0.04 85);
|
||||
--border-subtle: oklch(78% 0.04 85 / 0.45);
|
||||
|
||||
--favorite-color: oklch(68% 0.16 75);
|
||||
--favorite-glow: oklch(68% 0.16 75 / 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"][data-theme-preset="solarized"] {
|
||||
--color-accent-h: 175;
|
||||
--color-accent-c: 0.18;
|
||||
--color-accent-l: 60%;
|
||||
--color-warning-h: 45;
|
||||
--color-warning-c: 0.20;
|
||||
--color-success-h: 68;
|
||||
--color-error-h: 25;
|
||||
|
||||
--bg-base: oklch(18% 0.05 200);
|
||||
--bg-elevated: oklch(22% 0.05 200 / 0.98);
|
||||
--bg-hover: oklch(28% 0.05 200);
|
||||
--bg-disabled: oklch(28% 0.04 200);
|
||||
|
||||
--text-primary: oklch(72% 0.03 85);
|
||||
--text-secondary: oklch(58% 0.03 85);
|
||||
--text-inverse: oklch(18% 0.05 200);
|
||||
|
||||
--surface-base: oklch(24% 0.05 200);
|
||||
--surface-elevated: oklch(22% 0.05 200 / 0.98);
|
||||
--surface-hover: oklch(28% 0.05 200);
|
||||
--surface-subtle: oklch(100% 0 0 / 0.03);
|
||||
|
||||
--border-base: oklch(36% 0.04 200);
|
||||
--border-subtle: oklch(72% 0.03 85 / 0.15);
|
||||
|
||||
--favorite-color: oklch(72% 0.16 75);
|
||||
--favorite-glow: oklch(72% 0.16 75 / 0.5);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { updateService } from '../managers/UpdateService.js';
|
||||
import { toggleTheme } from '../utils/uiHelpers.js';
|
||||
import { toggleTheme, setPreset, CYCLE_ORDER, PRESET_NAMES } from '../utils/uiHelpers.js';
|
||||
import { SearchManager } from '../managers/SearchManager.js';
|
||||
import { FilterManager } from '../managers/FilterManager.js';
|
||||
import { initPageState } from '../state/index.js';
|
||||
import { getStorageItem } from '../utils/storageHelpers.js';
|
||||
import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
|
||||
import { updateElementAttribute } from '../utils/i18nHelpers.js';
|
||||
import { renderSupporters } from '../services/supportersService.js';
|
||||
|
||||
@@ -47,25 +47,8 @@ export class HeaderManager {
|
||||
}
|
||||
|
||||
initializeCommonElements() {
|
||||
// Handle theme toggle
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
if (themeToggle) {
|
||||
const currentTheme = getStorageItem('theme') || 'auto';
|
||||
themeToggle.classList.add(`theme-${currentTheme}`);
|
||||
this.initializeThemePopover();
|
||||
|
||||
// Use i18nHelpers to update themeToggle's title
|
||||
this.updateThemeTooltip(themeToggle, currentTheme);
|
||||
|
||||
themeToggle.addEventListener('click', async () => {
|
||||
if (typeof toggleTheme === 'function') {
|
||||
const newTheme = toggleTheme();
|
||||
// Use i18nHelpers to update themeToggle's title
|
||||
this.updateThemeTooltip(themeToggle, newTheme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle settings toggle
|
||||
const settingsToggle = document.querySelector('.settings-toggle');
|
||||
if (settingsToggle) {
|
||||
settingsToggle.addEventListener('click', () => {
|
||||
@@ -74,22 +57,19 @@ export class HeaderManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle update toggle
|
||||
|
||||
const updateToggle = document.getElementById('updateToggleBtn');
|
||||
if (updateToggle) {
|
||||
updateToggle.addEventListener('click', () => {
|
||||
updateService.toggleUpdateModal();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle support toggle
|
||||
|
||||
const supportToggle = document.getElementById('supportToggleBtn');
|
||||
if (supportToggle) {
|
||||
supportToggle.addEventListener('click', async () => {
|
||||
if (window.modalManager) {
|
||||
window.modalManager.toggleModal('supportModal');
|
||||
// Load supporters data when modal opens
|
||||
try {
|
||||
await renderSupporters();
|
||||
} catch (error) {
|
||||
@@ -99,41 +79,126 @@ export class HeaderManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Handle QR code toggle
|
||||
const qrToggle = document.getElementById('toggleQRCode');
|
||||
const qrContainer = document.getElementById('qrCodeContainer');
|
||||
|
||||
if (qrToggle && qrContainer) {
|
||||
qrToggle.addEventListener('click', function() {
|
||||
qrContainer.classList.toggle('show');
|
||||
qrToggle.classList.toggle('active');
|
||||
|
||||
const toggleText = qrToggle.querySelector('.toggle-text');
|
||||
if (qrContainer.classList.contains('show')) {
|
||||
toggleText.textContent = 'Hide WeChat QR Code';
|
||||
// Add small delay to ensure DOM is updated before scrolling
|
||||
setTimeout(() => {
|
||||
const supportModal = document.querySelector('.support-modal');
|
||||
if (supportModal) {
|
||||
supportModal.scrollTo({
|
||||
top: supportModal.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}, 250);
|
||||
} else {
|
||||
toggleText.textContent = 'Show WeChat QR Code';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hide search functionality on Statistics page
|
||||
this.updateHeaderForPage();
|
||||
|
||||
// Initialize hamburger menu for mobile
|
||||
if (qrToggle && qrContainer) {
|
||||
qrToggle.addEventListener('click', function () {
|
||||
qrContainer.classList.toggle('show');
|
||||
qrToggle.classList.toggle('active');
|
||||
|
||||
const toggleText = qrToggle.querySelector('.toggle-text');
|
||||
if (qrContainer.classList.contains('show')) {
|
||||
toggleText.textContent = 'Hide WeChat QR Code';
|
||||
setTimeout(() => {
|
||||
const supportModal = document.querySelector('.support-modal');
|
||||
if (supportModal) {
|
||||
supportModal.scrollTo({
|
||||
top: supportModal.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}, 250);
|
||||
} else {
|
||||
toggleText.textContent = 'Show WeChat QR Code';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.updateHeaderForPage();
|
||||
this.initializeHamburgerMenu();
|
||||
}
|
||||
|
||||
initializeThemePopover() {
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
const themePopover = document.getElementById('themePopover');
|
||||
if (!themeToggle || !themePopover) return;
|
||||
|
||||
const currentTheme = getStorageItem('theme') || 'auto';
|
||||
const currentPreset = getStorageItem('theme_preset') || 'default';
|
||||
themeToggle.classList.add(`theme-${currentTheme}`);
|
||||
this.updateThemeTooltip(themeToggle, currentTheme);
|
||||
this.updatePopoverActiveStates(currentTheme, currentPreset);
|
||||
|
||||
themeToggle.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.theme-popover')) return;
|
||||
e.stopPropagation();
|
||||
const isOpen = themePopover.classList.contains('active');
|
||||
this.closeAllPopovers();
|
||||
if (!isOpen) {
|
||||
themePopover.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
themePopover.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const modeBtn = e.target.closest('.theme-mode-btn');
|
||||
const presetBtn = e.target.closest('.theme-preset-btn');
|
||||
|
||||
if (modeBtn) {
|
||||
const mode = modeBtn.dataset.mode;
|
||||
this.setThemeMode(mode);
|
||||
} else if (presetBtn) {
|
||||
const preset = presetBtn.dataset.preset;
|
||||
this.setThemePreset(preset);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!themeToggle.contains(e.target) && !themePopover.contains(e.target)) {
|
||||
themePopover.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeAllPopovers() {
|
||||
const themePopover = document.getElementById('themePopover');
|
||||
if (themePopover) {
|
||||
themePopover.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
setThemeMode(mode) {
|
||||
setStorageItem('theme', mode);
|
||||
const htmlElement = document.documentElement;
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
htmlElement.removeAttribute('data-theme');
|
||||
if (mode === 'dark' || (mode === 'auto' && prefersDark)) {
|
||||
htmlElement.setAttribute('data-theme', 'dark');
|
||||
document.body.dataset.theme = 'dark';
|
||||
} else {
|
||||
htmlElement.setAttribute('data-theme', 'light');
|
||||
document.body.dataset.theme = 'light';
|
||||
}
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
if (themeToggle) {
|
||||
themeToggle.classList.remove('theme-light', 'theme-dark', 'theme-auto');
|
||||
themeToggle.classList.add(`theme-${mode}`);
|
||||
this.updateThemeTooltip(themeToggle, mode);
|
||||
}
|
||||
this.updateHamburgerThemeIcon();
|
||||
this.updatePopoverActiveStates(mode, getStorageItem('theme_preset') || 'default');
|
||||
}
|
||||
|
||||
setThemePreset(preset) {
|
||||
setPreset(preset);
|
||||
this.updatePopoverActiveStates(getStorageItem('theme') || 'auto', preset);
|
||||
this.updateHamburgerThemeIcon();
|
||||
}
|
||||
|
||||
updatePopoverActiveStates(theme, preset) {
|
||||
const popover = document.getElementById('themePopover');
|
||||
if (!popover) return;
|
||||
|
||||
popover.querySelectorAll('.theme-mode-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.mode === theme);
|
||||
});
|
||||
|
||||
popover.querySelectorAll('.theme-preset-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.preset === preset);
|
||||
});
|
||||
}
|
||||
|
||||
initializeHamburgerMenu() {
|
||||
const hamburgerBtn = document.getElementById('hamburgerMenuBtn');
|
||||
const hamburgerDropdown = document.getElementById('hamburgerDropdown');
|
||||
@@ -188,7 +253,6 @@ export class HeaderManager {
|
||||
case 'theme':
|
||||
if (typeof toggleTheme === 'function') {
|
||||
const newTheme = toggleTheme();
|
||||
// Update theme toggle in header if it exists
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
if (themeToggle) {
|
||||
themeToggle.classList.remove('theme-light', 'theme-dark', 'theme-auto');
|
||||
@@ -196,6 +260,7 @@ export class HeaderManager {
|
||||
this.updateThemeTooltip(themeToggle, newTheme);
|
||||
}
|
||||
this.updateHamburgerThemeIcon();
|
||||
this.updatePopoverActiveStates(newTheme, getStorageItem('theme_preset') || 'default');
|
||||
}
|
||||
break;
|
||||
case 'settings':
|
||||
|
||||
@@ -197,11 +197,17 @@ export function restoreFolderFilter() {
|
||||
}
|
||||
}
|
||||
|
||||
const CYCLE_ORDER = ['auto', 'light', 'dark'];
|
||||
const PRESET_NAMES = ['default', 'nord', 'gruvbox', 'monokai', 'dracula', 'solarized'];
|
||||
|
||||
export { CYCLE_ORDER, PRESET_NAMES };
|
||||
|
||||
export function initTheme() {
|
||||
const savedTheme = getStorageItem('theme') || 'auto';
|
||||
const savedPreset = getStorageItem('theme_preset') || 'default';
|
||||
applyTheme(savedTheme);
|
||||
applyPreset(savedPreset);
|
||||
|
||||
// Update theme when system preference changes (for 'auto' mode)
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
const currentTheme = getStorageItem('theme') || 'auto';
|
||||
if (currentTheme === 'auto') {
|
||||
@@ -212,34 +218,44 @@ export function initTheme() {
|
||||
|
||||
export function toggleTheme() {
|
||||
const currentTheme = getStorageItem('theme') || 'auto';
|
||||
let newTheme;
|
||||
|
||||
if (currentTheme === 'light') {
|
||||
newTheme = 'dark';
|
||||
} else {
|
||||
newTheme = 'light';
|
||||
}
|
||||
const currentIndex = CYCLE_ORDER.indexOf(currentTheme);
|
||||
const nextIndex = (currentIndex + 1) % CYCLE_ORDER.length;
|
||||
const newTheme = CYCLE_ORDER[nextIndex];
|
||||
|
||||
setStorageItem('theme', newTheme);
|
||||
applyTheme(newTheme);
|
||||
|
||||
// Force a repaint to ensure theme changes are applied immediately
|
||||
document.body.style.display = 'none';
|
||||
document.body.offsetHeight; // Trigger a reflow
|
||||
document.body.offsetHeight;
|
||||
document.body.style.display = '';
|
||||
|
||||
return newTheme;
|
||||
}
|
||||
|
||||
// Add a new helper function to apply the theme
|
||||
export function cyclePreset() {
|
||||
const currentPreset = getStorageItem('theme_preset') || 'default';
|
||||
const currentIndex = PRESET_NAMES.indexOf(currentPreset);
|
||||
const nextIndex = (currentIndex + 1) % PRESET_NAMES.length;
|
||||
const newPreset = PRESET_NAMES[nextIndex];
|
||||
|
||||
setStorageItem('theme_preset', newPreset);
|
||||
applyPreset(newPreset);
|
||||
|
||||
return newPreset;
|
||||
}
|
||||
|
||||
export function setPreset(name) {
|
||||
if (!PRESET_NAMES.includes(name)) return;
|
||||
setStorageItem('theme_preset', name);
|
||||
applyPreset(name);
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const htmlElement = document.documentElement;
|
||||
|
||||
// Remove any existing theme attributes
|
||||
htmlElement.removeAttribute('data-theme');
|
||||
|
||||
// Apply the appropriate theme
|
||||
if (theme === 'dark' || (theme === 'auto' && prefersDark)) {
|
||||
htmlElement.setAttribute('data-theme', 'dark');
|
||||
document.body.dataset.theme = 'dark';
|
||||
@@ -248,19 +264,18 @@ function applyTheme(theme) {
|
||||
document.body.dataset.theme = 'light';
|
||||
}
|
||||
|
||||
// Update the theme-toggle icon state
|
||||
updateThemeToggleIcons(theme);
|
||||
}
|
||||
|
||||
// New function to update theme toggle icons
|
||||
function applyPreset(preset) {
|
||||
document.documentElement.setAttribute('data-theme-preset', preset);
|
||||
}
|
||||
|
||||
function updateThemeToggleIcons(theme) {
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
if (!themeToggle) return;
|
||||
|
||||
// Remove any existing active classes
|
||||
themeToggle.classList.remove('theme-light', 'theme-dark', 'theme-auto');
|
||||
|
||||
// Add the appropriate class based on current theme
|
||||
themeToggle.classList.add(`theme-${theme}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,16 +46,20 @@
|
||||
</script>
|
||||
<script>
|
||||
(function() {
|
||||
// Apply theme immediately based on stored preference
|
||||
const STORAGE_PREFIX = 'lora_manager_';
|
||||
const savedTheme = localStorage.getItem(STORAGE_PREFIX + 'theme') || 'auto';
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
var STORAGE_PREFIX = 'lora_manager_';
|
||||
var savedTheme = localStorage.getItem(STORAGE_PREFIX + 'theme') || 'auto';
|
||||
var savedPreset = localStorage.getItem(STORAGE_PREFIX + 'theme_preset') || 'default';
|
||||
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
if (savedTheme === 'dark' || (savedTheme === 'auto' && prefersDark)) {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
}
|
||||
|
||||
if (savedPreset && savedPreset !== 'default') {
|
||||
document.documentElement.setAttribute('data-theme-preset', savedPreset);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% block head_scripts %}{% endblock %}
|
||||
|
||||
@@ -72,6 +72,55 @@
|
||||
<i class="fas fa-moon dark-icon"></i>
|
||||
<i class="fas fa-sun light-icon"></i>
|
||||
<i class="fas fa-adjust auto-icon"></i>
|
||||
<div class="theme-popover" id="themePopover">
|
||||
<div class="theme-popover-section">
|
||||
<div class="theme-popover-label">{{ t('header.theme.mode') }}</div>
|
||||
<div class="theme-popover-modes">
|
||||
<button class="theme-mode-btn" data-mode="light" title="{{ t('header.theme.light') }}">
|
||||
<i class="fas fa-sun"></i>
|
||||
<span>{{ t('header.theme.light') }}</span>
|
||||
</button>
|
||||
<button class="theme-mode-btn" data-mode="dark" title="{{ t('header.theme.dark') }}">
|
||||
<i class="fas fa-moon"></i>
|
||||
<span>{{ t('header.theme.dark') }}</span>
|
||||
</button>
|
||||
<button class="theme-mode-btn" data-mode="auto" title="{{ t('header.theme.auto') }}">
|
||||
<i class="fas fa-adjust"></i>
|
||||
<span>{{ t('header.theme.auto') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-popover-divider"></div>
|
||||
<div class="theme-popover-section">
|
||||
<div class="theme-popover-label">{{ t('header.theme.presets') }}</div>
|
||||
<div class="theme-popover-presets">
|
||||
<button class="theme-preset-btn" data-preset="default" title="{{ t('header.theme.default') }}">
|
||||
<span class="preset-swatch preset-swatch-default"></span>
|
||||
<span>{{ t('header.theme.default') }}</span>
|
||||
</button>
|
||||
<button class="theme-preset-btn" data-preset="nord" title="{{ t('header.theme.nord') }}">
|
||||
<span class="preset-swatch preset-swatch-nord"></span>
|
||||
<span>{{ t('header.theme.nord') }}</span>
|
||||
</button>
|
||||
<button class="theme-preset-btn" data-preset="gruvbox" title="{{ t('header.theme.gruvbox') }}">
|
||||
<span class="preset-swatch preset-swatch-gruvbox"></span>
|
||||
<span>{{ t('header.theme.gruvbox') }}</span>
|
||||
</button>
|
||||
<button class="theme-preset-btn" data-preset="monokai" title="{{ t('header.theme.monokai') }}">
|
||||
<span class="preset-swatch preset-swatch-monokai"></span>
|
||||
<span>{{ t('header.theme.monokai') }}</span>
|
||||
</button>
|
||||
<button class="theme-preset-btn" data-preset="dracula" title="{{ t('header.theme.dracula') }}">
|
||||
<span class="preset-swatch preset-swatch-dracula"></span>
|
||||
<span>{{ t('header.theme.dracula') }}</span>
|
||||
</button>
|
||||
<button class="theme-preset-btn" data-preset="solarized" title="{{ t('header.theme.solarized') }}">
|
||||
<span class="preset-swatch preset-swatch-solarized"></span>
|
||||
<span>{{ t('header.theme.solarized') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-toggle" title="{{ t('common.actions.settings') }}">
|
||||
<i class="fas fa-cog"></i>
|
||||
|
||||
Reference in New Issue
Block a user