Sidebar refactor + app multilanguage + popup resize/drag#8
Open
FieteGM wants to merge 5 commits into
Open
Conversation
Module extraction
- Extract bingewatcher.py helpers into bw/{constants,settings_types,
shutdown,session,episode_logic,player_loop}.py
- Move sidebar JS out of inline Python r-strings into bw/assets/
(sidebar_inject.js, sidebar_polish.js, continue_watching.js)
- Split sidebar_inject.js into 7 chunk files under bw/assets/sidebar/
and aggregate via bw/ui/{header,series_list,cast_modal,tools_menu,
settings_panel}.py. bw/ui_sidebar.py becomes a re-export shim.
- bingewatcher.py shrinks ~10.5k -> ~8.8k lines
Test + lint infrastructure
- Add tests/ with pytest wrappers around the legacy selftest_phase*.py
modules, plus targeted suites (imports, i18n parity, settings
validation, JS asset integrity, shutdown markers, episode logic,
Selenium smoke against tests/fixtures/jw_player.html)
- pyproject.toml: ruff + mypy config, per-file overrides
- pytest.ini: slow marker for live-network tests
- .github/workflows/ci.yml: lint, typecheck, fast+slow matrix
- scripts/_split_sidebar_inject.py, _extract_player_loop.py and
friends — reproducible build steps for the JS chunking and the
play_loop helper-binding migration
JS bug fixes
- Fix 8 SyntaxErrors in sidebar_inject.js where i18n fallback
expressions used template literal ${...} syntax inside
single-quoted strings instead of backticks (privacy popover, cast
modal, audio hint, bookmarks label). Validated with node --check.
UX polish
- Add window.__bwAttachResize helper: bottom-right grip on Tools,
Settings, and SeriesSkip popups. Minimum size = default; max =
viewport - 16. Persists per-popup to localStorage. Double-click
to reset.
- Tools menu: drag handle on TOOLS heading with grip glyph + drag
hint, close X anchored right via margin-left:auto
- Settings panel: width bumped 480 -> min(540, viewport-40); X
pinned to right edge; dropdowns get fixed 180px column,
color-scheme:dark + custom chevron + dark-styled <option>; text
inputs match the dropdown column; ellipsis on long labels
- Cast modal: "Auf Handy schauen" -> cast_modal_title ("Stream to
another device" / "Auf anderes Gerat streamen")
- Watchlist cleanup: drop entries matching auto-add fingerprint
(S1E1, pos=0, ts=0)
i18n sweep
- Add ~80 keys (EN+DE parity, 456 = 456). Covers: cast modal body
+ buttons + privacy details, OS-hardening modal (status,
counts, fix instructions, buttons), context menu (pin,
watchlist, mark watched, reset progress, delete), Tools menu
drag hint + close, resize grip hint, privacy session popover
(verdict, host table headers, route badges), FilmPalast
no-results, bookmarks label, audio picker, skip-times heading,
toolbar strings (translated directly since the iframe context
cant reach window.__bwI18n)
- Python side: bw/captcha.py and bw/sto_player_gate.py resolve
user-facing messages via i18n.t() at call time
- bw/firefox_detect.py, bw/screen_stream.py CLI help fully
translated to English
- bw/player_loop.py, bingewatcher.py docstrings: German -> English
Selftest updates
- selftest_phase1, selftest_phase4: also scan bw/player_loop.py
for content that moved out of bingewatcher.py
- selftest_privacy_ui: scan ui_sidebar.SIDEBAR_INJECT_JS (the
assembled bundle) instead of just ui_sidebar.py source, and
align checked IDs with the post-Task-13 privacy tab redesign
Verification
- 159 tests pass (1 deselected: selftest_screen_pipeline needs
live HLS, pre-existing)
- node --check on bundle: clean
- i18n parity: EN 456 / DE 456
- Bundle byte-identity preserved between bw/assets/sidebar/* chunks
and bw/assets/sidebar_inject.js
Comment on lines
+309
to
+655
| p.innerHTML = ( | ||
| '<style>' + ( | ||
| '#bwSettingsPanel .bw-set-head {' + | ||
| 'display:flex;align-items:center;gap:10px;padding:14px 18px 12px;' + | ||
| 'border-bottom:1px solid rgba(255,255,255,.08);cursor:move;flex-shrink:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-title {' + | ||
| 'font:700 16px/1 system-ui;' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);' + | ||
| '-webkit-background-clip:text;-webkit-text-fill-color:transparent;flex-shrink:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-search {' + | ||
| 'flex:1 1 auto;min-width:0;width:100%;' + | ||
| 'padding:6px 10px;border-radius:8px;border:1px solid rgba(255,255,255,.10);' + | ||
| 'background:rgba(2,6,23,.55);color:#e2e8f0;font:500 12px/1.2 system-ui;outline:none;' + | ||
| 'cursor:text;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-search:focus { border-color:rgba(99,102,241,.55); }' + | ||
| // Close button — ``margin-left:auto`` keeps it | ||
| // pinned to the right edge of the header even | ||
| // when the user resizes the panel wider than | ||
| // the search field naturally fills. | ||
| '#bwSettingsPanel .bw-set-close {' + | ||
| 'margin-left:auto;flex-shrink:0;' + | ||
| 'background:transparent;border:0;color:#94a3b8;cursor:pointer;' + | ||
| 'font-size:22px;line-height:1;padding:2px 6px;border-radius:6px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-close:hover { background:rgba(255,255,255,.08); color:#f8fafc; }' + | ||
| // Tabs | ||
| '#bwSettingsPanel .bw-set-tabs {' + | ||
| 'display:flex;gap:2px;padding:0 12px;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.18);' + | ||
| 'flex-shrink:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-tab {' + | ||
| 'flex:1;padding:10px 8px;background:transparent;border:0;border-bottom:2px solid transparent;' + | ||
| 'color:#94a3b8;font:600 12px/1.2 system-ui;cursor:pointer;display:flex;align-items:center;gap:6px;justify-content:center;' + | ||
| 'transition:color .15s ease,border-color .15s ease;letter-spacing:.2px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-tab:hover { color:#cbd5e1; }' + | ||
| '#bwSettingsPanel .bw-set-tab.active { color:#f8fafc; border-bottom-color:#6366f1; }' + | ||
| '#bwSettingsPanel .bw-set-tab .bw-tab-count {' + | ||
| 'display:inline-flex;align-items:center;justify-content:center;' + | ||
| 'min-width:18px;height:18px;padding:0 5px;border-radius:9px;' + | ||
| 'background:rgba(99,102,241,.22);color:#c7d2fe;font:600 10px/1 ui-monospace,Menlo;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-tab.active .bw-tab-count {' + | ||
| 'background:rgba(99,102,241,.42);color:#e0e7ff;' + | ||
| '}' + | ||
| // Body | ||
| '#bwSettingsPanel .bw-set-body {' + | ||
| 'padding:14px 18px 16px;overflow-y:auto;flex:1;min-height:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-pane[hidden] { display:none; }' + | ||
| '#bwSettingsPanel .bw-set-section-title {' + | ||
| 'font:700 11px/1 system-ui;letter-spacing:.6px;text-transform:uppercase;' + | ||
| 'color:#64748b;margin:8px 4px 8px;' + | ||
| '}' + | ||
| // Row — two columns: label (grows) + control (fixed). | ||
| // ``min-width:0`` on the label lets long labels | ||
| // truncate with an ellipsis rather than pushing the | ||
| // control off the right edge of the panel. | ||
| '#bwSettingsPanel .bw-set-row {' + | ||
| 'display:flex;align-items:center;gap:14px;padding:10px 6px;' + | ||
| 'border-radius:8px;transition:background .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-row[hidden] { display:none; }' + | ||
| '#bwSettingsPanel .bw-set-row:hover { background:rgba(255,255,255,.03); }' + | ||
| '#bwSettingsPanel .bw-set-row.bw-search-hit { background:rgba(99,102,241,.10); }' + | ||
| '#bwSettingsPanel .bw-set-row > label {' + | ||
| 'flex:1;min-width:0;display:flex;align-items:center;gap:8px;' + | ||
| 'cursor:pointer;color:#e2e8f0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-label {' + | ||
| 'font:600 13px/1.35 system-ui;' + | ||
| 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;' + | ||
| 'min-width:0;' + | ||
| '}' + | ||
| // Info icon button | ||
| '#bwSettingsPanel .bw-set-info {' + | ||
| 'width:18px;height:18px;border-radius:50%;padding:0;' + | ||
| 'background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);' + | ||
| 'color:#94a3b8;font:700 11px/1 ui-serif,Georgia;cursor:pointer;font-style:italic;' + | ||
| 'display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;' + | ||
| 'transition:background .15s ease,color .15s ease,border-color .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-info:hover {' + | ||
| 'background:rgba(99,102,241,.22);color:#c7d2fe;border-color:rgba(99,102,241,.45);' + | ||
| '}' + | ||
| // Restart badge | ||
| '#bwSettingsPanel .bw-set-restart {' + | ||
| 'display:inline-flex;align-items:center;justify-content:center;' + | ||
| 'width:18px;height:18px;border-radius:50%;' + | ||
| 'background:rgba(245,158,11,.16);color:#fbbf24;' + | ||
| 'font:600 11px/1 system-ui;cursor:help;flex-shrink:0;' + | ||
| '}' + | ||
| // Toggle switch (reused from old) | ||
| '#bwSettingsPanel .bw-set-toggle { position:relative;width:34px;height:20px;flex-shrink:0;cursor:pointer; }' + | ||
| '#bwSettingsPanel .bw-set-toggle input { opacity:0;width:0;height:0; }' + | ||
| '#bwSettingsPanel .bw-set-toggle .bw-set-slider {' + | ||
| 'position:absolute;inset:0;background:rgba(255,255,255,.14);' + | ||
| 'border-radius:99px;transition:background .2s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-toggle .bw-set-slider::before {' + | ||
| 'content:"";position:absolute;top:2px;left:2px;' + | ||
| 'width:16px;height:16px;border-radius:50%;background:#fff;' + | ||
| 'box-shadow:0 1px 3px rgba(0,0,0,.35);transition:left .2s cubic-bezier(.22,.61,.36,1);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-toggle input:checked + .bw-set-slider {' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-toggle input:checked + .bw-set-slider::before { left:16px; }' + | ||
| // Select — explicit dark theming so the native | ||
| // dropdown panel (the OS popup) stays dark too. | ||
| // ``color-scheme:dark`` switches the UA picker | ||
| // to a dark palette on Chromium/Edge; Firefox | ||
| // honours the styled ``option`` elements below. | ||
| // | ||
| // FIXED-WIDTH control column: ``flex:0 0 180px`` | ||
| // keeps every dropdown at the same predictable | ||
| // width regardless of the longest option, which | ||
| // means rows align cleanly and there's no | ||
| // horizontal scrollbar from a stretched select. | ||
| '#bwSettingsPanel select {' + | ||
| 'flex:0 0 180px;width:180px;box-sizing:border-box;' + | ||
| 'background-color:rgba(255,255,255,.06);' + | ||
| 'border:1px solid rgba(255,255,255,.14);' + | ||
| 'color:#e2e8f0;border-radius:8px;padding:6px 30px 6px 12px;' + | ||
| 'font:600 12px/1.2 system-ui;cursor:pointer;' + | ||
| 'color-scheme:dark;' + | ||
| '-webkit-appearance:none;-moz-appearance:none;appearance:none;' + | ||
| 'background-image:url("data:image/svg+xml;utf8,' + | ||
| '<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'10\' height=\'6\' viewBox=\'0 0 10 6\'>' + | ||
| '<path fill=\'%2394a3b8\' d=\'M0 0l5 6 5-6z\'/></svg>");' + | ||
| 'background-repeat:no-repeat;background-position:right 10px center;' + | ||
| 'transition:border-color .15s ease,background-color .15s ease;' + | ||
| 'text-overflow:ellipsis;' + | ||
| '}' + | ||
| '#bwSettingsPanel select:hover {' + | ||
| 'border-color:rgba(99,102,241,.45);background-color:rgba(255,255,255,.08);' + | ||
| '}' + | ||
| '#bwSettingsPanel select:focus {' + | ||
| 'border-color:rgba(99,102,241,.65);outline:none;' + | ||
| 'box-shadow:0 0 0 2px rgba(99,102,241,.22);' + | ||
| '}' + | ||
| // Firefox respects styled options. Chromium also | ||
| // picks these up when the OS supports dark UA. | ||
| '#bwSettingsPanel select option {' + | ||
| 'background-color:#0f172a;color:#e2e8f0;' + | ||
| 'padding:6px 10px;' + | ||
| '}' + | ||
| '#bwSettingsPanel select option:checked {' + | ||
| 'background:linear-gradient(0deg,rgba(99,102,241,.35),rgba(99,102,241,.35)),#0f172a;' + | ||
| 'color:#ffffff;' + | ||
| '}' + | ||
| '#bwSettingsPanel select option:disabled {' + | ||
| 'color:#475569;' + | ||
| '}' + | ||
| // Range slider — fixed 140px so it lines up with | ||
| // the dropdown column. The companion value | ||
| // (.bw-set-range-val) sits to the right. | ||
| '#bwSettingsPanel input[type=range] {' + | ||
| 'flex:0 0 140px;width:140px;height:4px;-webkit-appearance:none;appearance:none;' + | ||
| 'background:rgba(255,255,255,.10);border-radius:99px;outline:none;cursor:pointer;' + | ||
| '}' + | ||
| '#bwSettingsPanel input[type=range]::-webkit-slider-thumb {' + | ||
| '-webkit-appearance:none;width:14px;height:14px;border-radius:50%;' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);cursor:pointer;border:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel input[type=range]::-moz-range-thumb {' + | ||
| 'width:14px;height:14px;border-radius:50%;' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);cursor:pointer;border:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-range-val {' + | ||
| 'flex:0 0 38px;font:600 12px/1 system-ui;color:#cbd5e1;' + | ||
| 'font-variant-numeric:tabular-nums;text-align:right;' + | ||
| '}' + | ||
| // Text input — same fixed 180px column as the | ||
| // selects so the right edge of every control | ||
| // lines up across all rows. | ||
| '#bwSettingsPanel input[type=text] {' + | ||
| 'flex:0 0 180px;width:180px;box-sizing:border-box;' + | ||
| 'padding:6px 10px;border-radius:8px;' + | ||
| 'border:1px solid rgba(255,255,255,.12);background:rgba(2,6,23,.45);' + | ||
| 'color:#e2e8f0;font:500 12px/1.3 system-ui;min-width:0;' + | ||
| 'transition:border-color .15s ease,background-color .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel input[type=text]:hover {' + | ||
| 'border-color:rgba(99,102,241,.4);background-color:rgba(2,6,23,.6);' + | ||
| '}' + | ||
| '#bwSettingsPanel input[type=text]:focus {' + | ||
| 'border-color:rgba(99,102,241,.65);outline:none;' + | ||
| 'box-shadow:0 0 0 2px rgba(99,102,241,.22);' + | ||
| '}' + | ||
| // Privacy score | ||
| '#bwSettingsPanel .bw-priv-score {' + | ||
| 'padding:14px 14px;border-radius:12px;cursor:pointer;' + | ||
| 'background:linear-gradient(135deg,rgba(99,102,241,.10),rgba(139,92,246,.06));' + | ||
| 'border:1px solid rgba(99,102,241,.22);margin-bottom:14px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score:hover { border-color:rgba(99,102,241,.40); }' + | ||
| '#bwSettingsPanel .bw-priv-score-row {' + | ||
| 'display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score-heading { font:700 13px/1 system-ui;color:#cbd5e1; }' + | ||
| '#bwSettingsPanel .bw-priv-score-num {' + | ||
| 'font:800 28px/1 system-ui;font-variant-numeric:tabular-nums;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score-bar {' + | ||
| 'width:100%;height:6px;border-radius:3px;background:rgba(2,6,23,.5);overflow:hidden;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score-fill {' + | ||
| 'height:100%;transition:width .35s ease,background .25s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score-sub {' + | ||
| 'font:500 11px/1.4 system-ui;color:#94a3b8;margin-top:6px;' + | ||
| '}' + | ||
| // Presets | ||
| '#bwSettingsPanel .bw-priv-preset-block { margin-bottom:14px; }' + | ||
| '#bwSettingsPanel .bw-priv-presets {' + | ||
| 'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset {' + | ||
| 'display:flex;flex-direction:column;align-items:flex-start;gap:4px;' + | ||
| 'padding:10px 10px;border-radius:10px;cursor:pointer;text-align:left;' + | ||
| 'background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);' + | ||
| 'color:#cbd5e1;transition:all .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset:hover {' + | ||
| 'background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.18);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset.active {' + | ||
| 'background:linear-gradient(135deg,rgba(99,102,241,.18),rgba(139,92,246,.10));' + | ||
| 'border-color:rgba(99,102,241,.55);color:#e0e7ff;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset-name { font:700 12px/1 system-ui; }' + | ||
| '#bwSettingsPanel .bw-priv-preset-desc {' + | ||
| 'font:500 10px/1.3 system-ui;color:#94a3b8;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset.active .bw-priv-preset-desc { color:#c7d2fe; }' + | ||
| '#bwSettingsPanel .bw-priv-custom-pill {' + | ||
| 'display:inline-block;margin-top:8px;padding:2px 8px;border-radius:8px;' + | ||
| 'background:rgba(245,158,11,.16);border:1px solid rgba(245,158,11,.30);' + | ||
| 'color:#fbbf24;font:600 11px/1.4 system-ui;' + | ||
| '}' + | ||
| // Show advanced toggle | ||
| '#bwSettingsPanel .bw-priv-advanced-toggle {' + | ||
| 'display:flex;align-items:center;gap:8px;width:100%;' + | ||
| 'padding:9px 12px;border-radius:10px;cursor:pointer;' + | ||
| 'background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);' + | ||
| 'color:#94a3b8;font:600 12px/1 system-ui;text-align:left;margin-bottom:10px;' + | ||
| 'transition:background .15s ease,color .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-advanced-toggle:hover {' + | ||
| 'background:rgba(255,255,255,.06);color:#cbd5e1;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-advanced-toggle.open { color:#cbd5e1; }' + | ||
| '#bwSettingsPanel .bw-priv-advanced-chev {' + | ||
| 'font-size:10px;color:#94a3b8;' + | ||
| '}' + | ||
| // Forensik card | ||
| '#bwSettingsPanel .bw-forensik-card {' + | ||
| 'padding:12px;border-radius:12px;' + | ||
| 'background:rgba(2,6,23,.4);border:1px solid rgba(255,255,255,.08);' + | ||
| 'margin-bottom:10px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-card-head {' + | ||
| 'display:flex;align-items:center;gap:6px;margin-bottom:2px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-card-sub {' + | ||
| 'font:500 11px/1.4 system-ui;color:#64748b;margin-bottom:10px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tiles {' + | ||
| 'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:8px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile {' + | ||
| 'display:flex;flex-direction:column;align-items:center;gap:4px;' + | ||
| 'padding:10px 6px;border-radius:10px;cursor:pointer;text-align:center;' + | ||
| 'background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08);' + | ||
| 'color:#cbd5e1;transition:all .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile:hover {' + | ||
| 'background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.20);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile.active {' + | ||
| 'background:linear-gradient(135deg,rgba(99,102,241,.16),rgba(139,92,246,.08));' + | ||
| 'border-color:rgba(99,102,241,.55);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile-label {' + | ||
| 'font:700 12px/1 system-ui;color:#f1f5f9;margin-top:2px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile-desc {' + | ||
| 'font:500 10px/1.3 system-ui;color:#94a3b8;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-sub[data-show-mode]:not([data-active="1"]) { display:none; }' + | ||
| // Undo toast | ||
| '#bwSettingsPanel .bw-set-undo {' + | ||
| 'position:absolute;left:50%;bottom:12px;transform:translateX(-50%);' + | ||
| 'padding:8px 14px;border-radius:8px;background:rgba(15,23,42,.95);' + | ||
| 'border:1px solid rgba(99,102,241,.45);color:#cbd5e1;' + | ||
| 'font:600 12px/1 system-ui;display:flex;align-items:center;gap:8px;' + | ||
| 'box-shadow:0 8px 24px rgba(0,0,0,.5);opacity:0;pointer-events:none;' + | ||
| 'transition:opacity .2s ease;z-index:2;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-undo.show { opacity:1;pointer-events:auto; }' + | ||
| '#bwSettingsPanel .bw-set-undo button {' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);color:#fff;' + | ||
| 'border:0;border-radius:6px;padding:4px 10px;cursor:pointer;font:700 11px/1 system-ui;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-search-empty {' + | ||
| 'padding:20px 12px;color:#94a3b8;font:500 12px/1.4 system-ui;text-align:center;' + | ||
| '}' + | ||
| // Info popover | ||
| '#bwSetInfoPopover {' + | ||
| 'position:fixed;width:300px;padding:14px 16px;border-radius:12px;' + | ||
| 'background:linear-gradient(180deg,rgba(15,23,42,.99),rgba(2,6,23,.99));' + | ||
| 'border:1px solid rgba(99,102,241,.42);color:#e2e8f0;' + | ||
| 'box-shadow:0 16px 48px rgba(0,0,0,.55), 0 0 0 1px rgba(99,102,241,.12);' + | ||
| 'z-index:2147483647;font:500 12px/1.5 system-ui;' + | ||
| '}' + | ||
| '#bwSetInfoPopover .bw-info-close {' + | ||
| 'position:absolute;right:6px;top:4px;background:transparent;border:0;' + | ||
| 'color:#94a3b8;cursor:pointer;font-size:18px;padding:2px 6px;' + | ||
| '}' | ||
| ) + '</style>' + | ||
| '<div class="bw-set-head" id="bwSettingsDragHandle">' + | ||
| '<div class="bw-set-title">Settings</div>' + | ||
| '<input id="bwSetSearch" type="text" class="bw-set-search" placeholder="' + T('settings_search_placeholder','Search settings…').replace(/"/g, '"') + '" autocomplete="off"/>' + | ||
| '<button id="bwCloseSettings" class="bw-set-close" type="button" aria-label="' + T('hotkey_close','Close') + '">×</button>' + | ||
| '</div>' + | ||
| '<div class="bw-set-tabs" role="tablist">' + | ||
| '<button class="bw-set-tab active" data-tab="daily">' + T('settings_tab_daily','Daily') + ' <span class="bw-tab-count" data-count-for="daily">0</span></button>' + | ||
| '<button class="bw-set-tab" data-tab="playback">' + T('settings_tab_playback','Playback') + ' <span class="bw-tab-count" data-count-for="playback">0</span></button>' + | ||
| '<button class="bw-set-tab" data-tab="privacy">' + T('settings_tab_privacy','Privacy') + ' <span class="bw-tab-count" data-count-for="privacy">0</span></button>' + | ||
| '<button class="bw-set-tab" data-tab="advanced">' + T('settings_tab_advanced','Advanced') + ' <span class="bw-tab-count" data-count-for="advanced">0</span></button>' + | ||
| '</div>' + | ||
| '<div class="bw-set-body" id="bwSetBody">' + | ||
| '<div class="bw-set-pane" data-tab-pane="daily">' + dailyPaneHTML() + '</div>' + | ||
| '<div class="bw-set-pane" data-tab-pane="playback" hidden>' + playbackPaneHTML() + '</div>' + | ||
| '<div class="bw-set-pane" data-tab-pane="privacy" hidden>' + privacyPaneHTML() + '</div>' + | ||
| '<div class="bw-set-pane" data-tab-pane="advanced" hidden>' + advancedPaneHTML() + '</div>' + | ||
| '<div class="bw-set-search-empty" id="bwSetSearchEmpty" hidden>' + T('settings_search_no_results','No settings match your search.') + '</div>' + | ||
| '</div>' + | ||
| '<div class="bw-set-undo" id="bwSetUndo" role="status" aria-live="polite">' + | ||
| '<span id="bwSetUndoMsg">' + T('settings_saved_undo','Saved · Undo') + '</span>' + | ||
| '<button type="button" id="bwSetUndoBtn">' + T('settings_undo_btn','Undo') + '</button>' + | ||
| '</div>' | ||
| ); |
Comment on lines
+309
to
+655
| p.innerHTML = ( | ||
| '<style>' + ( | ||
| '#bwSettingsPanel .bw-set-head {' + | ||
| 'display:flex;align-items:center;gap:10px;padding:14px 18px 12px;' + | ||
| 'border-bottom:1px solid rgba(255,255,255,.08);cursor:move;flex-shrink:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-title {' + | ||
| 'font:700 16px/1 system-ui;' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);' + | ||
| '-webkit-background-clip:text;-webkit-text-fill-color:transparent;flex-shrink:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-search {' + | ||
| 'flex:1 1 auto;min-width:0;width:100%;' + | ||
| 'padding:6px 10px;border-radius:8px;border:1px solid rgba(255,255,255,.10);' + | ||
| 'background:rgba(2,6,23,.55);color:#e2e8f0;font:500 12px/1.2 system-ui;outline:none;' + | ||
| 'cursor:text;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-search:focus { border-color:rgba(99,102,241,.55); }' + | ||
| // Close button — ``margin-left:auto`` keeps it | ||
| // pinned to the right edge of the header even | ||
| // when the user resizes the panel wider than | ||
| // the search field naturally fills. | ||
| '#bwSettingsPanel .bw-set-close {' + | ||
| 'margin-left:auto;flex-shrink:0;' + | ||
| 'background:transparent;border:0;color:#94a3b8;cursor:pointer;' + | ||
| 'font-size:22px;line-height:1;padding:2px 6px;border-radius:6px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-close:hover { background:rgba(255,255,255,.08); color:#f8fafc; }' + | ||
| // Tabs | ||
| '#bwSettingsPanel .bw-set-tabs {' + | ||
| 'display:flex;gap:2px;padding:0 12px;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.18);' + | ||
| 'flex-shrink:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-tab {' + | ||
| 'flex:1;padding:10px 8px;background:transparent;border:0;border-bottom:2px solid transparent;' + | ||
| 'color:#94a3b8;font:600 12px/1.2 system-ui;cursor:pointer;display:flex;align-items:center;gap:6px;justify-content:center;' + | ||
| 'transition:color .15s ease,border-color .15s ease;letter-spacing:.2px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-tab:hover { color:#cbd5e1; }' + | ||
| '#bwSettingsPanel .bw-set-tab.active { color:#f8fafc; border-bottom-color:#6366f1; }' + | ||
| '#bwSettingsPanel .bw-set-tab .bw-tab-count {' + | ||
| 'display:inline-flex;align-items:center;justify-content:center;' + | ||
| 'min-width:18px;height:18px;padding:0 5px;border-radius:9px;' + | ||
| 'background:rgba(99,102,241,.22);color:#c7d2fe;font:600 10px/1 ui-monospace,Menlo;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-tab.active .bw-tab-count {' + | ||
| 'background:rgba(99,102,241,.42);color:#e0e7ff;' + | ||
| '}' + | ||
| // Body | ||
| '#bwSettingsPanel .bw-set-body {' + | ||
| 'padding:14px 18px 16px;overflow-y:auto;flex:1;min-height:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-pane[hidden] { display:none; }' + | ||
| '#bwSettingsPanel .bw-set-section-title {' + | ||
| 'font:700 11px/1 system-ui;letter-spacing:.6px;text-transform:uppercase;' + | ||
| 'color:#64748b;margin:8px 4px 8px;' + | ||
| '}' + | ||
| // Row — two columns: label (grows) + control (fixed). | ||
| // ``min-width:0`` on the label lets long labels | ||
| // truncate with an ellipsis rather than pushing the | ||
| // control off the right edge of the panel. | ||
| '#bwSettingsPanel .bw-set-row {' + | ||
| 'display:flex;align-items:center;gap:14px;padding:10px 6px;' + | ||
| 'border-radius:8px;transition:background .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-row[hidden] { display:none; }' + | ||
| '#bwSettingsPanel .bw-set-row:hover { background:rgba(255,255,255,.03); }' + | ||
| '#bwSettingsPanel .bw-set-row.bw-search-hit { background:rgba(99,102,241,.10); }' + | ||
| '#bwSettingsPanel .bw-set-row > label {' + | ||
| 'flex:1;min-width:0;display:flex;align-items:center;gap:8px;' + | ||
| 'cursor:pointer;color:#e2e8f0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-label {' + | ||
| 'font:600 13px/1.35 system-ui;' + | ||
| 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;' + | ||
| 'min-width:0;' + | ||
| '}' + | ||
| // Info icon button | ||
| '#bwSettingsPanel .bw-set-info {' + | ||
| 'width:18px;height:18px;border-radius:50%;padding:0;' + | ||
| 'background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);' + | ||
| 'color:#94a3b8;font:700 11px/1 ui-serif,Georgia;cursor:pointer;font-style:italic;' + | ||
| 'display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;' + | ||
| 'transition:background .15s ease,color .15s ease,border-color .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-info:hover {' + | ||
| 'background:rgba(99,102,241,.22);color:#c7d2fe;border-color:rgba(99,102,241,.45);' + | ||
| '}' + | ||
| // Restart badge | ||
| '#bwSettingsPanel .bw-set-restart {' + | ||
| 'display:inline-flex;align-items:center;justify-content:center;' + | ||
| 'width:18px;height:18px;border-radius:50%;' + | ||
| 'background:rgba(245,158,11,.16);color:#fbbf24;' + | ||
| 'font:600 11px/1 system-ui;cursor:help;flex-shrink:0;' + | ||
| '}' + | ||
| // Toggle switch (reused from old) | ||
| '#bwSettingsPanel .bw-set-toggle { position:relative;width:34px;height:20px;flex-shrink:0;cursor:pointer; }' + | ||
| '#bwSettingsPanel .bw-set-toggle input { opacity:0;width:0;height:0; }' + | ||
| '#bwSettingsPanel .bw-set-toggle .bw-set-slider {' + | ||
| 'position:absolute;inset:0;background:rgba(255,255,255,.14);' + | ||
| 'border-radius:99px;transition:background .2s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-toggle .bw-set-slider::before {' + | ||
| 'content:"";position:absolute;top:2px;left:2px;' + | ||
| 'width:16px;height:16px;border-radius:50%;background:#fff;' + | ||
| 'box-shadow:0 1px 3px rgba(0,0,0,.35);transition:left .2s cubic-bezier(.22,.61,.36,1);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-toggle input:checked + .bw-set-slider {' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-toggle input:checked + .bw-set-slider::before { left:16px; }' + | ||
| // Select — explicit dark theming so the native | ||
| // dropdown panel (the OS popup) stays dark too. | ||
| // ``color-scheme:dark`` switches the UA picker | ||
| // to a dark palette on Chromium/Edge; Firefox | ||
| // honours the styled ``option`` elements below. | ||
| // | ||
| // FIXED-WIDTH control column: ``flex:0 0 180px`` | ||
| // keeps every dropdown at the same predictable | ||
| // width regardless of the longest option, which | ||
| // means rows align cleanly and there's no | ||
| // horizontal scrollbar from a stretched select. | ||
| '#bwSettingsPanel select {' + | ||
| 'flex:0 0 180px;width:180px;box-sizing:border-box;' + | ||
| 'background-color:rgba(255,255,255,.06);' + | ||
| 'border:1px solid rgba(255,255,255,.14);' + | ||
| 'color:#e2e8f0;border-radius:8px;padding:6px 30px 6px 12px;' + | ||
| 'font:600 12px/1.2 system-ui;cursor:pointer;' + | ||
| 'color-scheme:dark;' + | ||
| '-webkit-appearance:none;-moz-appearance:none;appearance:none;' + | ||
| 'background-image:url("data:image/svg+xml;utf8,' + | ||
| '<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'10\' height=\'6\' viewBox=\'0 0 10 6\'>' + | ||
| '<path fill=\'%2394a3b8\' d=\'M0 0l5 6 5-6z\'/></svg>");' + | ||
| 'background-repeat:no-repeat;background-position:right 10px center;' + | ||
| 'transition:border-color .15s ease,background-color .15s ease;' + | ||
| 'text-overflow:ellipsis;' + | ||
| '}' + | ||
| '#bwSettingsPanel select:hover {' + | ||
| 'border-color:rgba(99,102,241,.45);background-color:rgba(255,255,255,.08);' + | ||
| '}' + | ||
| '#bwSettingsPanel select:focus {' + | ||
| 'border-color:rgba(99,102,241,.65);outline:none;' + | ||
| 'box-shadow:0 0 0 2px rgba(99,102,241,.22);' + | ||
| '}' + | ||
| // Firefox respects styled options. Chromium also | ||
| // picks these up when the OS supports dark UA. | ||
| '#bwSettingsPanel select option {' + | ||
| 'background-color:#0f172a;color:#e2e8f0;' + | ||
| 'padding:6px 10px;' + | ||
| '}' + | ||
| '#bwSettingsPanel select option:checked {' + | ||
| 'background:linear-gradient(0deg,rgba(99,102,241,.35),rgba(99,102,241,.35)),#0f172a;' + | ||
| 'color:#ffffff;' + | ||
| '}' + | ||
| '#bwSettingsPanel select option:disabled {' + | ||
| 'color:#475569;' + | ||
| '}' + | ||
| // Range slider — fixed 140px so it lines up with | ||
| // the dropdown column. The companion value | ||
| // (.bw-set-range-val) sits to the right. | ||
| '#bwSettingsPanel input[type=range] {' + | ||
| 'flex:0 0 140px;width:140px;height:4px;-webkit-appearance:none;appearance:none;' + | ||
| 'background:rgba(255,255,255,.10);border-radius:99px;outline:none;cursor:pointer;' + | ||
| '}' + | ||
| '#bwSettingsPanel input[type=range]::-webkit-slider-thumb {' + | ||
| '-webkit-appearance:none;width:14px;height:14px;border-radius:50%;' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);cursor:pointer;border:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel input[type=range]::-moz-range-thumb {' + | ||
| 'width:14px;height:14px;border-radius:50%;' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);cursor:pointer;border:0;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-range-val {' + | ||
| 'flex:0 0 38px;font:600 12px/1 system-ui;color:#cbd5e1;' + | ||
| 'font-variant-numeric:tabular-nums;text-align:right;' + | ||
| '}' + | ||
| // Text input — same fixed 180px column as the | ||
| // selects so the right edge of every control | ||
| // lines up across all rows. | ||
| '#bwSettingsPanel input[type=text] {' + | ||
| 'flex:0 0 180px;width:180px;box-sizing:border-box;' + | ||
| 'padding:6px 10px;border-radius:8px;' + | ||
| 'border:1px solid rgba(255,255,255,.12);background:rgba(2,6,23,.45);' + | ||
| 'color:#e2e8f0;font:500 12px/1.3 system-ui;min-width:0;' + | ||
| 'transition:border-color .15s ease,background-color .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel input[type=text]:hover {' + | ||
| 'border-color:rgba(99,102,241,.4);background-color:rgba(2,6,23,.6);' + | ||
| '}' + | ||
| '#bwSettingsPanel input[type=text]:focus {' + | ||
| 'border-color:rgba(99,102,241,.65);outline:none;' + | ||
| 'box-shadow:0 0 0 2px rgba(99,102,241,.22);' + | ||
| '}' + | ||
| // Privacy score | ||
| '#bwSettingsPanel .bw-priv-score {' + | ||
| 'padding:14px 14px;border-radius:12px;cursor:pointer;' + | ||
| 'background:linear-gradient(135deg,rgba(99,102,241,.10),rgba(139,92,246,.06));' + | ||
| 'border:1px solid rgba(99,102,241,.22);margin-bottom:14px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score:hover { border-color:rgba(99,102,241,.40); }' + | ||
| '#bwSettingsPanel .bw-priv-score-row {' + | ||
| 'display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score-heading { font:700 13px/1 system-ui;color:#cbd5e1; }' + | ||
| '#bwSettingsPanel .bw-priv-score-num {' + | ||
| 'font:800 28px/1 system-ui;font-variant-numeric:tabular-nums;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score-bar {' + | ||
| 'width:100%;height:6px;border-radius:3px;background:rgba(2,6,23,.5);overflow:hidden;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score-fill {' + | ||
| 'height:100%;transition:width .35s ease,background .25s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-score-sub {' + | ||
| 'font:500 11px/1.4 system-ui;color:#94a3b8;margin-top:6px;' + | ||
| '}' + | ||
| // Presets | ||
| '#bwSettingsPanel .bw-priv-preset-block { margin-bottom:14px; }' + | ||
| '#bwSettingsPanel .bw-priv-presets {' + | ||
| 'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset {' + | ||
| 'display:flex;flex-direction:column;align-items:flex-start;gap:4px;' + | ||
| 'padding:10px 10px;border-radius:10px;cursor:pointer;text-align:left;' + | ||
| 'background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);' + | ||
| 'color:#cbd5e1;transition:all .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset:hover {' + | ||
| 'background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.18);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset.active {' + | ||
| 'background:linear-gradient(135deg,rgba(99,102,241,.18),rgba(139,92,246,.10));' + | ||
| 'border-color:rgba(99,102,241,.55);color:#e0e7ff;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset-name { font:700 12px/1 system-ui; }' + | ||
| '#bwSettingsPanel .bw-priv-preset-desc {' + | ||
| 'font:500 10px/1.3 system-ui;color:#94a3b8;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-preset.active .bw-priv-preset-desc { color:#c7d2fe; }' + | ||
| '#bwSettingsPanel .bw-priv-custom-pill {' + | ||
| 'display:inline-block;margin-top:8px;padding:2px 8px;border-radius:8px;' + | ||
| 'background:rgba(245,158,11,.16);border:1px solid rgba(245,158,11,.30);' + | ||
| 'color:#fbbf24;font:600 11px/1.4 system-ui;' + | ||
| '}' + | ||
| // Show advanced toggle | ||
| '#bwSettingsPanel .bw-priv-advanced-toggle {' + | ||
| 'display:flex;align-items:center;gap:8px;width:100%;' + | ||
| 'padding:9px 12px;border-radius:10px;cursor:pointer;' + | ||
| 'background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);' + | ||
| 'color:#94a3b8;font:600 12px/1 system-ui;text-align:left;margin-bottom:10px;' + | ||
| 'transition:background .15s ease,color .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-advanced-toggle:hover {' + | ||
| 'background:rgba(255,255,255,.06);color:#cbd5e1;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-priv-advanced-toggle.open { color:#cbd5e1; }' + | ||
| '#bwSettingsPanel .bw-priv-advanced-chev {' + | ||
| 'font-size:10px;color:#94a3b8;' + | ||
| '}' + | ||
| // Forensik card | ||
| '#bwSettingsPanel .bw-forensik-card {' + | ||
| 'padding:12px;border-radius:12px;' + | ||
| 'background:rgba(2,6,23,.4);border:1px solid rgba(255,255,255,.08);' + | ||
| 'margin-bottom:10px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-card-head {' + | ||
| 'display:flex;align-items:center;gap:6px;margin-bottom:2px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-card-sub {' + | ||
| 'font:500 11px/1.4 system-ui;color:#64748b;margin-bottom:10px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tiles {' + | ||
| 'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:8px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile {' + | ||
| 'display:flex;flex-direction:column;align-items:center;gap:4px;' + | ||
| 'padding:10px 6px;border-radius:10px;cursor:pointer;text-align:center;' + | ||
| 'background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08);' + | ||
| 'color:#cbd5e1;transition:all .15s ease;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile:hover {' + | ||
| 'background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.20);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile.active {' + | ||
| 'background:linear-gradient(135deg,rgba(99,102,241,.16),rgba(139,92,246,.08));' + | ||
| 'border-color:rgba(99,102,241,.55);' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile-label {' + | ||
| 'font:700 12px/1 system-ui;color:#f1f5f9;margin-top:2px;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-tile-desc {' + | ||
| 'font:500 10px/1.3 system-ui;color:#94a3b8;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-forensik-sub[data-show-mode]:not([data-active="1"]) { display:none; }' + | ||
| // Undo toast | ||
| '#bwSettingsPanel .bw-set-undo {' + | ||
| 'position:absolute;left:50%;bottom:12px;transform:translateX(-50%);' + | ||
| 'padding:8px 14px;border-radius:8px;background:rgba(15,23,42,.95);' + | ||
| 'border:1px solid rgba(99,102,241,.45);color:#cbd5e1;' + | ||
| 'font:600 12px/1 system-ui;display:flex;align-items:center;gap:8px;' + | ||
| 'box-shadow:0 8px 24px rgba(0,0,0,.5);opacity:0;pointer-events:none;' + | ||
| 'transition:opacity .2s ease;z-index:2;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-undo.show { opacity:1;pointer-events:auto; }' + | ||
| '#bwSettingsPanel .bw-set-undo button {' + | ||
| 'background:linear-gradient(135deg,#3b82f6,#8b5cf6);color:#fff;' + | ||
| 'border:0;border-radius:6px;padding:4px 10px;cursor:pointer;font:700 11px/1 system-ui;' + | ||
| '}' + | ||
| '#bwSettingsPanel .bw-set-search-empty {' + | ||
| 'padding:20px 12px;color:#94a3b8;font:500 12px/1.4 system-ui;text-align:center;' + | ||
| '}' + | ||
| // Info popover | ||
| '#bwSetInfoPopover {' + | ||
| 'position:fixed;width:300px;padding:14px 16px;border-radius:12px;' + | ||
| 'background:linear-gradient(180deg,rgba(15,23,42,.99),rgba(2,6,23,.99));' + | ||
| 'border:1px solid rgba(99,102,241,.42);color:#e2e8f0;' + | ||
| 'box-shadow:0 16px 48px rgba(0,0,0,.55), 0 0 0 1px rgba(99,102,241,.12);' + | ||
| 'z-index:2147483647;font:500 12px/1.5 system-ui;' + | ||
| '}' + | ||
| '#bwSetInfoPopover .bw-info-close {' + | ||
| 'position:absolute;right:6px;top:4px;background:transparent;border:0;' + | ||
| 'color:#94a3b8;cursor:pointer;font-size:18px;padding:2px 6px;' + | ||
| '}' | ||
| ) + '</style>' + | ||
| '<div class="bw-set-head" id="bwSettingsDragHandle">' + | ||
| '<div class="bw-set-title">Settings</div>' + | ||
| '<input id="bwSetSearch" type="text" class="bw-set-search" placeholder="' + T('settings_search_placeholder','Search settings…').replace(/"/g, '"') + '" autocomplete="off"/>' + | ||
| '<button id="bwCloseSettings" class="bw-set-close" type="button" aria-label="' + T('hotkey_close','Close') + '">×</button>' + | ||
| '</div>' + | ||
| '<div class="bw-set-tabs" role="tablist">' + | ||
| '<button class="bw-set-tab active" data-tab="daily">' + T('settings_tab_daily','Daily') + ' <span class="bw-tab-count" data-count-for="daily">0</span></button>' + | ||
| '<button class="bw-set-tab" data-tab="playback">' + T('settings_tab_playback','Playback') + ' <span class="bw-tab-count" data-count-for="playback">0</span></button>' + | ||
| '<button class="bw-set-tab" data-tab="privacy">' + T('settings_tab_privacy','Privacy') + ' <span class="bw-tab-count" data-count-for="privacy">0</span></button>' + | ||
| '<button class="bw-set-tab" data-tab="advanced">' + T('settings_tab_advanced','Advanced') + ' <span class="bw-tab-count" data-count-for="advanced">0</span></button>' + | ||
| '</div>' + | ||
| '<div class="bw-set-body" id="bwSetBody">' + | ||
| '<div class="bw-set-pane" data-tab-pane="daily">' + dailyPaneHTML() + '</div>' + | ||
| '<div class="bw-set-pane" data-tab-pane="playback" hidden>' + playbackPaneHTML() + '</div>' + | ||
| '<div class="bw-set-pane" data-tab-pane="privacy" hidden>' + privacyPaneHTML() + '</div>' + | ||
| '<div class="bw-set-pane" data-tab-pane="advanced" hidden>' + advancedPaneHTML() + '</div>' + | ||
| '<div class="bw-set-search-empty" id="bwSetSearchEmpty" hidden>' + T('settings_search_no_results','No settings match your search.') + '</div>' + | ||
| '</div>' + | ||
| '<div class="bw-set-undo" id="bwSetUndo" role="status" aria-live="polite">' + | ||
| '<span id="bwSetUndoMsg">' + T('settings_saved_undo','Saved · Undo') + '</span>' + | ||
| '<button type="button" id="bwSetUndoBtn">' + T('settings_undo_btn','Undo') + '</button>' + | ||
| '</div>' | ||
| ); |
- bw/assets/sidebar_polish.js + continue_watching.js: fix 11 more
template-literal ${...} syntax errors inside single-quoted strings
(privacy modal, network monitor, new-episode toast, stats strip)
- bw/assets/sidebar/06_settings_panel.js + sidebar_inject.js: add
escapeHtml() and route _priv.forensikStashDir, _s.firefox_path
text-input values through it (CodeQL js/xss-through-dom alerts
#30/#36)
- bw/assets/continue_watching.js: move "Diese Woche / Streak / Heute"
+ empty-state strings to i18n keys; +6 EN/DE pairs
- .github/workflows/ci.yml lint: mirror style.yml rule set
(--select=E,F,W,B,C4,UP --ignore=E501,E701,...) so the two jobs
agree; format check downgraded to warning
- .github/workflows/ci.yml typecheck: add --follow-imports=silent
so strict mypy only checks the 6 declared modules, not the
legacy tree it transitively imports
- .github/workflows/security.yml privacy-invariant: exclude tests/
from the useTorProxy grep — test_settings.py uses the literal
to assert the legacy key is stripped on validate()
- bw/shutdown.py: typing.Callable -> collections.abc.Callable (UP035)
- tests/test_js_assets.py: bump asset baselines (374k/203k/16.6k)
to reflect the post-fix sizes; ±5% drift window unchanged
mypy strict on Linux can't resolve ctypes.windll (Windows-only). Add # type: ignore[attr-defined,unused-ignore] to the 3 windll calls in is_pid_alive(). The unused-ignore variant keeps Windows mypy happy too — there windll IS defined so the ignore would otherwise be flagged as unused.
- Add window.__bwEscapeHtml once at sidebar init so every click handler can reach it - Escape ctx.title and series in the filmpalast + series-row right-click context menus before they go into template literals (openFpContextMenu, series ctx menu) - Escape seriesName in the per-series Skip-Times panel heading - Guard the filmpalast row data-url click handler with a ^https?:// regex so a manipulated localStorage entry can't fire javascript: / data: via location.href - sidebar_polish.js: also escape backslashes when building CSS attribute selectors from a series slug (was only escaping " ; a slug with a literal \ would break out of the selector)
window.X = function() ... aliasing isn't followed by CodeQL's taint tracker, so the 4 sinks (filmpalast ctx, series ctx, skip-times heading, settings panel via row helper) kept showing up as XSS even after the previous fix. Switch to an IIFE-scoped const __bwEscapeHtml = (s) => ... . Same behaviour, but a local const the taint analyzer can follow through every call site.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
bingewatcher.pyintobw/{constants,settings_types,shutdown,session,episode_logic,player_loop}.py. The 10.5k-line god-file shrinks to ~8.8k.bw/assets/*.jsand the main bundle is split into 7 chunk files underbw/assets/sidebar/, assembled bybw/ui/{header,series_list,cast_modal,tools_menu,settings_panel}.py.bw/ui_sidebar.pybecomes a re-export shim, byte-identity verified by tests.tests/with pytest wrappers, ruff + mypy config, CI matrix, slow-marker for live-network selftests. 159 tests pass (1 known network deselect).${...}in single-quoted strings instead of backticks (privacy popover, cast modal, bookmarks label, audio hint).node --checkclean.<option>background.window.__bwI18naccess).Notable behaviour-preservation guarantees
bingewatcher.play_episodes_loop is bw.player_loop.play_episodes_loop→ same function object, just relocatedui_sidebar.SIDEBAR_INJECT_JSbyte-identical to the on-diskbw/assets/sidebar_inject.js, asserted bytests/test_imports.py::test_sidebar_bundle_byte_identityi18n.t()at call time using the user's selected language, with English fallback if the catalog can't loadTest plan
pytest tests/ -m "not slow"— 137 fast testspytest tests/ --deselect "tests/test_selftests.py::test_selftest_phase[selftest_screen_pipeline]"— 159 testsnode --check bw/assets/sidebar_inject.jsruff check bw/ui/ bw/player_loop.py bw/ui_sidebar.py tests/