mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
fix(extension): localize theme and appearance tooltips and selector options
This commit is contained in:
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Wendet Audioeffekte wie Kompression auf die Videowiedergabe an",
|
||||
"AUDIO_OPEN_SETTINGS": "Öffnen",
|
||||
"NEW_FEATURE_AUDIO": "Neu: Audio-Verarbeitung — probiere den Kompressor aus!",
|
||||
"NEW_FEATURE_THEME": "Neu: Themes — wechsle zum klassischen Look oder minimalem Graphit.",
|
||||
"AUDIO_BACK": "← Zurück",
|
||||
"AUDIO_PAGE_TITLE": "Audio-Einstellungen",
|
||||
"AUDIO_MASTER_TOGGLE": "Audio-Verarbeitung",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Apply audio effects like compression to video playback",
|
||||
"AUDIO_OPEN_SETTINGS": "Open",
|
||||
"NEW_FEATURE_AUDIO": "New: Audio Processing — try the compressor!",
|
||||
"NEW_FEATURE_THEME": "New: Themes — switch to the classic look or minimal graphite.",
|
||||
"AUDIO_BACK": "← Back",
|
||||
"AUDIO_PAGE_TITLE": "Audio Settings",
|
||||
"AUDIO_MASTER_TOGGLE": "Audio Processing",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Aplica efectos de audio como compresión a la reproducción de video",
|
||||
"AUDIO_OPEN_SETTINGS": "Abrir",
|
||||
"NEW_FEATURE_AUDIO": "Nuevo: Procesamiento de audio — ¡prueba el compresor!",
|
||||
"NEW_FEATURE_THEME": "Nuevo: Temas — cambia al aspecto clásico o al grafito minimalista.",
|
||||
"AUDIO_BACK": "← Volver",
|
||||
"AUDIO_PAGE_TITLE": "Configuración de audio",
|
||||
"AUDIO_MASTER_TOGGLE": "Procesamiento de audio",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Applique des effets audio comme la compression à la lecture vidéo",
|
||||
"AUDIO_OPEN_SETTINGS": "Ouvrir",
|
||||
"NEW_FEATURE_AUDIO": "Nouveau : Traitement audio — essayez le compresseur !",
|
||||
"NEW_FEATURE_THEME": "Nouveau : Thèmes — passez au look classique ou au graphite minimal.",
|
||||
"AUDIO_BACK": "← Retour",
|
||||
"AUDIO_PAGE_TITLE": "Paramètres audio",
|
||||
"AUDIO_MASTER_TOGGLE": "Traitement audio",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Applica effetti sonori (come il compressore) ai video",
|
||||
"AUDIO_OPEN_SETTINGS": "Apri",
|
||||
"NEW_FEATURE_AUDIO": "Novità: Elaborazione Audio — prova il compressore!",
|
||||
"NEW_FEATURE_THEME": "Nuovo: Temi — passa all'aspetto classico o al grafite minimale.",
|
||||
"AUDIO_BACK": "← Indietro",
|
||||
"AUDIO_PAGE_TITLE": "Impostazioni Audio",
|
||||
"AUDIO_MASTER_TOGGLE": "Elaborazione Audio",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "動画再生に圧縮などのオーディオエフェクトを適用します",
|
||||
"AUDIO_OPEN_SETTINGS": "開く",
|
||||
"NEW_FEATURE_AUDIO": "新機能: オーディオ処理 — コンプレッサーを試してみよう!",
|
||||
"NEW_FEATURE_THEME": "新機能:テーマ — クラシックな外観やミニマルなグラファイトに切り替え。",
|
||||
"AUDIO_BACK": "← 戻る",
|
||||
"AUDIO_PAGE_TITLE": "オーディオ設定",
|
||||
"AUDIO_MASTER_TOGGLE": "オーディオ処理",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "비디오 재생에 압축과 같은 오디오 효과를 적용합니다",
|
||||
"AUDIO_OPEN_SETTINGS": "열기",
|
||||
"NEW_FEATURE_AUDIO": "신규: 오디오 처리 — 컴프레서를 사용해보세요!",
|
||||
"NEW_FEATURE_THEME": "신규: 테마 — 클래식 룩이나 미니멀 그래파이트로 전환하세요.",
|
||||
"AUDIO_BACK": "← 뒤로",
|
||||
"AUDIO_PAGE_TITLE": "오디오 설정",
|
||||
"AUDIO_MASTER_TOGGLE": "오디오 처리",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Past audio-effecten zoals compressie toe op video-afspelen",
|
||||
"AUDIO_OPEN_SETTINGS": "Openen",
|
||||
"NEW_FEATURE_AUDIO": "Nieuw: Audioverwerking — probeer de compressor!",
|
||||
"NEW_FEATURE_THEME": "Nieuw: Thema's — schakel naar de klassieke look of minimaal grafiet.",
|
||||
"AUDIO_BACK": "← Terug",
|
||||
"AUDIO_PAGE_TITLE": "Audio-instellingen",
|
||||
"AUDIO_MASTER_TOGGLE": "Audioverwerking",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Stosuje efekty dźwiękowe, takie jak kompresja, do odtwarzania wideo",
|
||||
"AUDIO_OPEN_SETTINGS": "Otwórz",
|
||||
"NEW_FEATURE_AUDIO": "Nowość: Przetwarzanie dźwięku — wypróbuj kompresor!",
|
||||
"NEW_FEATURE_THEME": "Nowość: Motywy — przełącz na klasyczny wygląd lub minimalny grafit.",
|
||||
"AUDIO_BACK": "← Wstecz",
|
||||
"AUDIO_PAGE_TITLE": "Ustawienia dźwięku",
|
||||
"AUDIO_MASTER_TOGGLE": "Przetwarzanie dźwięku",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Aplica efeitos de áudio (como compressão) aos vídeos",
|
||||
"AUDIO_OPEN_SETTINGS": "Abrir",
|
||||
"NEW_FEATURE_AUDIO": "Novo: Processamento de áudio — experimente o compressor!",
|
||||
"NEW_FEATURE_THEME": "Novo: Temas — mude para o visual clássico ou grafite minimalista.",
|
||||
"AUDIO_BACK": "← Voltar",
|
||||
"AUDIO_PAGE_TITLE": "Configurações de áudio",
|
||||
"AUDIO_MASTER_TOGGLE": "Processamento de áudio",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Aplica efeitos de áudio (como compressão) aos vídeos",
|
||||
"AUDIO_OPEN_SETTINGS": "Abrir",
|
||||
"NEW_FEATURE_AUDIO": "Novo: Processamento de áudio — experimente o compressor!",
|
||||
"NEW_FEATURE_THEME": "Novo: Temas — mude para o visual clássico ou grafite minimalista.",
|
||||
"AUDIO_BACK": "← Voltar",
|
||||
"AUDIO_PAGE_TITLE": "Configurações de Áudio",
|
||||
"AUDIO_MASTER_TOGGLE": "Processamento de Áudio",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Применяет аудиоэффекты, такие как сжатие, к воспроизведению видео",
|
||||
"AUDIO_OPEN_SETTINGS": "Открыть",
|
||||
"NEW_FEATURE_AUDIO": "Новое: Обработка звука — попробуйте компрессор!",
|
||||
"NEW_FEATURE_THEME": "Новое: Темы — переключитесь на классический вид или минимальный графит.",
|
||||
"AUDIO_BACK": "← Назад",
|
||||
"AUDIO_PAGE_TITLE": "Настройки звука",
|
||||
"AUDIO_MASTER_TOGGLE": "Обработка звука",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Video oynatımına sıkıştırma gibi ses efektleri uygular",
|
||||
"AUDIO_OPEN_SETTINGS": "Aç",
|
||||
"NEW_FEATURE_AUDIO": "Yeni: Ses işleme — kompresörü deneyin!",
|
||||
"NEW_FEATURE_THEME": "Yeni: Temalar — klasik görünüme veya minimal grafite geçin.",
|
||||
"AUDIO_BACK": "← Geri",
|
||||
"AUDIO_PAGE_TITLE": "Ses ayarları",
|
||||
"AUDIO_MASTER_TOGGLE": "Ses işleme",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Застосуйте звукові ефекти, такі як стиснення, до відтворення відео",
|
||||
"AUDIO_OPEN_SETTINGS": "Відкрити",
|
||||
"NEW_FEATURE_AUDIO": "Новинка: обробка звуку — спробуйте компресор!",
|
||||
"NEW_FEATURE_THEME": "Нове: Теми — перемкніться на класичний вигляд або мінімальний графіт.",
|
||||
"AUDIO_BACK": "← Назад",
|
||||
"AUDIO_PAGE_TITLE": "Параметри звуку",
|
||||
"AUDIO_MASTER_TOGGLE": "Обробка аудіо",
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
"LABEL_AUDIO_PROCESSING_TOOLTIP": "将压缩等音频效果应用于视频播放",
|
||||
"AUDIO_OPEN_SETTINGS": "打开",
|
||||
"NEW_FEATURE_AUDIO": "新功能:音频处理 - 尝试压缩器!",
|
||||
"NEW_FEATURE_THEME": "新功能:主题 — 切换到经典外观或极简石墨风格。",
|
||||
"AUDIO_BACK": "← 返回",
|
||||
"AUDIO_PAGE_TITLE": "音频设置",
|
||||
"AUDIO_MASTER_TOGGLE": "音频处理",
|
||||
|
||||
+326
-2
@@ -73,6 +73,113 @@
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
THEME: CYBER — the original indigo / slate look, kept for
|
||||
people who prefer the pre-nature design. Cool navy neutrals
|
||||
with an electric indigo brand accent and a violet companion.
|
||||
============================================================ */
|
||||
html[data-palette="cyber"] {
|
||||
--bg-base: oklch(0.215 0.042 265);
|
||||
--surface: oklch(0.285 0.05 265);
|
||||
--surface-deep: oklch(0.185 0.04 265);
|
||||
--surface-alt: oklch(0.25 0.048 265);
|
||||
--border-soft: oklch(0.9 0.03 265 / 0.10);
|
||||
--border-strong: oklch(0.42 0.05 265);
|
||||
--text-primary: oklch(0.97 0.012 265);
|
||||
--text-secondary: oklch(0.74 0.03 265);
|
||||
--accent-green: oklch(0.59 0.20 275);
|
||||
--accent-green-hover: oklch(0.69 0.17 275);
|
||||
--accent-terracotta: oklch(0.64 0.21 320);
|
||||
--success-green: oklch(0.74 0.17 150);
|
||||
--warning-amber: oklch(0.79 0.15 75);
|
||||
--danger: oklch(0.63 0.20 25);
|
||||
--text-on-green: oklch(0.99 0.006 275);
|
||||
--text-on-warm: oklch(0.99 0.006 320);
|
||||
--status-success-text: oklch(0.82 0.15 150);
|
||||
--status-warning-text: oklch(0.85 0.14 78);
|
||||
--status-error-text: oklch(0.78 0.16 25);
|
||||
--status-neutral-text: oklch(0.74 0.03 265);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
html[data-palette="cyber"].theme-light {
|
||||
--bg-base: oklch(0.965 0.01 265);
|
||||
--surface: oklch(0.995 0.005 265);
|
||||
--surface-deep: oklch(0.945 0.014 265);
|
||||
--surface-alt: oklch(0.915 0.02 265);
|
||||
--border-soft: oklch(0.30 0.05 265 / 0.12);
|
||||
--border-strong: oklch(0.79 0.03 265);
|
||||
--text-primary: oklch(0.26 0.05 265);
|
||||
--text-secondary: oklch(0.48 0.045 265);
|
||||
--accent-green: oklch(0.53 0.21 275);
|
||||
--accent-green-hover: oklch(0.46 0.20 275);
|
||||
--accent-terracotta: oklch(0.55 0.23 320);
|
||||
--success-green: oklch(0.52 0.16 150);
|
||||
--warning-amber: oklch(0.62 0.15 72);
|
||||
--danger: oklch(0.56 0.21 25);
|
||||
--text-on-green: oklch(0.99 0.005 275);
|
||||
--text-on-warm: oklch(0.99 0.005 320);
|
||||
--status-success-text: oklch(0.45 0.15 150);
|
||||
--status-warning-text: oklch(0.50 0.13 70);
|
||||
--status-error-text: oklch(0.50 0.18 25);
|
||||
--status-neutral-text: oklch(0.48 0.045 265);
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
THEME: GRAPHITE — a quiet black-and-white minimal system.
|
||||
Near-neutral cool greys, a monochrome accent (bright on dark,
|
||||
ink on light); status hues are the only colour, kept muted so
|
||||
they still read as success / warning / danger.
|
||||
============================================================ */
|
||||
html[data-palette="graphite"] {
|
||||
--bg-base: oklch(0.19 0.004 260);
|
||||
--surface: oklch(0.255 0.005 260);
|
||||
--surface-deep: oklch(0.165 0.004 260);
|
||||
--surface-alt: oklch(0.225 0.005 260);
|
||||
--border-soft: oklch(0.92 0.008 260 / 0.11);
|
||||
--border-strong: oklch(0.42 0.006 260);
|
||||
--text-primary: oklch(0.97 0.003 260);
|
||||
--text-secondary: oklch(0.72 0.005 260);
|
||||
--accent-green: oklch(0.93 0.006 260);
|
||||
--accent-green-hover: oklch(0.99 0.003 260);
|
||||
--accent-terracotta: oklch(0.80 0.008 260);
|
||||
--success-green: oklch(0.74 0.10 155);
|
||||
--warning-amber: oklch(0.80 0.10 80);
|
||||
--danger: oklch(0.64 0.14 25);
|
||||
--text-on-green: oklch(0.20 0.004 260);
|
||||
--text-on-warm: oklch(0.20 0.004 260);
|
||||
--status-success-text: oklch(0.81 0.09 152);
|
||||
--status-warning-text: oklch(0.84 0.10 80);
|
||||
--status-error-text: oklch(0.76 0.12 25);
|
||||
--status-neutral-text: oklch(0.72 0.005 260);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
html[data-palette="graphite"].theme-light {
|
||||
--bg-base: oklch(0.955 0.002 260);
|
||||
--surface: oklch(1 0 0);
|
||||
--surface-deep: oklch(0.928 0.003 260);
|
||||
--surface-alt: oklch(0.90 0.004 260);
|
||||
--border-soft: oklch(0.20 0.006 260 / 0.13);
|
||||
--border-strong: oklch(0.77 0.004 260);
|
||||
--text-primary: oklch(0.22 0.005 260);
|
||||
--text-secondary: oklch(0.46 0.006 260);
|
||||
--accent-green: oklch(0.28 0.006 260);
|
||||
--accent-green-hover: oklch(0.18 0.005 260);
|
||||
--accent-terracotta: oklch(0.44 0.008 260);
|
||||
--success-green: oklch(0.51 0.12 152);
|
||||
--warning-amber: oklch(0.60 0.12 72);
|
||||
--danger: oklch(0.55 0.17 25);
|
||||
--text-on-green: oklch(0.98 0.002 260);
|
||||
--text-on-warm: oklch(0.98 0.002 260);
|
||||
--status-success-text: oklch(0.45 0.12 152);
|
||||
--status-warning-text: oklch(0.49 0.11 70);
|
||||
--status-error-text: oklch(0.49 0.16 25);
|
||||
--status-neutral-text: oklch(0.46 0.006 260);
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 360px;
|
||||
margin: 0;
|
||||
@@ -94,6 +201,60 @@
|
||||
radial-gradient(circle at 105% 5%, oklch(0.82 0.12 65 / 0.2), transparent 38%);
|
||||
}
|
||||
|
||||
/* Per-theme ambient glow: each palette carries its own signature
|
||||
corner wash so the backdrop matches the accent, not eucalyptus. */
|
||||
html[data-palette="cyber"] body {
|
||||
background-image:
|
||||
radial-gradient(circle at 12% -8%, oklch(0.64 0.19 275 / 0.16), transparent 42%),
|
||||
radial-gradient(circle at 105% 12%, oklch(0.64 0.21 320 / 0.11), transparent 34%);
|
||||
}
|
||||
html[data-palette="cyber"].theme-light body {
|
||||
background-image:
|
||||
radial-gradient(circle at 8% -5%, oklch(0.60 0.20 275 / 0.16), transparent 40%),
|
||||
radial-gradient(circle at 105% 5%, oklch(0.62 0.20 320 / 0.14), transparent 38%);
|
||||
}
|
||||
html[data-palette="graphite"] body {
|
||||
background-image:
|
||||
radial-gradient(circle at 12% -8%, oklch(0.93 0.006 260 / 0.05), transparent 44%);
|
||||
}
|
||||
html[data-palette="graphite"].theme-light body {
|
||||
background-image:
|
||||
radial-gradient(circle at 12% -8%, oklch(0.20 0.006 260 / 0.045), transparent 46%);
|
||||
}
|
||||
|
||||
/* The Create Room button carries an inline eucalyptus glow; retint
|
||||
its shadow per palette so the hero action stays on-theme. */
|
||||
html[data-palette="cyber"] #createRoomBtn,
|
||||
html[data-palette="cyber"] #syncTabCreateRoomBtn {
|
||||
box-shadow: 0 4px 12px oklch(0.64 0.19 275 / 0.4) !important;
|
||||
}
|
||||
html[data-palette="graphite"] #createRoomBtn,
|
||||
html[data-palette="graphite"] #syncTabCreateRoomBtn {
|
||||
box-shadow: 0 4px 12px oklch(0.20 0.006 260 / 0.4) !important;
|
||||
}
|
||||
|
||||
/* Cyber's brand ink is white (for the dark indigo accent), but its
|
||||
success/warning fills are bright — so the few surfaces that print
|
||||
text directly on those fills need dark ink to stay legible. */
|
||||
html[data-palette="cyber"] .toast-success,
|
||||
html[data-palette="cyber"] .toast-warning,
|
||||
html[data-palette="cyber"] .copy-success {
|
||||
color: oklch(0.22 0.03 265) !important;
|
||||
}
|
||||
|
||||
/* Theme cross-fade: enabled only for the ~260ms around a user-initiated
|
||||
theme/appearance switch (added by popup.js), so colours glide instead
|
||||
of snapping — without slowing down normal hover/state transitions. */
|
||||
html.theme-anim,
|
||||
html.theme-anim * {
|
||||
transition: background-color 0.24s ease, border-color 0.24s ease,
|
||||
color 0.24s ease, box-shadow 0.24s ease, fill 0.24s ease !important;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html.theme-anim,
|
||||
html.theme-anim * { transition: none !important; }
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -216,7 +377,7 @@
|
||||
input:focus,
|
||||
select:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px oklch(0.68 0.13 150 / 0.18);
|
||||
box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent), transparent 82%);
|
||||
}
|
||||
|
||||
button.primary {
|
||||
@@ -495,6 +656,23 @@
|
||||
.click-leaf-amber i {
|
||||
background: linear-gradient(135deg, oklch(0.68 0.14 55 / 0.95), oklch(0.55 0.13 40 / 0.85));
|
||||
}
|
||||
/* Non-eucalyptus themes retint the burst from their own accent pair so
|
||||
the particles stay on-theme and, because those vars flip with
|
||||
light/dark, remain visible against every background. */
|
||||
html[data-palette="cyber"] .click-leaf i,
|
||||
html[data-palette="graphite"] .click-leaf i {
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent-hover));
|
||||
}
|
||||
html[data-palette="cyber"] .click-leaf-sage i,
|
||||
html[data-palette="graphite"] .click-leaf-sage i {
|
||||
background: linear-gradient(135deg, var(--accent-hover), var(--accent));
|
||||
}
|
||||
html[data-palette="cyber"] .click-leaf-amber i {
|
||||
background: linear-gradient(135deg, var(--accent-terracotta), var(--accent));
|
||||
}
|
||||
html[data-palette="graphite"] .click-leaf-amber i {
|
||||
background: linear-gradient(135deg, var(--text-secondary), var(--accent));
|
||||
}
|
||||
@keyframes leafBurst {
|
||||
0% {
|
||||
opacity: 0;
|
||||
@@ -865,6 +1043,109 @@
|
||||
color: var(--text);
|
||||
box-shadow: 0 1px 5px oklch(0.08 0.02 150 / 0.32), inset 0 0 0 1px var(--border-soft);
|
||||
}
|
||||
|
||||
/* Theme (colour palette) picker: a full-width row of swatch cards so
|
||||
each curated scheme previews its own colours before selection. */
|
||||
.palette-row {
|
||||
display: block;
|
||||
padding: 12px 0 4px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
}
|
||||
.palette-row > label {
|
||||
margin: 0 0 9px;
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
}
|
||||
.palette-picker {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
.palette-picker select {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
.palette-choice {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 62px;
|
||||
margin: 0;
|
||||
padding: 10px 4px 9px;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 10px;
|
||||
background: var(--surface-deep);
|
||||
color: var(--text-muted);
|
||||
font-family: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.18s ease, background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
|
||||
}
|
||||
.palette-choice:hover {
|
||||
color: var(--text);
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.palette-choice.active {
|
||||
color: var(--text);
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in oklch, var(--accent) 10%, var(--surface-deep));
|
||||
box-shadow: 0 0 0 1px var(--accent), 0 5px 14px oklch(0.08 0.02 260 / 0.28);
|
||||
}
|
||||
.palette-swatch {
|
||||
display: inline-flex;
|
||||
padding: 3px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-alt);
|
||||
box-shadow: inset 0 0 0 1px var(--border-soft);
|
||||
}
|
||||
.palette-swatch i {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.22);
|
||||
}
|
||||
.palette-swatch i + i {
|
||||
margin-left: -5px;
|
||||
}
|
||||
.palette-labeltext {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.palette-name {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.palette-desc {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.85;
|
||||
}
|
||||
.palette-choice.active .palette-desc {
|
||||
color: var(--accent);
|
||||
opacity: 1;
|
||||
}
|
||||
.tab-btn[data-tab="tab-dev"],
|
||||
#devToolsTabBtn {
|
||||
color: color-mix(in oklch, var(--text-muted), transparent 18%);
|
||||
@@ -1147,7 +1428,7 @@
|
||||
<option value="" data-i18n="OPTION_SELECT_TAB">-- Select a Tab --</option>
|
||||
</select>
|
||||
<!-- Hint Tooltip -->
|
||||
<div id="targetTabHint" style="display: none; position: absolute; top: -25px; right: 0; background: var(--accent); color: var(--text-on-green); padding: 4px 8px; border-radius: 6px; font-size: 11px; font-weight: bold; pointer-events: none; animation: floatHint 2s ease-in-out infinite; box-shadow: 0 4px 12px rgba(86, 174, 108, 0.4); z-index: 10;" data-i18n="HINT_SELECT_VIDEO">
|
||||
<div id="targetTabHint" style="display: none; position: absolute; top: -25px; right: 0; background: var(--accent); color: var(--text-on-green); padding: 4px 8px; border-radius: 6px; font-size: 11px; font-weight: bold; pointer-events: none; animation: floatHint 2s ease-in-out infinite; box-shadow: 0 4px 12px color-mix(in oklch, var(--accent), transparent 60%); z-index: 10;" data-i18n="HINT_SELECT_VIDEO">
|
||||
Select your video here!
|
||||
<div style="position: absolute; bottom: -4px; right: 20px; width: 8px; height: 8px; background: var(--accent); transform: rotate(45deg);"></div>
|
||||
</div>
|
||||
@@ -1221,6 +1502,49 @@
|
||||
<button id="themeChoiceLight" class="theme-choice" type="button" data-theme-value="light">Light</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="palette-row">
|
||||
<label id="paletteLabel" for="paletteSelector" title="Pick a curated colour scheme for the extension">Theme</label>
|
||||
<div class="palette-picker" role="group" aria-labelledby="paletteLabel">
|
||||
<select id="paletteSelector" aria-label="Theme" tabindex="-1">
|
||||
<option value="eucalyptus">Eucalyptus</option>
|
||||
<option value="cyber">Cyber</option>
|
||||
<option value="graphite">Graphite</option>
|
||||
</select>
|
||||
<button id="paletteChoiceEucalyptus" class="palette-choice" type="button" data-palette-value="eucalyptus">
|
||||
<span class="palette-swatch" aria-hidden="true">
|
||||
<i style="background: oklch(0.73 0.13 155)"></i>
|
||||
<i style="background: oklch(0.62 0.14 45)"></i>
|
||||
<i style="background: oklch(0.30 0.035 150)"></i>
|
||||
</span>
|
||||
<span class="palette-labeltext">
|
||||
<span class="palette-name">Eucalyptus</span>
|
||||
<span class="palette-desc">Nature</span>
|
||||
</span>
|
||||
</button>
|
||||
<button id="paletteChoiceCyber" class="palette-choice" type="button" data-palette-value="cyber">
|
||||
<span class="palette-swatch" aria-hidden="true">
|
||||
<i style="background: oklch(0.64 0.19 275)"></i>
|
||||
<i style="background: oklch(0.64 0.21 320)"></i>
|
||||
<i style="background: oklch(0.185 0.04 265)"></i>
|
||||
</span>
|
||||
<span class="palette-labeltext">
|
||||
<span class="palette-name">Cyber</span>
|
||||
<span class="palette-desc">Classic</span>
|
||||
</span>
|
||||
</button>
|
||||
<button id="paletteChoiceGraphite" class="palette-choice" type="button" data-palette-value="graphite">
|
||||
<span class="palette-swatch" aria-hidden="true">
|
||||
<i style="background: oklch(0.95 0.004 260)"></i>
|
||||
<i style="background: oklch(0.55 0.006 260)"></i>
|
||||
<i style="background: oklch(0.20 0.004 260)"></i>
|
||||
</span>
|
||||
<span class="palette-labeltext">
|
||||
<span class="palette-name">Graphite</span>
|
||||
<span class="palette-desc">Minimal</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label for="langSelector" data-i18n="LABEL_LANGUAGE" data-i18n-title="LABEL_LANGUAGE_TOOLTIP" title="Choose your preferred extension language">App Language</label>
|
||||
<span class="settings-select">
|
||||
|
||||
+114
-11
@@ -65,6 +65,7 @@ const elements = {
|
||||
cancelLobbyBtn: document.getElementById('cancelLobbyBtn'),
|
||||
langSelector: document.getElementById('langSelector'),
|
||||
themeSelector: document.getElementById('themeSelector'),
|
||||
paletteSelector: document.getElementById('paletteSelector'),
|
||||
|
||||
audioSettingsLink: document.getElementById('audioSettingsLink'),
|
||||
settingsSupportLink: document.getElementById('settingsSupportLink'),
|
||||
@@ -115,6 +116,12 @@ const FEATURE_HINTS = [
|
||||
selector: '#audioProcessingLabel',
|
||||
position: 'beforebegin',
|
||||
i18nTooltip: 'NEW_FEATURE_AUDIO'
|
||||
},
|
||||
{
|
||||
key: 'theme_picker',
|
||||
selector: '#paletteLabel',
|
||||
position: 'afterend',
|
||||
i18nTooltip: 'NEW_FEATURE_THEME'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -135,16 +142,23 @@ function openAudioSettingsPage() {
|
||||
|
||||
function localizeThemeSelector(locale) {
|
||||
const labels = {
|
||||
de: ['Darstellung', 'System', 'Dunkel', 'Hell'], fr: ['Apparence', 'Système', 'Sombre', 'Clair'],
|
||||
es: ['Apariencia', 'Sistema', 'Oscuro', 'Claro'], it: ['Aspetto', 'Sistema', 'Scuro', 'Chiaro'],
|
||||
nl: ['Weergave', 'Systeem', 'Donker', 'Licht'], pl: ['Wygląd', 'System', 'Ciemny', 'Jasny'],
|
||||
pt: ['Aparência', 'Sistema', 'Escuro', 'Claro'], 'pt-BR': ['Aparência', 'Sistema', 'Escuro', 'Claro'],
|
||||
tr: ['Görünüm', 'Sistem', 'Koyu', 'Açık'], ru: ['Оформление', 'Системная', 'Тёмная', 'Светлая'],
|
||||
ja: ['外観', 'システム', 'ダーク', 'ライト'], ko: ['테마', '시스템', '다크', '라이트'],
|
||||
zh: ['外观', '跟随系统', '深色', '浅色'], uk: ['Вигляд', 'Системна', 'Темна', 'Світла'],
|
||||
en: ['Appearance', 'System', 'Dark', 'Light']
|
||||
de: ['Darstellung', 'System', 'Dunkel', 'Hell', 'Folge dem System-Design oder wähle ein festes Design'],
|
||||
fr: ['Apparence', 'Système', 'Sombre', 'Clair', 'Suivre le thème du système ou choisir une apparence fixe'],
|
||||
es: ['Apariencia', 'Sistema', 'Oscuro', 'Claro', 'Seguir el tema del sistema o elegir una apariencia fija'],
|
||||
it: ['Aspetto', 'Sistema', 'Scuro', 'Chiaro', 'Segui il tema di sistema o scegli un aspetto fisso'],
|
||||
nl: ['Weergave', 'Systeem', 'Donker', 'Licht', 'Volg het systeemthema of kies een vaste weergave'],
|
||||
pl: ['Wygląd', 'System', 'Ciemny', 'Jasny', 'Dostosuj do motywu systemowego lub wybierz stały wygląd'],
|
||||
pt: ['Aparência', 'Sistema', 'Escuro', 'Claro', 'Seguir o tema do sistema ou escolher uma aparência fixa'],
|
||||
'pt-BR': ['Aparência', 'Sistema', 'Escuro', 'Claro', 'Seguir o tema do sistema ou escolher uma aparência fixa'],
|
||||
tr: ['Görünüm', 'Sistem', 'Koyu', 'Açık', 'Sistem temasını takip et veya sabit bir görünüm seç'],
|
||||
ru: ['Оформление', 'Системная', 'Тёмная', 'Светлая', 'Следовать системной теме или выбрать фиксированное оформление'],
|
||||
ja: ['外観', 'システム', 'ダーク', 'ライト', 'システムテーマに従うか、固定の外観を選択します'],
|
||||
ko: ['테마', '시스템', '다크', '라이트', '시스템 테마를 따르거나 고정된 테마를 선택하세요'],
|
||||
zh: ['外观', '跟随系统', '深色', '浅色', '跟随系统主题或选择固定外观'],
|
||||
uk: ['Вигляд', 'Системна', 'Темна', 'Світла', 'Дотримуватися системної теми або вибрати фіксоване оформлення'],
|
||||
en: ['Appearance', 'System', 'Dark', 'Light', 'Follow the system theme or choose a fixed appearance']
|
||||
};
|
||||
const [label, system, dark, light] = labels[locale] || labels.en;
|
||||
const [label, system, dark, light, tooltip] = labels[locale] || labels.en;
|
||||
const labelEl = document.getElementById('themeLabel');
|
||||
const systemEl = document.getElementById('themeOptionSystem');
|
||||
const darkEl = document.getElementById('themeOptionDark');
|
||||
@@ -152,7 +166,10 @@ function localizeThemeSelector(locale) {
|
||||
const systemButton = document.getElementById('themeChoiceSystem');
|
||||
const darkButton = document.getElementById('themeChoiceDark');
|
||||
const lightButton = document.getElementById('themeChoiceLight');
|
||||
if (labelEl) labelEl.textContent = label;
|
||||
if (labelEl) {
|
||||
labelEl.textContent = label;
|
||||
if (tooltip) labelEl.setAttribute('title', tooltip);
|
||||
}
|
||||
if (systemEl) systemEl.textContent = system;
|
||||
if (darkEl) darkEl.textContent = dark;
|
||||
if (lightEl) lightEl.textContent = light;
|
||||
@@ -160,6 +177,51 @@ function localizeThemeSelector(locale) {
|
||||
if (darkButton) darkButton.textContent = dark;
|
||||
if (lightButton) lightButton.textContent = light;
|
||||
if (elements.themeSelector) elements.themeSelector.setAttribute('aria-label', label);
|
||||
|
||||
// The palette scheme names (Eucalyptus / Cyber / Graphite) are brand
|
||||
// words kept verbatim; only the row label is localized.
|
||||
const paletteLabels = {
|
||||
de: ['Farbschema', 'Wähle ein kuratiertes Farbschema für die Erweiterung'],
|
||||
fr: ['Thème', 'Choisissez une palette de couleurs personnalisée pour l\'extension'],
|
||||
es: ['Tema', 'Elige una paleta de colores seleccionada para la extensión'],
|
||||
it: ['Tema', 'Scegli una combinazione di colori curata per l\'estensione'],
|
||||
nl: ['Thema', 'Kies een samengesteld kleurenschema voor de extensie'],
|
||||
pl: ['Motyw', 'Wybierz dopasowany schemat kolorów dla rozszerzenia'],
|
||||
pt: ['Tema', 'Escolha um esquema de cores personalizado para a extensão'],
|
||||
'pt-BR': ['Tema', 'Escolha um esquema de cores personalizado para a extensão'],
|
||||
tr: ['Tema', 'Uzantı için özel olarak seçilmiş bir renk şeması seçin'],
|
||||
ru: ['Тема', 'Выберите цветовую схему для расширения'],
|
||||
ja: ['テーマ', '拡張機能の厳選された配色を選択します'],
|
||||
ko: ['테마', '확장 프로그램에 맞는 특별한 색상 테마를 선택하세요'],
|
||||
zh: ['主题', '为扩展程序选择精心设计的配色方案'],
|
||||
uk: ['Тема', 'Виберіть колірну схему для розширення'],
|
||||
en: ['Theme', 'Pick a curated colour scheme for the extension']
|
||||
};
|
||||
const [paletteLabel, paletteTooltip] = paletteLabels[locale] || paletteLabels.en;
|
||||
const paletteLabelEl = document.getElementById('paletteLabel');
|
||||
if (paletteLabelEl) {
|
||||
paletteLabelEl.textContent = paletteLabel;
|
||||
if (paletteTooltip) paletteLabelEl.setAttribute('title', paletteTooltip);
|
||||
}
|
||||
if (elements.paletteSelector) elements.paletteSelector.setAttribute('aria-label', paletteLabel);
|
||||
|
||||
// Micro-descriptors under each swatch card: [nature, classic, minimal].
|
||||
const paletteDescs = {
|
||||
de: ['Natur', 'Klassik', 'Minimal'], fr: ['Nature', 'Classique', 'Minimal'],
|
||||
es: ['Naturaleza', 'Clásico', 'Minimal'], it: ['Natura', 'Classico', 'Minimal'],
|
||||
nl: ['Natuur', 'Klassiek', 'Minimaal'], pl: ['Natura', 'Klasyczny', 'Minimal'],
|
||||
pt: ['Natureza', 'Clássico', 'Minimal'], 'pt-BR': ['Natureza', 'Clássico', 'Minimal'],
|
||||
tr: ['Doğa', 'Klasik', 'Minimal'], ru: ['Природа', 'Классика', 'Минимал'],
|
||||
ja: ['ネイチャー', 'クラシック', 'ミニマル'], ko: ['자연', '클래식', '미니멀'],
|
||||
zh: ['自然', '经典', '极简'], uk: ['Природа', 'Класика', 'Мінімал'],
|
||||
en: ['Nature', 'Classic', 'Minimal']
|
||||
};
|
||||
const [natureDesc, classicDesc, minimalDesc] = paletteDescs[locale] || paletteDescs.en;
|
||||
const descMap = { eucalyptus: natureDesc, cyber: classicDesc, graphite: minimalDesc };
|
||||
document.querySelectorAll('.palette-choice').forEach(btn => {
|
||||
const desc = btn.querySelector('.palette-desc');
|
||||
if (desc) desc.textContent = descMap[btn.dataset.paletteValue] || desc.textContent;
|
||||
});
|
||||
}
|
||||
|
||||
function syncThemePicker() {
|
||||
@@ -171,6 +233,25 @@ function syncThemePicker() {
|
||||
});
|
||||
}
|
||||
|
||||
function syncPalettePicker() {
|
||||
const activePalette = elements.paletteSelector?.value || 'eucalyptus';
|
||||
document.querySelectorAll('.palette-choice').forEach(button => {
|
||||
const active = button.dataset.paletteValue === activePalette;
|
||||
button.classList.toggle('active', active);
|
||||
button.setAttribute('aria-pressed', String(active));
|
||||
});
|
||||
}
|
||||
|
||||
// Briefly enable global colour transitions so a theme/appearance switch
|
||||
// cross-fades instead of snapping. Self-clearing; a no-op under reduced motion.
|
||||
let themeAnimTimer = null;
|
||||
function flashThemeTransition() {
|
||||
const root = document.documentElement;
|
||||
root.classList.add('theme-anim');
|
||||
if (themeAnimTimer) clearTimeout(themeAnimTimer);
|
||||
themeAnimTimer = setTimeout(() => root.classList.remove('theme-anim'), 300);
|
||||
}
|
||||
|
||||
async function updateFeatureHints() {
|
||||
const data = await chrome.storage.sync.get(['dismissedHints']);
|
||||
const dismissed = Array.isArray(data.dismissedHints) ? data.dismissedHints : [];
|
||||
@@ -243,7 +324,7 @@ function setRoomRefreshCooldown() {
|
||||
async function init() {
|
||||
// Local-only by design — settings and room credentials never come from
|
||||
// storage.sync (only onboardingComplete + dismissedHints live there).
|
||||
const localData = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username', 'filterNoise', 'autoSyncNextEpisode', 'sendTabTitle', 'mediaTitlePrivacyMode', 'titlePrivacyMode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'activeTab', 'themeMode']);
|
||||
const localData = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username', 'filterNoise', 'autoSyncNextEpisode', 'sendTabTitle', 'mediaTitlePrivacyMode', 'titlePrivacyMode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'activeTab', 'themeMode', 'themePalette']);
|
||||
|
||||
let activeLang = localData.locale;
|
||||
if (!activeLang) {
|
||||
@@ -257,7 +338,9 @@ async function init() {
|
||||
|
||||
if (elements.langSelector) elements.langSelector.value = activeLang;
|
||||
if (elements.themeSelector) elements.themeSelector.value = ['dark', 'light'].includes(localData.themeMode) ? localData.themeMode : 'system';
|
||||
if (elements.paletteSelector) elements.paletteSelector.value = ['cyber', 'graphite'].includes(localData.themePalette) ? localData.themePalette : 'eucalyptus';
|
||||
syncThemePicker();
|
||||
syncPalettePicker();
|
||||
|
||||
let username = localData.username;
|
||||
if (!username) {
|
||||
@@ -1304,6 +1387,7 @@ if (elements.autoCopyInvite) {
|
||||
if (elements.themeSelector) {
|
||||
elements.themeSelector.addEventListener('change', () => {
|
||||
const themeMode = elements.themeSelector.value;
|
||||
flashThemeTransition();
|
||||
window.koalaTheme?.setMode(themeMode);
|
||||
chrome.storage.local.set({ themeMode });
|
||||
syncThemePicker();
|
||||
@@ -1318,6 +1402,25 @@ document.querySelectorAll('.theme-choice').forEach(button => {
|
||||
});
|
||||
});
|
||||
|
||||
if (elements.paletteSelector) {
|
||||
elements.paletteSelector.addEventListener('change', () => {
|
||||
const themePalette = elements.paletteSelector.value;
|
||||
flashThemeTransition();
|
||||
window.koalaTheme?.setPalette(themePalette);
|
||||
chrome.storage.local.set({ themePalette });
|
||||
syncPalettePicker();
|
||||
dismissFeatureHint('theme_picker');
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.palette-choice').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
if (!elements.paletteSelector) return;
|
||||
elements.paletteSelector.value = button.dataset.paletteValue;
|
||||
elements.paletteSelector.dispatchEvent(new window.Event('change'));
|
||||
});
|
||||
});
|
||||
|
||||
if (elements.audioSettingsLink) {
|
||||
elements.audioSettingsLink.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
+19
-3
@@ -1,8 +1,11 @@
|
||||
(() => {
|
||||
const systemTheme = window.matchMedia('(prefers-color-scheme: light)');
|
||||
let mode = 'system';
|
||||
let palette = 'eucalyptus';
|
||||
|
||||
const normalize = value => ['system', 'dark', 'light'].includes(value) ? value : 'system';
|
||||
const normalizePalette = value => ['eucalyptus', 'cyber', 'graphite'].includes(value) ? value : 'eucalyptus';
|
||||
|
||||
const apply = value => {
|
||||
mode = normalize(value);
|
||||
const light = mode === 'light' || (mode === 'system' && systemTheme.matches);
|
||||
@@ -11,10 +14,18 @@
|
||||
document.documentElement.style.colorScheme = light ? 'light' : 'dark';
|
||||
};
|
||||
|
||||
const applyPalette = value => {
|
||||
palette = normalizePalette(value);
|
||||
document.documentElement.dataset.palette = palette;
|
||||
};
|
||||
|
||||
apply('system');
|
||||
applyPalette('eucalyptus');
|
||||
window.koalaTheme = {
|
||||
getMode: () => mode,
|
||||
setMode: apply
|
||||
setMode: apply,
|
||||
getPalette: () => palette,
|
||||
setPalette: applyPalette
|
||||
};
|
||||
|
||||
systemTheme.addEventListener('change', () => {
|
||||
@@ -22,8 +33,13 @@
|
||||
});
|
||||
|
||||
if (!globalThis.chrome?.storage?.local) return;
|
||||
chrome.storage.local.get(['themeMode'], data => apply(data.themeMode));
|
||||
chrome.storage.local.get(['themeMode', 'themePalette'], data => {
|
||||
apply(data.themeMode);
|
||||
applyPalette(data.themePalette);
|
||||
});
|
||||
chrome.storage.onChanged.addListener((changes, area) => {
|
||||
if (area === 'local' && changes.themeMode) apply(changes.themeMode.newValue);
|
||||
if (area !== 'local') return;
|
||||
if (changes.themeMode) apply(changes.themeMode.newValue);
|
||||
if (changes.themePalette) applyPalette(changes.themePalette.newValue);
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user