319 lines
15 KiB
HTML
Executable File
319 lines
15 KiB
HTML
Executable File
<!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" />
|
||
<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="settings">⚙ Settings</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">
|
||
<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>
|
||
</div>
|
||
<div id="bufferSpinner" class="spinner 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">
|
||
<input id="seek" class="seek" type="range" min="0" max="1000" value="0" />
|
||
<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>
|
||
<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="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 & download</button>
|
||
<button id="addPlaylistBtn" class="np-btn" title="Add to a playlist">+ Playlist</button>
|
||
</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 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>
|
||
|
||
<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="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) -->
|
||
<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>?</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="async-guard.js"></script>
|
||
<script src="sw-update.js"></script>
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|