fix(subtitle-sidebar): address latest CodeRabbit review

This commit is contained in:
2026-03-21 16:28:30 -07:00
parent de111dbf8d
commit b049cf388d
6 changed files with 1550 additions and 37 deletions

View File

@@ -3,6 +3,7 @@ import { ResolveContext } from './context';
import {
asBoolean,
asColor,
asCssColor,
asFrequencyBandedColors,
asNumber,
asString,
@@ -439,6 +440,19 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
);
}
const autoOpen = asBoolean((src.subtitleSidebar as { autoOpen?: unknown }).autoOpen);
if (autoOpen !== undefined) {
resolved.subtitleSidebar.autoOpen = autoOpen;
} else if ((src.subtitleSidebar as { autoOpen?: unknown }).autoOpen !== undefined) {
resolved.subtitleSidebar.autoOpen = fallback.autoOpen;
warn(
'subtitleSidebar.autoOpen',
(src.subtitleSidebar as { autoOpen?: unknown }).autoOpen,
resolved.subtitleSidebar.autoOpen,
'Expected boolean.',
);
}
const layout = asString((src.subtitleSidebar as { layout?: unknown }).layout);
if (layout === 'overlay' || layout === 'embedded') {
resolved.subtitleSidebar.layout = layout;
@@ -507,7 +521,7 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
}
const opacity = asNumber((src.subtitleSidebar as { opacity?: unknown }).opacity);
if (opacity !== undefined && opacity > 0 && opacity <= 1) {
if (opacity !== undefined && opacity >= 0 && opacity <= 1) {
resolved.subtitleSidebar.opacity = opacity;
} else if ((src.subtitleSidebar as { opacity?: unknown }).opacity !== undefined) {
resolved.subtitleSidebar.opacity = fallback.opacity;
@@ -541,16 +555,16 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
'hoverLineBackgroundColor',
] as const;
for (const field of cssColorFields) {
const value = asString((src.subtitleSidebar as Record<string, unknown>)[field]);
if (value !== undefined && value.trim().length > 0) {
resolved.subtitleSidebar[field] = value.trim();
const value = asCssColor((src.subtitleSidebar as Record<string, unknown>)[field]);
if (value !== undefined) {
resolved.subtitleSidebar[field] = value;
} else if ((src.subtitleSidebar as Record<string, unknown>)[field] !== undefined) {
resolved.subtitleSidebar[field] = fallback[field];
warn(
`subtitleSidebar.${field}`,
(src.subtitleSidebar as Record<string, unknown>)[field],
resolved.subtitleSidebar[field],
'Expected string.',
'Expected valid CSS color.',
);
}
}

View File

@@ -7,6 +7,7 @@ test('subtitleSidebar resolves valid values and preserves dedicated defaults', (
const { context } = createResolveContext({
subtitleSidebar: {
enabled: true,
autoOpen: true,
layout: 'embedded',
toggleKey: 'KeyB',
pauseVideoOnHover: true,
@@ -27,6 +28,7 @@ test('subtitleSidebar resolves valid values and preserves dedicated defaults', (
applySubtitleDomainConfig(context);
assert.equal(context.resolved.subtitleSidebar.enabled, true);
assert.equal(context.resolved.subtitleSidebar.autoOpen, true);
assert.equal(context.resolved.subtitleSidebar.layout, 'embedded');
assert.equal(context.resolved.subtitleSidebar.toggleKey, 'KeyB');
assert.equal(context.resolved.subtitleSidebar.pauseVideoOnHover, true);
@@ -37,30 +39,55 @@ test('subtitleSidebar resolves valid values and preserves dedicated defaults', (
assert.equal(context.resolved.subtitleSidebar.fontSize, 17);
});
test('subtitleSidebar accepts zero opacity', () => {
const { context, warnings } = createResolveContext({
subtitleSidebar: {
opacity: 0,
},
});
applySubtitleDomainConfig(context);
assert.equal(context.resolved.subtitleSidebar.opacity, 0);
assert.equal(warnings.some((warning) => warning.path === 'subtitleSidebar.opacity'), false);
});
test('subtitleSidebar falls back and warns on invalid values', () => {
const { context, warnings } = createResolveContext({
subtitleSidebar: {
enabled: 'yes' as never,
autoOpen: 'yes' as never,
layout: 'floating' as never,
maxWidth: -1,
opacity: 5,
fontSize: 0,
textColor: 'blue',
backgroundColor: 'not-a-color',
},
});
applySubtitleDomainConfig(context);
assert.equal(context.resolved.subtitleSidebar.enabled, false);
assert.equal(context.resolved.subtitleSidebar.autoOpen, false);
assert.equal(context.resolved.subtitleSidebar.layout, 'overlay');
assert.equal(context.resolved.subtitleSidebar.maxWidth, 420);
assert.equal(context.resolved.subtitleSidebar.opacity, 0.95);
assert.equal(context.resolved.subtitleSidebar.fontSize, 16);
assert.equal(context.resolved.subtitleSidebar.textColor, '#cad3f5');
assert.equal(context.resolved.subtitleSidebar.backgroundColor, 'rgba(73, 77, 100, 0.9)');
assert.ok(warnings.some((warning) => warning.path === 'subtitleSidebar.enabled'));
assert.ok(warnings.some((warning) => warning.path === 'subtitleSidebar.autoOpen'));
assert.ok(warnings.some((warning) => warning.path === 'subtitleSidebar.layout'));
assert.ok(warnings.some((warning) => warning.path === 'subtitleSidebar.maxWidth'));
assert.ok(warnings.some((warning) => warning.path === 'subtitleSidebar.opacity'));
assert.ok(warnings.some((warning) => warning.path === 'subtitleSidebar.fontSize'));
assert.ok(warnings.some((warning) => warning.path === 'subtitleSidebar.textColor'));
assert.ok(
warnings.some(
(warning) =>
warning.path === 'subtitleSidebar.backgroundColor' &&
warning.message === 'Expected valid CSS color.',
),
);
});

View File

@@ -1,6 +1,7 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { SubtitleSidebarConfig } from '../../types';
import { createMouseHandlers } from './mouse.js';
import { YOMITAN_POPUP_HIDDEN_EVENT, YOMITAN_POPUP_SHOWN_EVENT } from '../yomitan-popup.js';
@@ -39,6 +40,7 @@ function createMouseTestContext() {
const overlayClassList = createClassList();
const subtitleRootClassList = createClassList();
const subtitleContainerClassList = createClassList();
const secondarySubContainerClassList = createClassList();
const ctx = {
dom: {
@@ -54,6 +56,7 @@ function createMouseTestContext() {
addEventListener: () => {},
},
secondarySubContainer: {
classList: secondarySubContainerClassList,
addEventListener: () => {},
},
},
@@ -63,6 +66,9 @@ function createMouseTestContext() {
},
state: {
isOverSubtitle: false,
isOverSubtitleSidebar: false,
subtitleSidebarModalOpen: false,
subtitleSidebarConfig: null as SubtitleSidebarConfig | null,
isDragging: false,
dragStartY: 0,
startYPercent: 0,
@@ -72,7 +78,7 @@ function createMouseTestContext() {
return ctx;
}
test('auto-pause on subtitle hover pauses on enter and resumes on leave when enabled', async () => {
test('secondary hover pauses on enter, reveals secondary subtitle, and resumes on leave when enabled', async () => {
const ctx = createMouseTestContext();
const mpvCommands: Array<(string | number)[]> = [];
@@ -92,8 +98,10 @@ test('auto-pause on subtitle hover pauses on enter and resumes on leave when ena
},
});
await handlers.handleMouseEnter();
await handlers.handleMouseLeave();
await handlers.handleSecondaryMouseEnter();
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), true);
await handlers.handleSecondaryMouseLeave();
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), false);
assert.deepEqual(mpvCommands, [
['set_property', 'pause', 'yes'],
@@ -101,6 +109,38 @@ test('auto-pause on subtitle hover pauses on enter and resumes on leave when ena
]);
});
test('moving between primary and secondary subtitle containers keeps the hover pause active', async () => {
const ctx = createMouseTestContext();
const mpvCommands: Array<(string | number)[]> = [];
const handlers = createMouseHandlers(ctx as never, {
modalStateReader: {
isAnySettingsModalOpen: () => false,
isAnyModalOpen: () => false,
},
applyYPercent: () => {},
getCurrentYPercent: () => 10,
persistSubtitlePositionPatch: () => {},
getSubtitleHoverAutoPauseEnabled: () => true,
getYomitanPopupAutoPauseEnabled: () => false,
getPlaybackPaused: async () => false,
sendMpvCommand: (command) => {
mpvCommands.push(command);
},
});
await handlers.handleSecondaryMouseEnter();
await handlers.handleSecondaryMouseLeave({
relatedTarget: ctx.dom.subtitleContainer,
} as unknown as MouseEvent);
await handlers.handlePrimaryMouseEnter({
relatedTarget: ctx.dom.secondarySubContainer,
} as unknown as MouseEvent);
assert.equal(ctx.state.isOverSubtitle, true);
assert.deepEqual(mpvCommands, [['set_property', 'pause', 'yes']]);
});
test('auto-pause on subtitle hover skips when playback is already paused', async () => {
const ctx = createMouseTestContext();
const mpvCommands: Array<(string | number)[]> = [];
@@ -127,6 +167,36 @@ test('auto-pause on subtitle hover skips when playback is already paused', async
assert.deepEqual(mpvCommands, []);
});
test('primary hover pauses on enter without revealing secondary subtitle', async () => {
const ctx = createMouseTestContext();
const mpvCommands: Array<(string | number)[]> = [];
const handlers = createMouseHandlers(ctx as never, {
modalStateReader: {
isAnySettingsModalOpen: () => false,
isAnyModalOpen: () => false,
},
applyYPercent: () => {},
getCurrentYPercent: () => 10,
persistSubtitlePositionPatch: () => {},
getSubtitleHoverAutoPauseEnabled: () => true,
getYomitanPopupAutoPauseEnabled: () => false,
getPlaybackPaused: async () => false,
sendMpvCommand: (command) => {
mpvCommands.push(command);
},
});
await handlers.handlePrimaryMouseEnter();
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), false);
await handlers.handlePrimaryMouseLeave();
assert.deepEqual(mpvCommands, [
['set_property', 'pause', 'yes'],
['set_property', 'pause', 'no'],
]);
});
test('auto-pause on subtitle hover is skipped when disabled in config', async () => {
const ctx = createMouseTestContext();
const mpvCommands: Array<(string | number)[]> = [];
@@ -153,6 +223,67 @@ test('auto-pause on subtitle hover is skipped when disabled in config', async ()
assert.deepEqual(mpvCommands, []);
});
test('subtitle leave restores passthrough while embedded sidebar is open but not hovered', async () => {
const ctx = createMouseTestContext();
const ignoreMouseCalls: Array<[boolean, { forward?: boolean } | undefined]> = [];
const previousWindow = (globalThis as { window?: unknown }).window;
ctx.platform.shouldToggleMouseIgnore = true;
ctx.state.isOverSubtitle = true;
ctx.state.subtitleSidebarModalOpen = true;
ctx.state.subtitleSidebarConfig = {
enabled: true,
autoOpen: false,
layout: 'embedded',
toggleKey: 'Backslash',
pauseVideoOnHover: false,
autoScroll: true,
maxWidth: 360,
opacity: 0.92,
backgroundColor: 'rgba(54, 58, 79, 0.88)',
textColor: '#cad3f5',
fontFamily: '"Iosevka Aile", sans-serif',
fontSize: 17,
timestampColor: '#a5adcb',
activeLineColor: '#f5bde6',
activeLineBackgroundColor: 'rgba(138, 173, 244, 0.22)',
hoverLineBackgroundColor: 'rgba(54, 58, 79, 0.84)',
};
Object.defineProperty(globalThis, 'window', {
configurable: true,
value: {
electronAPI: {
setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => {
ignoreMouseCalls.push([ignore, options]);
},
},
},
});
try {
const handlers = createMouseHandlers(ctx as never, {
modalStateReader: {
isAnySettingsModalOpen: () => false,
isAnyModalOpen: () => true,
},
applyYPercent: () => {},
getCurrentYPercent: () => 10,
persistSubtitlePositionPatch: () => {},
getSubtitleHoverAutoPauseEnabled: () => false,
getYomitanPopupAutoPauseEnabled: () => false,
getPlaybackPaused: async () => false,
sendMpvCommand: () => {},
});
await handlers.handlePrimaryMouseLeave();
assert.deepEqual(ignoreMouseCalls.at(-1), [true, { forward: true }]);
} finally {
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
}
});
test('pending hover pause check is ignored when mouse leaves before pause state resolves', async () => {
const ctx = createMouseTestContext();
const mpvCommands: Array<(string | number)[]> = [];

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ import type {
SubtitleSidebarSnapshot,
} from '../../types';
import type { ModalStateReader, RendererContext } from '../context';
import { syncOverlayMouseIgnoreState } from '../overlay-mouse-ignore.js';
const MANUAL_SCROLL_HOLD_MS = 1500;
const ACTIVE_CUE_LOOKAHEAD_SEC = 0.18;
@@ -11,7 +12,6 @@ const CLICK_SEEK_OFFSET_SEC = 0.08;
const SNAPSHOT_POLL_INTERVAL_MS = 80;
const EMBEDDED_SIDEBAR_MIN_WIDTH_PX = 240;
const EMBEDDED_SIDEBAR_MAX_RATIO = 0.45;
function subtitleCueListsEqual(a: SubtitleCue[], b: SubtitleCue[]): boolean {
if (a.length !== b.length) {
return false;
@@ -107,7 +107,10 @@ export function findActiveSubtitleCueIndex(
if (forwardMatches.length > 0) {
return forwardMatches[0]!;
}
return preferredCueIndex;
if (matchingIndices.includes(preferredCueIndex)) {
return preferredCueIndex;
}
return matchingIndices[matchingIndices.length - 1] ?? -1;
}
let nearestIndex = matchingIndices[0]!;
@@ -131,8 +134,13 @@ export function createSubtitleSidebarModal(
modalStateReader: Pick<ModalStateReader, 'isAnyModalOpen'>;
},
) {
let snapshotPollInterval: ReturnType<typeof setInterval> | null = null;
let snapshotPollInterval: ReturnType<typeof setTimeout> | null = null;
let lastAppliedVideoMarginRatio: number | null = null;
let subtitleSidebarHoverRequestId = 0;
function restoreEmbeddedSidebarPassthrough(): void {
syncOverlayMouseIgnoreState(ctx);
}
function setStatus(message: string): void {
ctx.dom.subtitleSidebarStatus.textContent = message;
@@ -190,6 +198,26 @@ export function createSubtitleSidebarModal(
'video-margin-ratio-right',
Number(ratio.toFixed(4)),
]);
window.electronAPI.sendMpvCommand([
'set_property',
'osd-align-x',
'left',
]);
window.electronAPI.sendMpvCommand([
'set_property',
'osd-align-y',
'top',
]);
window.electronAPI.sendMpvCommand([
'set_property',
'user-data/osc/margins',
JSON.stringify({
l: 0,
r: Number(ratio.toFixed(4)),
t: 0,
b: 0,
}),
]);
if (ratio === 0) {
window.electronAPI.sendMpvCommand(['set_property', 'video-pan-x', 0]);
}
@@ -228,6 +256,22 @@ export function createSubtitleSidebarModal(
]);
}
function getCueRowLabel(cue: SubtitleCue): string {
return `Jump to subtitle at ${formatCueTimestamp(cue.startTime)}`;
}
function resumeSubtitleSidebarHoverPause(): void {
subtitleSidebarHoverRequestId += 1;
if (!ctx.state.subtitleSidebarPausedByHover) {
restoreEmbeddedSidebarPassthrough();
return;
}
ctx.state.subtitleSidebarPausedByHover = false;
window.electronAPI.sendMpvCommand(['set_property', 'pause', 'no']);
restoreEmbeddedSidebarPassthrough();
}
function maybeAutoScrollActiveCue(
previousActiveCueIndex: number,
behavior: ScrollBehavior = 'smooth',
@@ -250,8 +294,14 @@ export function createSubtitleSidebarModal(
const targetScrollTop =
active.offsetTop - (list.clientHeight - active.clientHeight) / 2;
const nextScrollTop = Math.max(0, targetScrollTop);
if (previousActiveCueIndex < 0) {
list.scrollTop = nextScrollTop;
return;
}
list.scrollTo({
top: Math.max(0, targetScrollTop),
top: nextScrollTop,
behavior,
});
}
@@ -263,6 +313,16 @@ export function createSubtitleSidebarModal(
row.className = 'subtitle-sidebar-item';
row.classList.toggle('active', index === ctx.state.subtitleSidebarActiveCueIndex);
row.dataset.index = String(index);
row.tabIndex = 0;
row.setAttribute('role', 'button');
row.setAttribute('aria-label', getCueRowLabel(cue));
row.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key !== 'Enter' && event.key !== ' ') {
return;
}
event.preventDefault();
seekToCue(cue);
});
const timestamp = document.createElement('div');
timestamp.className = 'subtitle-sidebar-timestamp';
@@ -315,6 +375,23 @@ export function createSubtitleSidebarModal(
async function refreshSnapshot(): Promise<SubtitleSidebarSnapshot> {
const snapshot = await window.electronAPI.getSubtitleSidebarSnapshot();
applyConfig(snapshot);
if (!snapshot.config.enabled) {
resumeSubtitleSidebarHoverPause();
ctx.state.subtitleSidebarCues = [];
ctx.state.subtitleSidebarModalOpen = false;
ctx.dom.subtitleSidebarModal.classList.add('hidden');
ctx.dom.subtitleSidebarModal.setAttribute('aria-hidden', 'true');
stopSnapshotPolling();
updateActiveCue(null, snapshot.currentTimeSec ?? null);
setStatus('Subtitle sidebar disabled in config.');
syncEmbeddedSidebarLayout();
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
ctx.dom.overlay.classList.remove('interactive');
}
restoreEmbeddedSidebarPassthrough();
return snapshot;
}
const cuesChanged = !subtitleCueListsEqual(ctx.state.subtitleSidebarCues, snapshot.cues);
if (cuesChanged) {
ctx.state.subtitleSidebarCues = snapshot.cues;
@@ -328,34 +405,50 @@ export function createSubtitleSidebarModal(
}
function startSnapshotPolling(): void {
if (snapshotPollInterval) {
clearInterval(snapshotPollInterval);
}
snapshotPollInterval = setInterval(() => {
void refreshSnapshot();
}, SNAPSHOT_POLL_INTERVAL_MS);
stopSnapshotPolling();
const pollOnce = async (): Promise<void> => {
try {
await refreshSnapshot();
} catch {
// Keep polling; a transient IPC failure should not stop updates.
}
if (!ctx.state.subtitleSidebarModalOpen) {
snapshotPollInterval = null;
return;
}
snapshotPollInterval = setTimeout(pollOnce, SNAPSHOT_POLL_INTERVAL_MS);
};
snapshotPollInterval = setTimeout(pollOnce, SNAPSHOT_POLL_INTERVAL_MS);
}
function stopSnapshotPolling(): void {
if (!snapshotPollInterval) {
return;
}
clearInterval(snapshotPollInterval);
clearTimeout(snapshotPollInterval);
snapshotPollInterval = null;
}
async function openSubtitleSidebarModal(): Promise<void> {
const snapshot = await refreshSnapshot();
ctx.dom.subtitleSidebarList.innerHTML = '';
if (!snapshot.config.enabled) {
setStatus('Subtitle sidebar disabled in config.');
} else if (snapshot.cues.length === 0) {
return;
}
ctx.dom.subtitleSidebarList.innerHTML = '';
if (snapshot.cues.length === 0) {
setStatus('No parsed subtitle cues available.');
} else {
setStatus(`${snapshot.cues.length} parsed subtitle lines`);
}
ctx.state.subtitleSidebarModalOpen = true;
ctx.state.isOverSubtitleSidebar = false;
ctx.dom.subtitleSidebarModal.classList.remove('hidden');
ctx.dom.subtitleSidebarModal.setAttribute('aria-hidden', 'false');
renderCueList();
@@ -363,12 +456,23 @@ export function createSubtitleSidebarModal(
maybeAutoScrollActiveCue(-1, 'auto', true);
startSnapshotPolling();
syncEmbeddedSidebarLayout();
restoreEmbeddedSidebarPassthrough();
}
async function autoOpenSubtitleSidebarOnStartup(): Promise<void> {
const snapshot = await refreshSnapshot();
if (!snapshot.config.enabled || !snapshot.config.autoOpen || ctx.state.subtitleSidebarModalOpen) {
return;
}
await openSubtitleSidebarModal();
}
function closeSubtitleSidebarModal(): void {
if (!ctx.state.subtitleSidebarModalOpen) {
return;
}
resumeSubtitleSidebarHoverPause();
ctx.state.isOverSubtitleSidebar = false;
ctx.state.subtitleSidebarModalOpen = false;
ctx.dom.subtitleSidebarModal.classList.add('hidden');
ctx.dom.subtitleSidebarModal.setAttribute('aria-hidden', 'true');
@@ -377,6 +481,7 @@ export function createSubtitleSidebarModal(
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
ctx.dom.overlay.classList.remove('interactive');
}
restoreEmbeddedSidebarPassthrough();
}
async function toggleSubtitleSidebarModal(): Promise<void> {
@@ -425,21 +530,29 @@ export function createSubtitleSidebarModal(
ctx.state.subtitleSidebarManualScrollUntilMs = Date.now() + MANUAL_SCROLL_HOLD_MS;
});
ctx.dom.subtitleSidebarModal.addEventListener('mouseenter', async () => {
ctx.state.isOverSubtitleSidebar = true;
restoreEmbeddedSidebarPassthrough();
if (!ctx.state.subtitleSidebarPauseVideoOnHover || ctx.state.subtitleSidebarPausedByHover) {
return;
}
const paused = await window.electronAPI.getPlaybackPaused();
const requestId = ++subtitleSidebarHoverRequestId;
let paused: boolean | null | undefined;
try {
paused = await window.electronAPI.getPlaybackPaused();
} catch {
paused = undefined;
}
if (requestId !== subtitleSidebarHoverRequestId) {
return;
}
if (paused === false) {
window.electronAPI.sendMpvCommand(['set_property', 'pause', 'yes']);
ctx.state.subtitleSidebarPausedByHover = true;
}
});
ctx.dom.subtitleSidebarModal.addEventListener('mouseleave', () => {
if (!ctx.state.subtitleSidebarPausedByHover) {
return;
}
ctx.state.subtitleSidebarPausedByHover = false;
window.electronAPI.sendMpvCommand(['set_property', 'pause', 'no']);
ctx.state.isOverSubtitleSidebar = false;
resumeSubtitleSidebarHoverPause();
});
window.addEventListener('resize', () => {
if (!ctx.state.subtitleSidebarModalOpen) {
@@ -450,6 +563,7 @@ export function createSubtitleSidebarModal(
}
return {
autoOpenSubtitleSidebarOnStartup,
openSubtitleSidebarModal,
closeSubtitleSidebarModal,
toggleSubtitleSidebarModal,

View File

@@ -723,6 +723,7 @@ body.subtitle-sidebar-embedded-open #secondarySubContainer {
#secondarySubContainer {
--secondary-sub-background-color: transparent;
--secondary-sub-backdrop-filter: none;
position: absolute;
top: 40px;
left: 50%;
@@ -779,7 +780,11 @@ body.settings-modal-open #secondarySubContainer {
}
body.subtitle-sidebar-embedded-open #secondarySubContainer.secondary-sub-hover {
transform: translateX(calc(var(--subtitle-sidebar-reserved-width) * -0.5));
left: 0;
right: var(--subtitle-sidebar-reserved-width);
max-width: none;
padding-right: 0;
transform: none;
}
#secondarySubContainer.secondary-sub-hover {
@@ -808,11 +813,13 @@ body.subtitle-sidebar-embedded-open #secondarySubContainer.secondary-sub-hover {
padding: 10px 18px;
}
#secondarySubContainer.secondary-sub-hover:hover {
#secondarySubContainer.secondary-sub-hover:hover,
#secondarySubContainer.secondary-sub-hover.secondary-sub-hover-active {
opacity: 1;
}
#secondarySubContainer.secondary-sub-hover:hover #secondarySubRoot {
#secondarySubContainer.secondary-sub-hover:hover #secondarySubRoot,
#secondarySubContainer.secondary-sub-hover.secondary-sub-hover-active #secondarySubRoot {
background: var(--secondary-sub-background-color, transparent);
backdrop-filter: var(--secondary-sub-backdrop-filter, none);
-webkit-backdrop-filter: var(--secondary-sub-backdrop-filter, none);
@@ -1490,7 +1497,6 @@ body.subtitle-sidebar-embedded-open #subtitleSidebarContent {
min-height: 0;
border-radius: 0;
background: transparent;
scroll-behavior: smooth;
}
.subtitle-sidebar-list::-webkit-scrollbar {
@@ -1531,6 +1537,12 @@ body.subtitle-sidebar-embedded-open #subtitleSidebarContent {
background: var(--subtitle-sidebar-hover-background-color, rgba(54, 58, 79, 0.65));
}
.subtitle-sidebar-item:focus-visible {
outline: 2px solid var(--subtitle-sidebar-active-line-color, #f5bde6);
outline-offset: -2px;
background: var(--subtitle-sidebar-hover-background-color, rgba(54, 58, 79, 0.65));
}
.subtitle-sidebar-item.active {
background: var(--subtitle-sidebar-active-background-color, rgba(138, 173, 244, 0.12));
}