Files
ytplayer/frontend/index.html

602 lines
36 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 'self' 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="preload" href="fonts/BricolageGrotesque-latin.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="fonts/HankenGrotesk-latin.woff2" as="font" type="font/woff2" crossorigin />
<link rel="stylesheet" href="fonts/fonts.css" />
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="service-ui.css" />
<link rel="stylesheet" href="refresh.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>
<span id="syncSpinner" class="sync-spinner hidden" title="Syncing with your profile…" aria-live="polite" role="status"></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>
<div id="searchRecent" class="search-recent hidden" role="listbox" aria-label="Recent searches"></div>
</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>
<!-- Lyrics shown in place of the video (watch party / sing-along) -->
<div id="stageLyrics" class="stage-lyrics hidden">
<div id="stageLyricsList" class="sl-list"></div>
<div class="stage-lyrics-bar">
<button id="stageLyricsAuto" class="np-btn" type="button" aria-pressed="true">⇅ Auto-scroll on</button>
<button id="stageLyricsOff" class="np-btn" type="button">📺 Show video</button>
</div>
</div>
<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>
<button id="fsMini" class="fs-mini" type="button" title="Fullscreen" aria-label="Fullscreen"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></svg></button>
</div>
<button id="waveLoud" class="wave-loud hidden" type="button">⚡ Jump to the loudest part</button>
<div class="deck-main">
<button id="modeBtn" class="ctrl" type="button" title="Playback mode" aria-label="Playback mode"></button>
<button id="prevBtn" class="ctrl" title="Previous" aria-label="Previous"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><rect x="5" y="5" width="2.8" height="14" rx="1.2"/><path d="M19 5.5v13L9.5 12z"/></svg></button>
<button id="playBtn" class="ctrl play" title="Play/Pause" aria-label="Play or pause"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><path d="M9 5.5v13l10-6.5z"/></svg></button>
<button id="nextBtn" class="ctrl" title="Next" aria-label="Next"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><rect x="16.2" y="5" width="2.8" height="14" rx="1.2"/><path d="M5 5.5v13l9.5-6.5z"/></svg></button>
<button id="muteBtn" class="ctrl" title="Mute" aria-label="Mute">🔊</button>
<div id="dkModeHost" hidden><button id="shuffleBtn" class="ctrl" title="Shuffle upcoming tracks (S)" aria-label="Shuffle"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M16 3h5v5M4 20 21 3M21 16v5h-5M15 15l6 6M4 4l5 5"/></svg></button><button id="repeatBtn" class="ctrl" title="Repeat list when finished (R)" aria-label="Repeat list"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3"/></svg></button><button id="loopBtn" class="ctrl" title="Loop this video (L)"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3M11 10.2l1.6-.9v5.4"/></svg></button></div>
</div>
<div class="deck-vol vol">
<input id="volume" type="range" min="0" max="1" step="0.01" value="1" aria-label="Volume" />
</div>
<div class="dk-quick">
<button id="fsBtn" class="ctrl" title="Fullscreen" aria-label="Fullscreen"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></svg><span class="fs-label">Fullscreen</span></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="ctlMoreBtn" class="dk-more" type="button" aria-haspopup="dialog" title="More controls" aria-label="More controls"><span><em class="mc-dots">⋯</em><i class="mc-label"> More<i class="mc-rest"> controls</i></i></span><small>Speed · Quality · A-B · Sleep · EQ</small></button>
</div>
<div id="dkMoreHost" hidden>
<button id="rememberPosBtn" class="ctrl" title="Remember playback position for this song (per playlist)">📍</button>
<button id="sleepTimerBtn" class="ctrl" title="Sleep timer">⏱</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>
<label class="sel dk-sel">
<span>Speed</span>
<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 dk-sel">
<span>Quality</span>
<select id="qualitySelect"></select>
</label>
</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 class="np-avail hidden" id="npAvail" aria-live="polite"></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="exportBtn" class="np-btn" title="Save this video to your gallery, Files app or Downloads folder — works while streaming too">🖼 Gallery</button>
<button id="addPlaylistBtn" class="np-btn" title="Add to a playlist">+ Playlist</button>
<div id="npMoreHost" class="np-more-host" hidden>
<button id="editBtn" class="np-btn" title="Cut parts out and save a custom edited copy">✂ Edit &amp; download</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>
<button id="moreBtn" class="np-more" type="button" aria-haspopup="dialog"><span>⋯ More actions</span><small>Edit &amp; download · Share moment · Open in… · Report broken</small></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>
<button id="notesFoldBtn" class="notes-fold" type="button" aria-expanded="true" title="Fold this card">▾</button>
</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>
<button id="lyricsFlagBtn" class="lyr-flag hidden" type="button" aria-pressed="false" title="Mark a line with wrong words or timing — an admin reviews it"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 21V4M5 4h11l-2 4 2 4H5"/></svg><span>Report a wrong line</span></button>
</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 -->
<!-- Mobile: floating rail that jumps between the cards on this page and
follows the scroll. Tapping the active icon folds that card. -->
<nav id="sectionRail" class="sec-rail hidden" aria-label="Jump to a section"></nav>
<section id="serviceMode" class="service-mode hidden" aria-label="Service Mode">
<div class="svc-top">
<div class="svc-head">
<div id="serviceWakeState" class="svc-wake" role="status">Screen may sleep</div>
<button id="serviceExitBtn" class="svc-exit" type="button" aria-label="Exit Service Mode">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>Exit
</button>
</div>
<div class="svc-dock" role="toolbar" aria-label="Service view">
<button id="serviceLyricsBtn" class="svc-tile" type="button" aria-pressed="false" aria-label="Show only the lyrics">
<svg class="svc-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 18V6l10-2v12"/><circle cx="6.5" cy="18" r="2.5"/><circle cx="16.5" cy="16" r="2.5"/></svg>
<span class="svc-label">Lyrics</span><i class="svc-led"></i>
</button>
<button id="serviceAutoscrollBtn" class="svc-tile hidden" type="button" aria-pressed="true" aria-label="Keep the sung line centred">
<svg class="svc-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v16M7 9l5-5 5 5M7 15l5 5 5-5"/></svg>
<span class="svc-label">Auto-scroll</span><i class="svc-led"></i>
</button>
<button id="serviceFlagBtn" class="svc-tile svc-flag hidden" type="button" aria-pressed="false" aria-label="Report a wrong lyric line">
<svg class="svc-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 21V4M5 4h11l-2 4 2 4H5"/></svg>
<span class="svc-label">Report</span><i class="svc-led"></i>
</button>
<button id="serviceVideoBtn" class="svc-tile" type="button" aria-pressed="false" aria-label="Show or hide the video">
<svg class="svc-ico" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="6" width="13" height="12" rx="2.5"/><path d="M16 10.5l5-3v9l-5-3"/></svg>
<span class="svc-label">Video</span><i class="svc-led"></i>
</button>
</div>
<div id="serviceFlagHint" class="svc-hint hidden" role="status">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 21V4M5 4h11l-2 4 2 4H5"/></svg>
<span>Tap the line that’s wrong</span>
</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 class="svc-progress" aria-hidden="true"><i id="serviceProgressFill"></i></div>
<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="flag-ui.js"></script>
<script src="export.js"></script>
<script src="stats-core.js"></script>
<script src="sha256.js"></script>
<script src="device-db.js"></script>
<script src="p2p-client.js"></script>
<script src="p2p-core.js"></script>
<script src="p2p-transfer.js"></script>
<script src="async-guard.js"></script>
<script src="sw-update.js"></script>
<script src="app.js"></script>
</body>
</html>