Skip to content

Commit 89d9410

Browse files
committed
修复字幕翻译窗口暗色模式样式,保持窗口外层透明,同时让字幕条运行时背景正确使用暗色渐变
1 parent b40e042 commit 89d9410

4 files changed

Lines changed: 86 additions & 2 deletions

File tree

static/css/dark-mode.css

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,9 @@ html #chat-container {
302302

303303
/* ===== 暗色模式:字幕显示区域 ===== */
304304
[data-theme="dark"] #subtitle-display {
305-
background: linear-gradient(135deg, rgba(42, 123, 196, 0.92) 0%, rgba(42, 123, 196, 0.88) 50%, rgba(60, 145, 220, 0.92) 100%);
305+
background: linear-gradient(135deg, rgba(18, 29, 45, 0.95) 0%, rgba(22, 45, 68, 0.91) 52%, rgba(30, 74, 108, 0.95) 100%);
306306
border-color: rgba(255, 255, 255, 0.25);
307-
box-shadow: 0 8px 32px rgba(42, 123, 196, 0.5),
307+
box-shadow: 0 8px 32px rgba(6, 12, 24, 0.5),
308308
0 0 0 1px rgba(255, 255, 255, 0.2) inset;
309309
}
310310

@@ -670,6 +670,13 @@ html #chat-container {
670670
}
671671
}
672672

673+
/* 独立字幕窗口是透明 BrowserWindow;暗色移动端黑底规则会因 600px 窗口宽度命中。
674+
这里在黑底规则之后重新钉住 html/body 透明,避免字幕翻译窗口露出整块黑底。 */
675+
html[data-theme="dark"].subtitle-window-host,
676+
html[data-theme="dark"].subtitle-window-host body.subtitle-window-host {
677+
background: transparent !important;
678+
}
679+
673680
/* ===========================================================
674681
子页面通用暗色模式覆盖
675682
适用于 character_card_manager, api_key_settings, memory_browser,

static/subtitle-shared.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,9 +431,20 @@
431431
};
432432
}
433433

434+
function isDarkThemeActive() {
435+
return !!(
436+
document.documentElement &&
437+
document.documentElement.getAttribute('data-theme') === 'dark'
438+
);
439+
}
440+
434441
function applyBackgroundOpacity(display, opacity) {
435442
if (!display) return;
436443
var alpha = clampOpacity(opacity) / 100;
444+
if (isDarkThemeActive()) {
445+
display.style.background = 'linear-gradient(135deg, rgba(18,29,45,' + alpha + ') 0%, rgba(22,45,68,' + Math.max(0, alpha - 0.04) + ') 52%, rgba(30,74,108,' + alpha + ') 100%)';
446+
return;
447+
}
437448
display.style.background = 'linear-gradient(135deg, rgba(68,183,254,' + alpha + ') 0%, rgba(68,183,254,' + Math.max(0, alpha - 0.05) + ') 50%, rgba(100,200,255,' + alpha + ') 100%)';
438449
}
439450

@@ -869,6 +880,14 @@
869880
applyState(state, { changedKeys: [], source: 'init' });
870881
cleanupFns.push(subscribeSettings(applyState, { immediate: false }));
871882

883+
var onThemeChanged = function() {
884+
applyState(getSettings(), { changedKeys: ['theme'], source: 'subtitle-ui-theme' });
885+
};
886+
window.addEventListener('neko-theme-changed', onThemeChanged);
887+
cleanupFns.push(function() {
888+
window.removeEventListener('neko-theme-changed', onThemeChanged);
889+
});
890+
872891
if (window.i18next && typeof window.i18next.on === 'function') {
873892
var onLanguageChanged = function(nextLocale) {
874893
updateSettings({ uiLocale: nextLocale }, {

tests/frontend/test_subtitle_incremental.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,50 @@ def _open_subtitle_harness(
2727
mock_page.goto(f"http://neko.test{path}")
2828

2929

30+
@pytest.mark.frontend
31+
def test_subtitle_background_opacity_tracks_dark_theme(
32+
mock_page: Page,
33+
):
34+
_open_subtitle_harness(
35+
mock_page,
36+
"subtitle-window-host",
37+
"""
38+
<div id="subtitle-display">
39+
<div id="subtitle-scroll"><span id="subtitle-text"></span></div>
40+
</div>
41+
""",
42+
)
43+
mock_page.evaluate(
44+
"""
45+
() => {
46+
document.documentElement.setAttribute('data-theme', 'dark');
47+
window.localStorage.setItem('subtitleOpacity', '80');
48+
}
49+
"""
50+
)
51+
mock_page.add_script_tag(path=str(PROJECT_ROOT / "static/subtitle-shared.js"))
52+
53+
result = mock_page.evaluate(
54+
"""
55+
() => {
56+
const controller = window.nekoSubtitleShared.initSubtitleUI({ host: 'web' });
57+
const display = document.getElementById('subtitle-display');
58+
const darkBackground = display.style.background;
59+
document.documentElement.removeAttribute('data-theme');
60+
window.dispatchEvent(new CustomEvent('neko-theme-changed', {
61+
detail: { darkMode: false },
62+
}));
63+
const lightBackground = display.style.background;
64+
controller.destroy();
65+
return { darkBackground, lightBackground };
66+
}
67+
"""
68+
)
69+
70+
assert "rgba(18, 29, 45, 0.8)" in result["darkBackground"]
71+
assert "rgba(68, 183, 254, 0.8)" in result["lightBackground"]
72+
73+
3074
@pytest.mark.frontend
3175
def test_subtitle_incremental_translation_starts_when_sentence_punctuation_arrives(
3276
mock_page: Page,

tests/unit/test_react_chat_window_static.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
APP_CHAT_EXPORT_PATH = Path(__file__).resolve().parents[2] / "static" / "app-chat-export.js"
77
MUSIC_UI_PATH = Path(__file__).resolve().parents[2] / "static" / "music_ui.js"
88
STATIC_INDEX_CSS_PATH = Path(__file__).resolve().parents[2] / "static" / "css" / "index.css"
9+
STATIC_DARK_MODE_CSS_PATH = Path(__file__).resolve().parents[2] / "static" / "css" / "dark-mode.css"
910
REACT_CHAT_STYLES_PATH = Path(__file__).resolve().parents[2] / "frontend" / "react-neko-chat" / "src" / "styles.css"
1011
REACT_CHAT_APP_PATH = Path(__file__).resolve().parents[2] / "frontend" / "react-neko-chat" / "src" / "App.tsx"
1112
CHAT_TEMPLATE_PATH = Path(__file__).resolve().parents[2] / "templates" / "chat.html"
@@ -39,6 +40,19 @@ def assert_no_layout_transition(block: str) -> None:
3940
assert prop not in transition_section
4041

4142

43+
def test_subtitle_window_dark_mode_keeps_transparent_background():
44+
source = STATIC_DARK_MODE_CSS_PATH.read_text(encoding="utf-8")
45+
selector = (
46+
'html[data-theme="dark"].subtitle-window-host,\n'
47+
'html[data-theme="dark"].subtitle-window-host body.subtitle-window-host {'
48+
)
49+
assert selector in source
50+
block = source.split(selector, 1)[1].split("}", 1)[0]
51+
52+
assert "background: transparent !important;" in block
53+
assert source.index(selector) > source.index("background: #000 !important;")
54+
55+
4256
def test_chat_surface_mode_preference_is_shared_with_electron():
4357
source = APP_REACT_CHAT_WINDOW_PATH.read_text(encoding="utf-8")
4458

0 commit comments

Comments
 (0)