Files
ytplayer/frontend/index.html

557 lines
30 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#1a1311" />
<!-- Build this page was served from — the server stamps it at request time;
app.js compares it with /api/version before offering an update. -->
<meta name="ytp-build" content="__BUILD_TAG__" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="YT Player" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self' https: data: asset: http://asset.localhost blob:; media-src 'self' https: blob: asset: http://asset.localhost; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com data:; script-src 'self'; connect-src 'self' https: ipc: http://ipc.localhost blob:; worker-src 'self';"
/>
<title>YT Player</title>
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700;12..96,800&family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="app">
<!-- Sidebar -->
<aside id="sidebar" class="sidebar">
<div class="brand">
<span class="brand-mark">▶</span> YT Player
</div>
<nav class="nav">
<button class="nav-item active" data-view="search">🔍 Search</button>
<button class="nav-item" data-view="queue">▶ Queue <span id="navQueueCount" class="nav-badge hidden">0</span></button>
<button class="nav-item" data-view="history">🕘 History</button>
<button class="nav-item" data-view="saved">💾 Saved</button>
<button class="nav-item" data-view="downloads">⬇ Downloads <span id="navDlCount" class="nav-badge hidden">0</span></button>
<button class="nav-item" data-view="library">📁 Library</button>
<button class="nav-item" data-view="notes">📝 Notes</button>
<button class="nav-item" data-view="stats">📊 Stats</button>
<button class="nav-item" data-view="settings">⚙ Settings</button>
<button class="nav-item" id="navPartyBtn" type="button" title="Watch together with friends">🎉 Watch party</button>
<button class="nav-item" id="navRemoteBtn" type="button" title="Control this screen from a phone, or use this phone as a remote">📱 Remote</button>
</nav>
<div class="pl-header">
<span>Playlists</span>
<button id="newPlaylistBtn" class="icon-btn" title="New playlist">+</button>
</div>
<div id="playlistList" class="playlist-list"></div>
<div class="pl-header smart-pl-header">
<span>Auto Playlists</span>
</div>
<div id="smartPlaylistList" class="playlist-list smart-pl-list"></div>
<div class="sidebar-footer">
<!-- Shown only when an online profile is linked (see updateProfileStatus) -->
<div id="sidebarProfile" class="sb-profile hidden">
<div class="sb-profile-id">
<span class="sb-profile-mark">👤</span>
<span id="sidebarProfileName" class="sb-profile-name"></span>
</div>
<div class="sb-profile-actions">
<button id="sidebarProfileCopy" class="sb-profile-btn" type="button" title="Copy profile link">⧉ Copy link</button>
<button id="sidebarProfileShare" class="sb-profile-btn" type="button" title="Share profile link">↗ Share</button>
</div>
</div>
<label class="switch">
<input type="checkbox" id="audioOnlyToggle" />
<span>Audio-only mode</span>
</label>
</div>
</aside>
<!-- Sidebar backdrop (mobile drawer) -->
<div id="sidebarBackdrop" class="sidebar-backdrop"></div>
<!-- Main -->
<main class="main">
<header class="topbar">
<button
id="sidebarToggle"
class="sidebar-toggle"
type="button"
aria-label="Toggle sidebar"
aria-controls="sidebar"
aria-expanded="false"
>☰</button>
<form id="searchForm" class="search-form">
<input
id="searchInput"
type="text"
placeholder="Search YouTube… (no login, no ads)"
autocomplete="off"
/>
<button type="submit" class="search-btn">Search</button>
</form>
</header>
<div class="body">
<!-- Player pane -->
<section id="playerPane" class="player-pane empty">
<div class="player-stage">
<video id="video" playsinline></video>
<audio id="audio"></audio>
<div id="artFallback" class="art-fallback hidden">
<img id="artImg" alt="" />
</div>
<div id="playerPlaceholder" class="player-placeholder">
<div class="hero">
<div class="ph-logo">▶</div>
<h1 class="hero-title">YT Player</h1>
<p class="hero-tagline">Ad-free YouTube — no login, no tracking. Search, play, and keep playlists right on your device.</p>
<div class="hero-pills">
<span class="hero-pill">No ads</span>
<span class="hero-pill">No sign-in</span>
<span class="hero-pill">Offline playlists</span>
</div>
<div class="hero-suggests">
<span class="hero-suggests-label">Try</span>
<button class="chip" data-q="lofi hip hop radio">lofi beats</button>
<button class="chip" data-q="live news">live news</button>
<button class="chip" data-q="relaxing music">relaxing music</button>
<button class="chip" data-q="podcast highlights">podcasts</button>
</div>
</div>
<!-- Replaces the hero once this device has playlists (renderHomePlaylists) -->
<div id="homePlaylists" class="home-pl hidden">
<div class="home-pl-head">
<span class="home-pl-title">Your playlists</span>
<button id="homePlNewBtn" class="home-pl-new" type="button">+ New</button>
</div>
<div id="homePlGrid" class="home-pl-grid"></div>
</div>
</div>
<div id="bufferSpinner" class="spinner hidden"></div>
<!-- Touch gesture feedback (Gestures in app.js) -->
<div id="gestureHud" class="gesture-hud hidden"></div>
</div>
<div id="controls" class="controls hidden">
<div class="seek-row">
<span id="curTime" class="time">0:00</span>
<div class="seek-wrap">
<!-- Audio-only: loudness waveform drawn behind the transparent range input -->
<canvas id="waveCanvas" class="wave-canvas" aria-hidden="true"></canvas>
<span id="waveTip" class="wave-tip hidden"></span>
<input id="seek" class="seek" type="range" min="0" max="1000" value="0" />
<div id="seekMarkers" class="seek-markers" aria-hidden="true"></div>
<div id="abIndicator" class="ab-indicator hidden">
<span id="abALabel" class="ab-label">A: --</span>
<span style="flex:1"></span>
<span id="abBLabel" class="ab-label">B: --</span>
</div>
</div>
<span id="durTime" class="time">0:00</span>
</div>
<button id="waveLoud" class="wave-loud hidden" type="button">⚡ Jump to the loudest part</button>
<div class="btn-row">
<button id="prevBtn" class="ctrl" title="Previous">⏮</button>
<button id="playBtn" class="ctrl play" title="Play/Pause">▶</button>
<button id="nextBtn" class="ctrl" title="Next">⏭</button>
<button id="abABtn" class="ctrl ab-ctrl" title="Set A-B loop start [A]">A</button>
<button id="abBBtn" class="ctrl ab-ctrl" title="Set A-B loop end [B]">B</button>
<button id="abClearBtn" class="ctrl ab-ctrl hidden" title="Clear A-B loop">↺</button>
<button id="rememberPosBtn" class="ctrl" title="Remember playback position for this song (per playlist)">📍</button>
<div class="vol">
<button id="muteBtn" class="ctrl" title="Mute">🔊</button>
<input id="volume" type="range" min="0" max="1" step="0.01" value="1" />
</div>
<div class="spacer"></div>
<label class="sel">
Speed
<select id="speedSelect">
<option value="0.5">0.5×</option>
<option value="0.75">0.75×</option>
<option value="1" selected>1×</option>
<option value="1.25">1.25×</option>
<option value="1.5">1.5×</option>
<option value="2">2×</option>
</select>
</label>
<label class="sel">
Quality
<select id="qualitySelect"></select>
</label>
<button id="sleepTimerBtn" class="ctrl" title="Sleep timer">⏱</button>
<button id="shuffleBtn" class="ctrl" title="Shuffle upcoming tracks (S)">🔀</button>
<button id="loopBtn" class="ctrl" title="Loop current video (L)">🔂</button>
<button id="repeatBtn" class="ctrl" title="Repeat list when finished (R)">🔁</button>
<button id="transitionBtn" class="ctrl" title="Between songs in a playlist: Normal — click to change">↦</button>
<button id="eqBtn" class="ctrl" title="Equalizer (E)">🎚</button>
<button id="pipBtn" class="ctrl hidden" title="Picture-in-picture (I)">⧉</button>
<button id="fsBtn" class="ctrl" title="Fullscreen">⛶</button>
</div>
</div>
<div id="sleepStatus" class="sleep-status hidden">
<span id="sleepCountdown" class="sleep-countdown"></span>
<button id="sleepCancelBtn" class="sleep-cancel">✕ Cancel</button>
</div>
<div id="nowPlayingMeta" class="now-meta hidden">
<div class="np-text">
<div class="np-title" id="npTitle"></div>
<div class="np-channel" id="npChannel"></div>
</div>
<div class="np-actions">
<button id="queueBtn" class="np-btn" title="Add to the temporary queue">+ Queue</button>
<button id="saveBtn" class="np-btn" title="Save this video for offline playback">⬇ Save</button>
<button id="editBtn" class="np-btn" title="Cut parts out and save a custom edited copy">✂ Edit &amp; download</button>
<button id="addPlaylistBtn" class="np-btn" title="Add to a playlist">+ Playlist</button>
<button id="shareMomentBtn" class="np-btn" title="Share a link or GIF of this exact moment">🔗 Share moment</button>
<button id="externalBtn" class="np-btn" title="Play this in VLC, MX Player or another app">📺 Open in…</button>
<button id="brokenBtn" class="np-btn" title="Video won't play or looks broken? Re-download the server's copy" hidden>⚠ Broken</button>
</div>
</div>
<!-- Lyrics · chapters · bookmarks (shared notes, notes.js on the server) -->
<div id="notesPanel" class="notes-panel hidden">
<div class="notes-tabs" role="tablist">
<button class="notes-tab active" data-tab="lyrics" role="tab" type="button">Lyrics</button>
<button class="notes-tab" data-tab="markers" role="tab" type="button">Chapters &amp; bookmarks <span id="markersCount" class="notes-count"></span></button>
<button class="notes-tab" data-tab="search" role="tab" type="button">🔎 Search</button>
<span id="notesStatus" class="notes-status"></span>
</div>
<div id="lyricsPane" class="notes-pane">
<div class="lyrics-toolbar">
<button id="lyricsEditBtn" class="np-btn" type="button" title="Edit the shared lyrics">✎ Edit</button>
<button id="lyricsSyncBtn" class="np-btn" type="button" title="Tap along to time each line">⏱ Tap-sync</button>
<button id="lyricsHistoryBtn" class="np-btn" type="button" title="Earlier versions">🕘 History</button>
<span class="notes-spacer"></span>
<span class="lyr-offset" title="Your personal sync offset for this song — positive shows lyrics later">
<button id="lyrOffMinus" class="lyr-off-btn" type="button">−0.5s</button>
<span id="lyrOffVal" class="lyr-off-val">0.0s</span>
<button id="lyrOffPlus" class="lyr-off-btn" type="button">+0.5s</button>
</span>
<label class="lyr-auto"><input type="checkbox" id="lyrAutoscroll" checked /> Autoscroll</label>
</div>
<div id="lyricsSyncBar" class="lyrics-syncbar hidden">
<span id="lyricsSyncHint" class="lyrics-sync-hint"></span>
<button id="lyricsStampBtn" class="np-btn primary-btn" type="button">Stamp line ⏎</button>
<button id="lyricsUndoBtn" class="np-btn" type="button">↶ Undo</button>
<button id="lyricsSyncCancel" class="np-btn" type="button">Cancel</button>
<button id="lyricsSyncDone" class="np-btn" type="button">✓ Save timings</button>
</div>
<div id="lyricsTags" class="lyrics-tags"></div>
<div id="lyricsList" class="lyrics-list"></div>
</div>
<div id="searchPane" class="notes-pane hidden">
<div class="ts-bar">
<input id="tsQuery" class="ts-query" type="search" placeholder="Search what's said or sung…" autocomplete="off" />
<select id="tsSource" class="ts-source" aria-label="Search in">
<option value="lyrics">Lyrics</option>
<option value="captions">YouTube transcript</option>
</select>
</div>
<div id="tsInfo" class="ts-info"></div>
<div id="tsResults" class="ts-results"></div>
</div>
<div id="markersPane" class="notes-pane hidden">
<div id="markerChips" class="marker-chips"></div>
<div class="markers-toolbar">
<button id="addChapterBtn" class="np-btn" type="button">+ Chapter</button>
<button id="addBookmarkBtn" class="np-btn" type="button" title="Pin a private bookmark here [P]">📌 Bookmark</button>
<button id="addNoteBtn" class="np-btn" type="button" title="Write a note tied to this moment [N]">📝 Note</button>
<span class="notes-spacer"></span>
<button id="chaptersHistoryBtn" class="np-btn" type="button" title="Earlier versions of the shared chapters">🕘</button>
<button id="exportMarkersBtn" class="np-btn" type="button">⤓ Export</button>
</div>
<div class="markers-section">
<div class="markers-heading">Chapters <span class="markers-scope">shared with everyone</span></div>
<div id="chapterList" class="marker-list"></div>
</div>
<div class="markers-section">
<div class="markers-heading">My notes &amp; bookmarks <span class="markers-scope">only you</span></div>
<div id="bookmarkList" class="marker-list"></div>
</div>
</div>
</div>
<!-- Up next queue -->
<div id="upnext" class="upnext hidden">
<div class="upnext-header">
<span>Up next</span>
<span id="upnextCount" class="upnext-count">0</span>
</div>
<div id="upnextList" class="upnext-list"></div>
</div>
<!-- Related videos -->
<div id="relatedPanel" class="related-panel hidden">
<div class="related-header">
<span class="related-label">Related</span>
<button id="relatedToggleBtn" class="related-toggle-btn">−</button>
</div>
<div id="relatedList" class="related-list"></div>
</div>
</section>
<!-- List pane -->
<section id="listPane" class="list-pane">
<div class="list-header">
<h2 id="listTitle">Search</h2>
<div id="listActions" class="list-actions"></div>
</div>
<div id="listFilterBar" class="list-filter-bar hidden">
<input id="listFilterInput" type="text" placeholder="Filter…" autocomplete="off" />
</div>
<div id="batchBar" class="batch-bar hidden">
<span id="batchCount" class="batch-count">0 selected</span>
<button id="batchQueueBtn" class="batch-btn">+ Queue</button>
<button id="batchAddPlaylistBtn" class="batch-btn">+ Playlist</button>
<button id="batchDeleteBtn" class="batch-btn batch-danger">Delete</button>
<button id="batchCancelBtn" class="batch-btn">Cancel</button>
</div>
<div id="status" class="status hidden"></div>
<div id="cards" class="cards"></div>
</section>
</div>
</main>
</div>
<!-- Generic modal -->
<div id="modal" class="modal-backdrop hidden">
<div class="modal">
<h3 id="modalTitle"></h3>
<div id="modalBody"></div>
<div class="modal-actions" id="modalActions"></div>
</div>
</div>
<!-- Service mode (set-list view) overlay -->
<section id="serviceMode" class="service-mode hidden" aria-label="Service Mode">
<div class="service-topbar">
<div id="serviceWakeState" class="service-wake-state">Screen may sleep</div>
<div class="service-topbar-actions">
<button id="serviceAutoscrollBtn" class="service-exit-btn service-video-btn hidden" type="button"
aria-pressed="true" aria-label="Keep the sung line centred">⇅ Auto-scroll on</button>
<button id="serviceLyricsBtn" class="service-exit-btn service-video-btn" type="button"
aria-pressed="false" aria-label="Show only the lyrics">🎵 Lyrics off</button>
<button id="serviceVideoBtn" class="service-exit-btn service-video-btn" type="button"
aria-pressed="false" aria-label="Show or hide the video">📺 Video off</button>
<button id="serviceExitBtn" class="service-exit-btn" type="button" aria-label="Exit Service Mode">Exit</button>
</div>
</div>
<div class="service-center">
<div class="service-meta">
<h1 id="serviceTitle" class="service-title"></h1>
<div id="serviceChannel" class="service-channel"></div>
<div id="serviceNote" class="service-note hidden"></div>
</div>
<div id="serviceUpNext" class="service-upnext">Last song</div>
<!-- Lyrics-only view: the sung line is centred, largest and highlighted -->
<div id="serviceLyrics" class="service-lyrics hidden" aria-live="off">
<div id="serviceLyricsList" class="sl-list"></div>
</div>
</div>
<div class="service-bottom">
<div id="serviceTime" class="service-time">
<span id="serviceCurTime">0:00</span> / <span id="serviceDurTime">0:00</span>
</div>
<div class="service-controls">
<button id="servicePrevBtn" class="service-btn" type="button" aria-label="Previous track">⏮</button>
<button id="servicePlayBtn" class="service-btn service-btn-play" type="button" aria-label="Play or Pause">▶</button>
<button id="serviceNextBtn" class="service-btn" type="button" aria-label="Next track">⏭</button>
</div>
</div>
</section>
<!-- Sleep timer "screen off": black curtain; tap to wake -->
<div id="sleepCurtain" class="sleep-curtain hidden" role="button" aria-label="Wake">
<span id="sleepCurtainMsg">Sleep timer ended · tap to wake</span>
</div>
<div id="toastContainer" class="toast-container"></div>
<!-- Mini now-playing bar -->
<div id="miniBar" class="mini-bar hidden">
<div class="mini-bar-inner">
<button id="miniPlayBtn" class="mini-btn">▶</button>
<div class="mini-info">
<div id="miniTitle" class="mini-title"></div>
<div id="miniChannel" class="mini-channel"></div>
</div>
<span id="miniTime" class="mini-time">0:00</span>
<button id="miniServiceBtn" class="mini-btn mini-service" type="button"
title="Service mode" aria-label="Service mode">🎤</button>
<button id="miniCloseBtn" class="mini-btn mini-close">✕</button>
</div>
<div id="miniProgress" class="mini-progress"><div id="miniProgressFill" class="mini-progress-fill"></div></div>
</div>
<!-- YouTube-style bottom navigation bar (portrait PWA mode only) -->
<!-- Watch party panel (Party in app.js) -->
<aside id="partyPanel" class="party-panel hidden" aria-label="Watch party">
<div class="pt-head"><span id="ptTitle">🎉 Watch party</span><span id="ptStatus" class="pt-status"></span><button id="ptMin" class="rv-icon" type="button" aria-label="Minimise">▁</button></div>
<div class="pt-body"></div>
</aside>
<!-- Desktop: shown while phones are driving this screen (remote.js) -->
<button id="remoteChip" class="remote-chip hidden" type="button" title="Phone remote settings">
<span class="remote-chip-dot"></span><span id="remoteChipText">Remote connected</span>
</button>
<!-- Presenter / projector screen: lyrics of whatever the paired screen plays -->
<section id="presenterView" class="presenter-view hidden" aria-label="Presenter view">
<div id="pvList" class="sl-list pv-list"></div>
<div id="pvTitle" class="pv-title hidden"></div>
<div id="pvBar" class="pv-bar">
<span id="pvStatus" class="pv-status">Connecting…</span>
<span class="pv-hint">Double-click or F = fullscreen · B = blank screen</span>
<button id="pvFull" class="pv-btn" type="button">⛶ Fullscreen</button>
</div>
<div id="pvPair" class="pv-pair hidden">
<div class="pv-pair-title">Presenter screen</div>
<p>Enter the 6-digit code shown on the playing computer (📱 Remote → Control this screen → Presenter).</p>
<input id="pvCode" inputmode="numeric" maxlength="7" placeholder="000 000" />
<button id="pvPairBtn" class="rv-primary" type="button">Connect</button>
<div id="pvMsg" class="rv-pair-msg"></div>
</div>
<div id="pvBlank" class="pv-blank hidden"></div>
</section>
<!-- Phone: remote control for a desktop instance -->
<section id="remoteView" class="remote-view hidden" aria-label="Phone remote">
<div class="rv-top">
<button id="rvClose" class="rv-icon" type="button" aria-label="Close remote">✕</button>
<div class="rv-host">
<div id="rvHostName" class="rv-host-name">Remote</div>
<div id="rvStatus" class="rv-status">Not connected</div>
</div>
<button id="rvMenu" class="rv-icon" type="button" aria-label="Remote options">⋯</button>
</div>
<div id="rvPair" class="rv-pair">
<div class="rv-pair-title">Connect to a screen</div>
<p class="rv-pair-help">On the computer that plays to the TV or speakers, open <b>📱 Remote</b> → <b>Control from a phone</b>, then enter the 6-digit code shown there (or scan its QR code with your camera).</p>
<input id="rvCode" class="rv-code" inputmode="numeric" autocomplete="one-time-code" maxlength="7" placeholder="000 000" />
<input id="rvName" class="rv-name" maxlength="40" placeholder="This phone's name" />
<button id="rvPairBtn" class="rv-primary" type="button">Pair</button>
<div id="rvPairMsg" class="rv-pair-msg"></div>
</div>
<div id="rvControl" class="rv-control hidden">
<div class="rv-now">
<img id="rvThumb" class="rv-thumb" alt="" />
<div class="rv-now-text">
<div id="rvTitle" class="rv-title">Nothing playing</div>
<div id="rvChannel" class="rv-channel"></div>
</div>
</div>
<div class="rv-seek">
<input id="rvSeek" type="range" min="0" max="1000" value="0" />
<div class="rv-times"><span id="rvCur">0:00</span><span id="rvDur">0:00</span></div>
</div>
<div class="rv-transport">
<button id="rvPrev" class="rv-tbtn" type="button" aria-label="Previous">⏮</button>
<button id="rvToggle" class="rv-tbtn rv-play" type="button" aria-label="Play or pause">▶</button>
<button id="rvNext" class="rv-tbtn" type="button" aria-label="Next">⏭</button>
</div>
<div class="rv-volume">
<span>🔊</span><input id="rvVolume" type="range" min="0" max="100" value="100" /><span id="rvVolVal">100%</span>
</div>
<div class="rv-queue-head"><span>Up next</span><span id="rvQueueCount"></span></div>
<div id="rvQueue" class="rv-queue"></div>
<div class="rv-queue-hint">Tap a song to play it · drag ⠿ to reorder · ✕ removes</div>
<div class="rv-actions">
<button id="rvAdd" class="rv-action" type="button">+ Add to queue</button>
<button id="rvSleep" class="rv-action" type="button">⏱ Sleep</button>
<button id="rvService" class="rv-action" type="button">🎤 Service mode</button>
</div>
</div>
</section>
<nav id="bottomNav" class="bottom-nav" aria-label="Main navigation">
<button class="bottom-nav-btn active" data-view="search" aria-label="Search">
<span class="bottom-nav-icon">🔍</span>
<span class="bottom-nav-label">Search</span>
</button>
<button class="bottom-nav-btn" data-view="queue" aria-label="Queue">
<span class="bottom-nav-icon">▶</span>
<span class="bottom-nav-label">Queue</span>
<span id="bnQueueCount" class="bottom-nav-badge hidden">0</span>
</button>
<button class="bottom-nav-btn" data-view="history" aria-label="History">
<span class="bottom-nav-icon">🕘</span>
<span class="bottom-nav-label">History</span>
</button>
<button class="bottom-nav-btn" data-view="saved" aria-label="Saved">
<span class="bottom-nav-icon">💾</span>
<span class="bottom-nav-label">Saved</span>
</button>
<button class="bottom-nav-btn" data-view="downloads" aria-label="Downloads">
<span class="bottom-nav-icon">⬇</span>
<span class="bottom-nav-label">Downloads</span>
<span id="bnDlCount" class="bottom-nav-badge hidden">0</span>
</button>
<button class="bottom-nav-btn" data-view="settings" aria-label="Settings">
<span class="bottom-nav-icon">⚙</span>
<span class="bottom-nav-label">More</span>
</button>
</nav>
<!-- Keyboard shortcuts help overlay -->
<div id="shortcutHelp" class="shortcut-overlay hidden">
<div class="shortcut-panel">
<div class="shortcut-header">
<h2>Keyboard Shortcuts</h2>
<button id="shortcutClose" class="shortcut-close">✕</button>
</div>
<div class="shortcut-grid">
<div class="shortcut-row"><kbd>Space</kbd><span>Play / Pause</span></div>
<div class="shortcut-row"><kbd>←</kbd><kbd>→</kbd><span>Seek backward / forward 5s</span></div>
<div class="shortcut-row"><kbd>↑</kbd><kbd>↓</kbd><span>Volume up / down</span></div>
<div class="shortcut-row"><kbd>F</kbd><span>Toggle fullscreen</span></div>
<div class="shortcut-row"><kbd>M</kbd><span>Toggle mute</span></div>
<div class="shortcut-row"><kbd>S</kbd><span>Shuffle upcoming tracks</span></div>
<div class="shortcut-row"><kbd>L</kbd><span>Loop current video</span></div>
<div class="shortcut-row"><kbd>R</kbd><span>Repeat list when finished</span></div>
<div class="shortcut-row"><kbd>Q</kbd><span>Add current video to queue</span></div>
<div class="shortcut-row"><kbd>A</kbd><span>Set A-B loop start point</span></div>
<div class="shortcut-row"><kbd>B</kbd><span>Set A-B loop end point</span></div>
<div class="shortcut-row"><kbd>P</kbd><span>Pin a bookmark at the current time</span></div>
<div class="shortcut-row"><kbd>N</kbd><span>Write a note at this moment</span></div>
<div class="shortcut-row"><kbd>I</kbd><span>Picture-in-picture</span></div>
<div class="shortcut-row"><kbd>E</kbd><span>Equalizer</span></div>
<div class="shortcut-row"><kbd>?</kbd><span>Show this help</span></div>
<div class="shortcut-row"><kbd>Esc</kbd><span>Close overlay / cancel selection</span></div>
</div>
</div>
</div>
<script src="fingerprint.js"></script>
<script src="opfs.js"></script>
<script src="video-edit.js"></script>
<script src="lyrics-core.js"></script>
<script src="stats-core.js"></script>
<script src="async-guard.js"></script>
<script src="sw-update.js"></script>
<script src="app.js"></script>
</body>
</html>