Mute toggle, fullscreen shortcut, seek feedback overlay, playlist search, scroll-to-current, PiP, timestamp insertion, keyboard help, speed shortcuts, reset confirmation, error state, double-click fullscreen, playlist stats, per-item progress bars, collapsible docks.
10 KiB
TutorialVault — 15 UI Enhancements Design
Goal: Add 15 real, polished enhancements to the TutorialVault frontend — all WCAG 2.2 AAA compliant.
Architecture: All changes are frontend-only (TypeScript + CSS). No Rust/backend changes needed. Each enhancement is independent — no cross-dependencies between them.
Tech Stack: TypeScript, CSS, HTML (Tauri v2 webview frontend)
Enhancement 1: Mute Toggle (M key + volume icon click)
What: Press M to toggle mute. Click the volume icon to toggle mute. Muted state shows fa-volume-xmark, grays out the slider. Unmuting restores previous volume.
Files: main.ts, player.ts, player.css
Behavior:
- Store
lastVolumebefore muting. Set volume to 0 on mute. - On unmute, restore
lastVolume(default 1.0 if none stored). - Volume icon dynamically swaps:
fa-volume-high(>50%),fa-volume-low(>0%),fa-volume-xmark(0/muted). - Dragging the volume slider while muted unmutes automatically.
WCAG: Volume icon gets aria-label="Mute"/"Unmute" dynamically. Muted visual uses icon change + reduced opacity (not color alone).
Enhancement 2: Fullscreen Shortcut (F key)
What: Press F to toggle fullscreen. Mirrors the existing fullscreen button behavior.
Files: main.ts
Behavior: Add case 'f': to the keyboard switch. Calls the same fullscreen toggle as fsBtn.onclick.
WCAG: No visual changes needed.
Enhancement 3: Seek Feedback Overlay
What: Flash −5s / +5s text in the video overlay when seeking via arrow keys. Fades after 600ms.
Files: main.ts, player.ts, player.css
Behavior:
- New
showSeekFeedback(delta: number)function in player.ts. - Reuses the existing video overlay area. Shows text like "−5s" or "+5s" centered.
- Uses a CSS class
.seekFeedbackthat fades in/out. - Consecutive presses accumulate: pressing ArrowRight 3 times quickly shows "+15s".
WCAG: Overlay area has aria-live="assertive" so screen readers announce "Seeked forward 5 seconds". Text meets 7:1 contrast (white on semi-transparent dark).
Enhancement 4: Playlist Search/Filter
What: Search input above the playlist. Typing filters by video name or relative path in real-time. Shows "X of Y" while filtering. Clear button (x) resets.
Files: index.html, playlist.ts, playlist.css
Behavior:
- Add
<input id="plistSearch">in the playlist.panelHeader. renderList()checks the filter value and skips non-matching items.- Filter is case-insensitive, matches against
it.title,it.name,it.relpath. - Clear button appears only when input has text.
- Pressing Escape in the search input clears it.
WCAG: aria-label="Search playlist". Filtered count via aria-live="polite" region. Clear button has 44x44 target and aria-label="Clear search".
Enhancement 5: Scroll-to-Current Button
What: Button in playlist header that scrolls the active item into view. Only visible when the active item is off-screen.
Files: index.html, playlist.ts, playlist.css
Behavior:
- Uses
IntersectionObserveron the.row.activeelement. - Button appears (fades in) when active row is not visible.
- Clicking calls
activeRow.scrollIntoView({ block: 'center', behavior: 'smooth' }). - Icon:
fa-crosshairsorfa-location-dot.
WCAG: aria-label="Scroll to current video". 44x44 touch target. Hidden with display:none when not actionable (removed from a11y tree).
Enhancement 6: Picture-in-Picture Button
What: PiP toggle button next to the fullscreen button. Uses native requestPictureInPicture() API.
Files: index.html, player.ts
Behavior:
- New
iconBtnafter fsBtn:<button class="iconBtn" id="pipBtn">. - Icon swaps:
fa-up-right-from-square(enter PiP) /fa-down-left-and-up-right-to-center(exit PiP). - Listen to
enterpictureinpicture/leavepictureinpictureevents to update icon. - Feature-detect: hide button if
document.pictureInPictureEnabledis false.
WCAG: Dynamic aria-label="Enter picture-in-picture"/"Exit picture-in-picture". Same focus-visible ring as all icon buttons.
Enhancement 7: Timestamp Insertion in Notes
What: Button in the notes dock header that inserts [MM:SS] at the textarea cursor position using the current video time.
Files: index.html, ui.ts, panels.css
Behavior:
- Small button in
#notesHeader:<button id="insertTimestamp" aria-label="Insert timestamp">. - Icon:
fa-clockorfa-stopwatch. - On click: read
player.currentTime, format as[M:SS], insert atnotesBox.selectionStart. - After insert, focus the textarea and place cursor after the inserted text.
WCAG: aria-label="Insert timestamp". 44x44 target via ::before expansion. data-tooltip for hover discoverability.
Enhancement 8: Keyboard Shortcut Help Panel
What: Press ? to open a help overlay listing all keyboard shortcuts. Press ? or Escape to close.
Files: index.html, main.ts, main.css or components.css
Behavior:
- Hidden
<div id="shortcutHelp" role="dialog" aria-label="Keyboard shortcuts">in HTML. - Two-column grid showing key + description pairs.
- Shortcuts shown: Space (play/pause), Left/Right (seek ±5s), Up/Down (volume), M (mute), F (fullscreen), [ / ] (speed), Alt+Arrow (reorder playlist), ? (this help).
- Focus trap inside the dialog while open.
- Semi-transparent dark backdrop.
WCAG: role="dialog" with aria-label. Focus trapped. Escape closes. All text 7:1 contrast. Backdrop doesn't remove background content from a11y tree (uses aria-hidden="true" on app root while dialog is open).
Enhancement 9: Playback Speed Shortcuts ([ and ])
What: Press [ to decrease speed one step, ] to increase speed one step through the SPEEDS array.
Files: main.ts, player.ts
Behavior:
- New
cycleSpeed(delta: number)exported from player.ts. - Finds current speed in SPEEDS array, moves by delta (clamped to bounds).
- Updates speed button text, gauge icon, and saves to backend.
- Shows toast notification: "Speed: 1.25x".
WCAG: Speed change announced via toast (existing aria-live="polite" region).
Enhancement 10: Reset Progress Confirmation
What: Two-click confirmation before resetting progress. First click changes button to "Confirm?" state with red tint. Second click within 3 seconds actually resets. Timeout or click-away cancels.
Files: ui.ts, main.css
Behavior:
- First click: add
.confirmingclass, change icon tofa-exclamation-triangle, start 3s timer. - Second click while
.confirming: execute the actual reset. - Timer expiry or blur: remove
.confirming, restore original icon. .confirmingstate: red-tinted background (rgba(255,70,70,.14)).
WCAG: aria-label changes to "Confirm reset progress" in confirm state. Visual change uses both color AND icon change (not color alone).
Enhancement 11: Video Load Error State
What: When the <video> fires an error event, show a centered overlay with warning icon, error message, and "Try next" button.
Files: player.ts, player.css
Behavior:
- Listen to
player.addEventListener('error', ...). - Create/show error overlay inside
.videoWrapwithfa-triangle-exclamationicon. - Message: "This file format may not be supported. Try MP4 (H.264/AAC) or WebM."
- "Try next" button calls
nextPrev(+1). - Overlay clears on next successful
loadedmetadata.
WCAG: role="alert" for screen reader announcement. "Try next" button has 44x44 target. Error text meets 7:1 contrast.
Enhancement 12: Double-Click to Fullscreen
What: Double-clicking the video toggles fullscreen. Single click still toggles play/pause.
Files: player.ts
Behavior:
- Replace simple click handler with a click/double-click discriminator.
- On click: set a 300ms timer. If no second click arrives, toggle play/pause.
- On double-click: cancel the timer, toggle fullscreen instead.
dblclickevent on the video overlay area.
WCAG: Mouse/touch enhancement only. Fullscreen remains keyboard-accessible via F key and button.
Enhancement 13: Playlist Stats in Header
What: Show video count and completion info next to "Playlist" header. E.g., "24 videos · 8 done".
Files: index.html, playlist.ts
Behavior:
- Add
<span class="plistStats" id="plistStats"></span>in the panelHeader. - In
renderList(): compute total count, done count, update the span. - Styled as
--textMuted, smaller font (11px), monospace.
WCAG: Stats text meets 7:1 contrast. Updated via DOM so screen readers can read it on demand.
Enhancement 14: Mini Progress Bars Per Playlist Item
What: Thin 2px progress bar at the bottom of each playlist row showing watched/total. Green for done, accent blue for in-progress.
Files: playlist.ts, playlist.css
Behavior:
- For each row in
renderList(), create a.rowProgressdiv. - Width:
(watched / duration) * 100%. If duration is null, hide. - Color:
--successfor done items,--accentfor in-progress. - Positioned absolutely at bottom of the row.
WCAG: aria-hidden="true" — numeric info already in the row's aria-label. Colors pass 3:1 minimum for non-text contrast per WCAG 2.2.
Enhancement 15: Collapsible Dock Panes
What: Clicking a dock header collapses that pane, giving the other pane full height. Collapsed state shows just the header with a chevron. Persisted via prefs.
Files: ui.ts, panels.css, index.html
Behavior:
- Click on
.dockHeadertoggles.collapsedon the parent.dockPane. - Collapsed:
flex:0 0 auto, content inside hasdisplay:none. - Chevron icon in header rotates to indicate collapsed/expanded.
- Collapsing one pane doesn't collapse the other.
- State saved in prefs as
notes_collapsed/info_collapsedbooleans.
WCAG: Headers get aria-expanded="true"/"false". Chevron is aria-hidden="true". Enter/Space on header toggles. Collapsed content is display:none (removed from a11y tree).
Approach
Single-pass, all frontend: Every enhancement modifies only TypeScript and CSS files. No backend/Rust changes. Each enhancement is independent and can be implemented in any order.
Implementation strategy: Direct edits to existing files using Edit/Write tools. No subagents for file editing. Each enhancement touches 1-3 files. Build verification after each enhancement or batch.
Testing: npm run build (TypeScript compilation + Vite build) after each enhancement to catch errors immediately.