a11y: bring UI to WCAG 2.2 AAA compliance

Semantic HTML: lang attr, landmarks (header/main/region/complementary),
heading hierarchy (h1-h3), dl/dt/dd for info panel.

ARIA: labels on all icon buttons, aria-hidden on decorative icons,
progressbar role with dynamic aria-valuenow, aria-haspopup/expanded
on all menu triggers, role=listbox/option on playlist, aria-selected,
computed aria-labels on playlist rows.

Contrast: raised --textMuted/--textDim/--icon to AAA 7:1 ratios.

Focus: global :focus-visible outline, slider thumb glow, menu item
highlight, switch focus-within, row focus styles.

Target sizes: 44x44 hit areas on zoom/window/remove buttons via
::before pseudo-elements.

Keyboard: playlist arrow nav + Enter/Space activate + Alt+Arrow
reorder with live region announcements + move buttons. Speed menu,
subtitles menu, and recent menu all keyboard-navigable with
Arrow/Enter/Space/Escape. Dividers resizable via Arrow keys.

Dynamic document.title updates on video/folder load.
This commit is contained in:
Your Name
2026-02-19 16:35:19 +02:00
parent 290ef82176
commit 52934d15d6
11 changed files with 956 additions and 662 deletions

View File

@@ -3,18 +3,15 @@
* Orchestrates all modules and holds cross-module callbacks.
*/
import '@fortawesome/fontawesome-free/css/all.min.css';
import '@fontsource/sora/500.css';
import '@fontsource/sora/600.css';
import '@fontsource/sora/700.css';
import '@fontsource/sora/800.css';
import '@fontsource/manrope/400.css';
import '@fontsource/manrope/500.css';
import '@fontsource/manrope/600.css';
import '@fontsource/manrope/700.css';
import '@fontsource/manrope/800.css';
import '@fontsource/ibm-plex-mono/400.css';
import '@fontsource/ibm-plex-mono/500.css';
import '@fontsource/ibm-plex-mono/600.css';
import '@fontsource/fraunces/600.css';
import '@fontsource/fraunces/700.css';
import '@fontsource/fraunces/800.css';
import '@fontsource/inter/400.css';
import '@fontsource/inter/500.css';
import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
import '@fontsource/space-mono/400.css';
import '@fontsource/space-mono/700.css';
import './styles/main.css';
import './styles/player.css';
import './styles/playlist.css';
@@ -37,7 +34,7 @@ import {
getVideoDuration, getPlayer, isVolDragging,
} from './player';
import { initPlaylist, renderList } from './playlist';
import { initPlaylist, renderList, isDragging } from './playlist';
import { initSubtitles, refreshSubtitles, clearSubtitles } from './subtitles';
import {
initUI, applyZoom, applySplit, applyDockSplit,
@@ -97,6 +94,7 @@ async function boot(): Promise<void> {
updateInfoPanel();
buildSpeedMenu(1.0);
notify('Open a folder to begin.');
document.title = 'TutorialVault - Open a folder';
}
// ---- onLibraryLoaded ----
@@ -123,6 +121,7 @@ async function onLibraryLoaded(info: LibraryInfo, startScan: boolean): Promise<v
updateOverall();
renderList();
updateInfoPanel();
document.title = info?.folder ? `${info.folder} - TutorialVault` : 'TutorialVault';
if (startScan) {
try { await api.startDurationScan(); } catch (_) {}
@@ -144,6 +143,7 @@ async function loadIndex(
const it = library.items[currentIndex] || null;
updateNowHeader(it);
document.title = it ? `${it.title || it.name} - TutorialVault` : 'TutorialVault';
await api.setCurrent(currentIndex, Number(timecode || 0.0));
renderList();
@@ -197,7 +197,7 @@ async function tick(): Promise<void> {
updateInfoPanel();
updateNowHeader(currentItem());
if (oldIndex !== currentIndex || oldCount !== (library?.items?.length || 0)) {
if (!isDragging() && (oldIndex !== currentIndex || oldCount !== (library?.items?.length || 0))) {
renderList();
}
}