diff --git a/BACKLOG.md b/BACKLOG.md index 85a9737..a90bc06 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -17,7 +17,7 @@ bottom as they come up. ## UX / polish - [x] Empty-state designs for empty playlists and empty history (match the new hero). - [x] Loading skeletons for search results instead of a bare "Searching…". -- [ ] Keyboard-shortcut help overlay (press `?`), listing space / arrows / f / m. +- [x] Keyboard-shortcut help overlay (press `?`), listing space / arrows / f / m. - [ ] Download/preload progress indicator on cards (percent or a bar, not just a pulse). - [ ] Remember and restore the last playback position per video. - [ ] Toast queue (stack multiple toasts instead of replacing). diff --git a/frontend/app.js b/frontend/app.js index ccc0c50..d4aa902 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -944,7 +944,24 @@ function deletePlaylist(pl) { ]); } -// ---------- Modal ---------- +// ============================================================================ +// Keyboard shortcut help overlay +// ============================================================================ +function toggleShortcutHelp() { + const overlay = $('shortcutHelp'); + overlay.classList.toggle('hidden'); +} + +function wireShortcutHelp() { + $('shortcutClose').addEventListener('click', () => $('shortcutHelp').classList.add('hidden')); + $('shortcutHelp').addEventListener('click', (e) => { + if (e.target.id === 'shortcutHelp') $('shortcutHelp').classList.add('hidden'); + }); +} + +// ============================================================================ +// Modal +// ============================================================================ function showModal(title, bodyNode, actions) { $('modalTitle').textContent = title; const body = $('modalBody'); @@ -1087,6 +1104,8 @@ function wireUI() { else if (e.code === 'ArrowLeft') Player.seek(Math.max(0, Player.master.currentTime - 5)); else if (e.key === 'f') els.fsBtn.click(); else if (e.key === 'm') els.muteBtn.click(); + else if (e.key === '?') toggleShortcutHelp(); + else if (e.key === 'Escape' && !$('shortcutHelp').classList.contains('hidden')) { $('shortcutHelp').classList.add('hidden'); } }); $('modal').addEventListener('click', (e) => { if (e.target.id === 'modal') closeModal(); }); @@ -1098,6 +1117,7 @@ function wireUI() { async function boot() { wirePlayerEvents(); wireUI(); + wireShortcutHelp(); try { const loaded = await API.loadData(); if (loaded && typeof loaded === 'object') { diff --git a/frontend/index.html b/frontend/index.html index 1248978..20a8b5a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -164,6 +164,25 @@ + + + diff --git a/frontend/styles.css b/frontend/styles.css index 64f468a..c1f3aef 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -915,6 +915,73 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); } } @keyframes toastIn { from { opacity: 0; transform: translate(-50%, 12px); } to { opacity: 1; transform: translate(-50%, 0); } } +/* ===================== Keyboard shortcut overlay ===================== */ +.shortcut-overlay { + position: fixed; inset: 0; + z-index: 500; + background: rgba(0,0,0,0.55); + backdrop-filter: blur(4px); + display: flex; align-items: center; justify-content: center; + animation: fadeIn 0.2s ease; +} +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } +.shortcut-panel { + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 28px 32px; + min-width: 380px; + max-width: 480px; + box-shadow: 0 24px 64px -16px rgba(0,0,0,0.6); + animation: panelIn 0.2s var(--ease); +} +@keyframes panelIn { from { opacity: 0; transform: scale(0.95) translateY(10px); } to { opacity: 1; transform: none; } } +.shortcut-header { + display: flex; align-items: center; justify-content: space-between; + margin-bottom: 20px; +} +.shortcut-header h2 { + margin: 0; + font-family: var(--display); + font-weight: 700; + font-size: 20px; + letter-spacing: -0.01em; +} +.shortcut-close { + background: var(--bg-3); + border: 1px solid var(--line); + color: var(--text-2); + width: 32px; height: 32px; + border-radius: 9px; + cursor: pointer; + font-size: 15px; + display: grid; place-items: center; + transition: all 0.16s; +} +.shortcut-close:hover { color: var(--text); border-color: var(--accent); background: var(--bg-3); } +.shortcut-grid { + display: flex; flex-direction: column; gap: 10px; +} +.shortcut-row { + display: flex; align-items: center; gap: 10px; + padding: 6px 0; + border-bottom: 1px solid var(--line-soft); +} +.shortcut-row:last-child { border-bottom: none; } +.shortcut-row span { color: var(--text-2); font-size: 13px; flex: 1; } +.shortcut-row kbd { + font-family: var(--mono); + font-size: 11px; font-weight: 700; + color: var(--text); + background: var(--bg-3); + border: 1px solid var(--line); + border-radius: 6px; + padding: 4px 9px; + min-width: 28px; + text-align: center; + line-height: 1; +} + .hidden { display: none !important; } @media (max-width: 1080px) { diff --git a/releases/YT Player_1.0.0_x64-setup.exe b/releases/YT Player_1.0.0_x64-setup.exe index 0dd335f..4f5be60 100755 Binary files a/releases/YT Player_1.0.0_x64-setup.exe and b/releases/YT Player_1.0.0_x64-setup.exe differ diff --git a/releases/YT Player_1.0.0_x64_en-US.msi b/releases/YT Player_1.0.0_x64_en-US.msi index 064598c..daaabf5 100755 Binary files a/releases/YT Player_1.0.0_x64_en-US.msi and b/releases/YT Player_1.0.0_x64_en-US.msi differ diff --git a/releases/ytplayer.exe b/releases/ytplayer.exe index af70ee4..e431aca 100644 Binary files a/releases/ytplayer.exe and b/releases/ytplayer.exe differ