Merge remote-tracking branch 'origin/main' into fix/1857-reduced-motion

# Conflicts:
#	CHANGELOG.md
#	frontend/src/i18n/locales/ar.json
#	frontend/src/i18n/locales/de.json
#	frontend/src/i18n/locales/es.json
#	frontend/src/i18n/locales/fr.json
#	frontend/src/i18n/locales/hi.json
#	frontend/src/i18n/locales/id.json
#	frontend/src/i18n/locales/it.json
#	frontend/src/i18n/locales/ja.json
#	frontend/src/i18n/locales/ko.json
#	frontend/src/i18n/locales/nl.json
#	frontend/src/i18n/locales/pl.json
#	frontend/src/i18n/locales/pt.json
#	frontend/src/i18n/locales/ru.json
#	frontend/src/i18n/locales/sv.json
#	frontend/src/i18n/locales/th.json
#	frontend/src/i18n/locales/tr.json
#	frontend/src/i18n/locales/uk.json
#	frontend/src/i18n/locales/vi.json
#	frontend/src/i18n/locales/zh-CN.json
#	frontend/src/i18n/locales/zh-TW.json
This commit is contained in:
Palash Debnath
2026-09-09 21:27:37 -07:00
25 changed files with 155 additions and 11 deletions
+1
View File
@@ -10,6 +10,7 @@ the frozen-backend fallback mirror it for their toolchains.
**Highlights**
- A Reduce motion switch in Settings, for calm without changing your whole system (#1857)
- A light theme, and System Auto now follows a light-mode OS instead of staying dark (#1973) — thanks @CoDe-ReDz!
- Generating from a one-character input now says the input was too short, instead of quoting a convolution error (#1826)
- First run asks about text size before the install, not after it (#1849)
- Cloning without a reference clip now says so, instead of naming library parameters you cannot set (#1879)
@@ -13,12 +13,19 @@ import { useAppStore, FONT_OPTIONS, FONT_STACKS } from '../../store';
import { SettingsSection, SettingRow, InfoHint, SettingsToggle } from './primitives';
const THEMES = [
{
id: 'auto',
labelKey: 'settings.theme_auto',
defaultLabel: 'System Auto',
dot: 'linear-gradient(135deg, #fdf6e3 50%, #1d2021 50%)',
},
{ id: 'gruvbox', label: 'Gruvbox', dot: '#d3869b' },
{ id: 'midnight', label: 'Midnight', dot: '#8b5cf6' },
{ id: 'nord', label: 'Nord', dot: '#88c0d0' },
{ id: 'solarized', label: 'Solarized', dot: '#268bd2' },
{ id: 'rose-pine', label: 'Rosé Pine', dot: '#ebbcba' },
{ id: 'catppuccin', label: 'Catppuccin', dot: '#cba6f7' },
{ id: 'light', labelKey: 'settings.theme_light', defaultLabel: 'Light', dot: '#1d6b9f' },
];
/**
@@ -204,8 +211,10 @@ export default function AppearancePanel() {
style={{ '--dot-color': th.dot }}
onClick={() => setTheme(th.id)}
onKeyDown={(e) => radioGroupKeyDown(e, themeIds, theme, setTheme)}
title={th.label}
aria-label={th.label}
title={th.labelKey ? t(th.labelKey, { defaultValue: th.defaultLabel }) : th.label}
aria-label={
th.labelKey ? t(th.labelKey, { defaultValue: th.defaultLabel }) : th.label
}
aria-checked={theme === th.id}
role="radio"
tabIndex={radioTabIndex(themeIds, theme, th.id)}
@@ -149,3 +149,21 @@ describe('AppearancePanel — navigation style picker', () => {
useAppStore.getState().setNavStyle('rail');
});
});
describe('AppearancePanel — theme selection', () => {
it('renders localized labels for the Auto and Light themes', () => {
render(<AppearancePanel />);
// The test runner uses the `defaultValue` from our t() hooks.
// This verifies the labels are correctly exposed to screen readers via aria-label.
const autoTheme = screen.getByRole('radio', { name: 'System Auto' });
const lightTheme = screen.getByRole('radio', { name: 'Light' });
expect(autoTheme).toBeInTheDocument();
expect(lightTheme).toBeInTheDocument();
// Verify it generates the correct data attribute for the OS-sync theme
expect(autoTheme).toHaveAttribute('data-radio-value', 'auto');
expect(lightTheme).toHaveAttribute('data-radio-value', 'light');
});
});
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (مشفّر، موصى به)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "متغير البيئة",
"theme_auto": "تلقائي حسب النظام",
"theme_light": "فاتح",
"reduce_motion": "تقليل الحركة",
"reduce_motion_desc": "يوقف الرسوم المتحركة في الخلفية والانتقالات. إعداد النظام يظل ساريًا بذاته؛ وهذا يضاف إليه."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (verschlüsselt, empfohlen)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Umgebungsvariable",
"theme_auto": "System-Standard",
"theme_light": "Hell",
"reduce_motion": "Bewegung reduzieren",
"reduce_motion_desc": "Stoppt Hintergrundanimationen und Übergänge. Die Systemeinstellung gilt weiterhin für sich; dies kommt hinzu."
},
+2
View File
@@ -695,6 +695,8 @@
"device": "Device & compute",
"device_desc": "Live hardware and backend readouts.",
"network_desc": "Proxy for downloads and model fetches.",
"theme_auto": "System Auto",
"theme_light": "Light",
"translation_desc": "How dubbing translates dialogue, and which engine does it.",
"translate_quality": "Translation quality",
"translate_quality_desc": "Fast is literal and quick; Cinematic uses the LLM for natural phrasing.",
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (cifrado, recomendado)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Variable de entorno",
"theme_auto": "Automático del sistema",
"theme_light": "Claro",
"reduce_motion": "Reducir movimiento",
"reduce_motion_desc": "Detiene las animaciones de fondo y las transiciones. Tu ajuste del sistema se respeta por sí solo; esto se suma."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (chiffré, recommandé)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Variable denvironnement",
"theme_auto": "Auto (Système)",
"theme_light": "Clair",
"reduce_motion": "Réduire les animations",
"reduce_motion_desc": "Arrête les animations darrière-plan et les transitions. Le réglage système reste actif de son côté ; ceci sy ajoute."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (एन्क्रिप्ट किया गया, अनुशंसित)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "परिवेश चर",
"theme_auto": "सिस्टम डिफ़ॉल्ट",
"theme_light": "लाइट",
"reduce_motion": "गति कम करें",
"reduce_motion_desc": "पृष्ठभूमि एनिमेशन और ट्रांज़िशन रोकता है। आपकी सिस्टम सेटिंग स्वयं लागू रहती है; यह उसमें जुड़ता है।"
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (terenkripsi, disarankan)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Variabel lingkungan",
"theme_auto": "Otomatis Sistem",
"theme_light": "Terang",
"reduce_motion": "Kurangi gerakan",
"reduce_motion_desc": "Menghentikan animasi latar dan transisi. Pengaturan sistem Anda tetap berlaku sendiri; ini menambahnya."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (crittografato, consigliato)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Variabile dambiente",
"theme_auto": "Automatico di sistema",
"theme_light": "Chiaro",
"reduce_motion": "Riduci le animazioni",
"reduce_motion_desc": "Ferma le animazioni di sfondo e le transizioni. Limpostazione di sistema resta valida da sola; questa si aggiunge."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio(暗号化済み、推奨)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "環境変数",
"theme_auto": "システム自動",
"theme_light": "ライト",
"reduce_motion": "視差効果を減らす",
"reduce_motion_desc": "背景アニメーションとトランジションを停止します。システム設定は単独で有効なままで、これはそれに加わります。"
},
+2
View File
@@ -576,6 +576,8 @@
"generate_timeout_shadowed_note": "VoiceStudio 외부의 환경 변수(셸, .env 파일 또는 컨테이너)가 현재 이 값을 설정하고 있습니다 — 해당 변수가 제거될 때까지 여기에 저장한 값은 무시됩니다.",
"generate_timeout_shadowed_toast": "저장되었지만, VoiceStudio 외부의 환경 변수가 현재 이 값을 설정하고 있습니다 — 해당 변수가 제거될 때까지 계속 사용됩니다.",
"hf_token_not_checked": "테스트 안 함",
"theme_auto": "시스템 자동",
"theme_light": "라이트",
"reduce_motion": "모션 줄이기",
"reduce_motion_desc": "배경 애니메이션과 전환을 중지합니다. 시스템 설정은 그대로 적용되며, 이 옵션은 거기에 더해집니다."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (versleuteld, aanbevolen)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Omgevingsvariabele",
"theme_auto": "Systeem auto",
"theme_light": "Licht",
"reduce_motion": "Beweging verminderen",
"reduce_motion_desc": "Stopt achtergrondanimaties en overgangen. Je systeeminstelling blijft op zichzelf gelden; dit komt daarbij."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (zaszyfrowane, zalecane)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Zmienna środowiskowa",
"theme_auto": "Automatyczny systemowy",
"theme_light": "Jasny",
"reduce_motion": "Ogranicz ruch",
"reduce_motion_desc": "Zatrzymuje animacje tła i przejścia. Ustawienie systemowe działa samo z siebie; to je uzupełnia."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (criptografado, recomendado)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Variável de ambiente",
"theme_auto": "Automático do Sistema",
"theme_light": "Claro",
"reduce_motion": "Reduzir movimento",
"reduce_motion_desc": "Para as animações de fundo e as transições. A definição do sistema continua a ser respeitada; esta acrescenta-se."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (зашифровано, рекомендуется)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Переменная окружения",
"theme_auto": "Системный",
"theme_light": "Светлая",
"reduce_motion": "Уменьшить движение",
"reduce_motion_desc": "Останавливает фоновые анимации и переходы. Системная настройка действует сама по себе; эта добавляется к ней."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (krypterad, rekommenderas)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Miljövariabel",
"theme_auto": "Systemval",
"theme_light": "Ljust",
"reduce_motion": "Minska rörelse",
"reduce_motion_desc": "Stoppar bakgrundsanimationer och övergångar. Systeminställningen gäller ändå; det här läggs till."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (เข้ารหัส แนะนำ)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "ตัวแปรสภาพแวดล้อม",
"theme_auto": "อัตโนมัติตามระบบ",
"theme_light": "สว่าง",
"reduce_motion": "ลดการเคลื่อนไหว",
"reduce_motion_desc": "หยุดแอนิเมชันพื้นหลังและทรานซิชัน การตั้งค่าระบบยังมีผลด้วยตัวเอง ส่วนนี้เพิ่มเข้าไป"
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (şifrelenmiş, önerilen)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Ortam değişkeni",
"theme_auto": "Sistem Otomatik",
"theme_light": "Açık",
"reduce_motion": "Hareketi azalt",
"reduce_motion_desc": "Arka plan animasyonlarını ve geçişleri durdurur. Sistem ayarınız kendi başına geçerlidir; bu ona eklenir."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (зашифровано, рекомендовано)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Змінна середовища",
"theme_auto": "Системний",
"theme_light": "Світла",
"reduce_motion": "Зменшити рух",
"reduce_motion_desc": "Зупиняє фонові анімації та переходи. Системне налаштування діє саме по собі; це доповнює його."
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio (được mã hóa, khuyên dùng)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "Biến môi trường",
"theme_auto": "Tự động theo hệ thống",
"theme_light": "Sáng",
"reduce_motion": "Giảm chuyển động",
"reduce_motion_desc": "Dừng hoạt ảnh nền và hiệu ứng chuyển tiếp. Cài đặt hệ thống vẫn có hiệu lực riêng; mục này bổ sung thêm."
},
+2
View File
@@ -841,6 +841,8 @@
"generate_timeout_shadowed_note": "VoiceStudio 之外的一个环境变量(终端、.env 文件或容器)当前正在设置此值——在移除该变量之前,此处保存的值会被忽略。",
"generate_timeout_shadowed_toast": "已保存,但 VoiceStudio 之外的一个环境变量当前正在设置此值——在移除该变量之前,将继续使用该值。",
"hf_token_not_checked": "未测试",
"theme_auto": "系统自动",
"theme_light": "浅色",
"reduce_motion": "减少动效",
"reduce_motion_desc": "停止背景动画和过渡效果。系统设置本身仍然生效,此项在其基础上叠加。"
},
+2
View File
@@ -354,6 +354,8 @@
"hf_source_app_label": "VoiceStudio(已加密,建議)",
"hf_source_cli_label": "HuggingFace CLI",
"hf_source_env_label": "環境變數",
"theme_auto": "系統自動",
"theme_light": "淺色",
"reduce_motion": "減少動態效果",
"reduce_motion_desc": "停止背景動畫與轉場效果。系統設定本身仍然生效,此項在其基礎上疊加。"
},
+83 -9
View File
@@ -563,18 +563,78 @@
--select-caret: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2210%22%20height%3D%226%22%3E%3Cpath%20d%3D%22M1%201l4%204%204-4%22%20fill%3D%22none%22%20stroke%3D%22%23a6adc8%22%20stroke-width%3D%221.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
}
/* ── Light (Solarized Light) ───────────────────────────────────────── */
[data-theme="light"] {
--color-fg: #073642;
--color-fg-muted: #586e75;
--color-fg-subtle: #4a5a60;
--color-fg-inverse: #fdf6e3;
--color-bg: #fdf6e3;
--color-bg-elev-1: rgba(238, 232, 213, 0.85);
--color-bg-elev-2: rgba(238, 232, 213, 0.40);
--color-bg-elev-3: rgba(238, 232, 213, 0.25);
--color-border: rgba(7, 54, 66, 0.12);
--color-border-strong: rgba(7, 54, 66, 0.20);
--color-border-warm: rgba(181, 137, 0, 0.15);
--color-brand: #1d6b9f;
--color-brand-hover: #144b70;
--color-brand-glow: rgba(29, 107, 159, 0.25);
--color-accent: #8c6b00;
--color-success: #667500;
--color-warn: #9c3a11;
--color-danger: #a82624;
--color-info: #515594;
--chrome-bg: #eee8d5;
--chrome-fg: #073642;
--chrome-fg-muted: #4d5f66;
--chrome-fg-dim: #4a5a60;
--chrome-border: #d5ccb7;
color-scheme: light;
--select-caret: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2210%22%20height%3D%226%22%3E%3Cpath%20d%3D%22M1%201l4%204%204-4%22%20fill%3D%22none%22%20stroke%3D%22%23586e75%22%20stroke-width%3D%221.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
}
/* ── System-preference sync ────────────────────────────────────────── */
/* When theme is "auto", the light theme activates on light-mode OS.
Currently we don't ship a light theme; this is scaffolding for
community contributions. */
/* When theme is "auto", the light theme activates on light-mode OS. */
@media (prefers-color-scheme: light) {
[data-theme="auto"] {
/* Future light theme tokens go here. NOTE: no light theme ships yet, so
"auto" still renders the dark default tokens even on a light-mode OS
which is why `color-scheme` is intentionally left at the :root `dark`
here (a `light` value would make native form chrome mismatch the still
-dark surface). When a real light theme lands, set `color-scheme: light`
and its own --select-caret in this block. */
--color-fg: #073642;
--color-fg-muted: #586e75;
--color-fg-subtle: #4a5a60;
--color-fg-inverse: #fdf6e3;
--color-bg: #fdf6e3;
--color-bg-elev-1: rgba(238, 232, 213, 0.85);
--color-bg-elev-2: rgba(238, 232, 213, 0.40);
--color-bg-elev-3: rgba(238, 232, 213, 0.25);
--color-border: rgba(7, 54, 66, 0.12);
--color-border-strong: rgba(7, 54, 66, 0.20);
--color-border-warm: rgba(181, 137, 0, 0.15);
--color-brand: #1d6b9f;
--color-brand-hover: #144b70;
--color-brand-glow: rgba(29, 107, 159, 0.25);
--color-accent: #8c6b00;
--color-success: #667500;
--color-warn: #9c3a11;
--color-danger: #a82624;
--color-info: #515594;
--chrome-bg: #eee8d5;
--chrome-fg: #073642;
--chrome-fg-muted: #4d5f66;
--chrome-fg-dim: #4a5a60;
--chrome-border: #d5ccb7;
color-scheme: light;
--select-caret: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2210%22%20height%3D%226%22%3E%3Cpath%20d%3D%22M1%201l4%204%204-4%22%20fill%3D%22none%22%20stroke%3D%22%23586e75%22%20stroke-width%3D%221.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
}
}
@@ -7231,6 +7291,20 @@ button.dub-stepper__action:focus-visible {
max-width: 240px;
}
/* Override header gradient for light modes */
[data-theme="light"] .header-area h1 {
background: linear-gradient(135deg, var(--chrome-fg) 0%, var(--chrome-fg-muted) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
@media (prefers-color-scheme: light) {
[data-theme="auto"] .header-area h1 {
background: linear-gradient(135deg, var(--chrome-fg) 0%, var(--chrome-fg-muted) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
/*
APP-WIDE BORDER / DIVIDER REMOVAL (owner-approved)
Zero every decorative border/hairline token. Kept LAST in source order so