From e64880c1639552612c76232c2eb1823127b18aca Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 15:29:44 +0000 Subject: [PATCH] Redesign playlist header, player deck, now-playing actions and lyrics panel layout --- frontend/app.js | 4 + frontend/index.html | 87 +++++++------ frontend/refresh.css | 299 +++++++++++++++++++++++++++++++++++++++++++ frontend/sw.js | 1 + 4 files changed, 351 insertions(+), 40 deletions(-) create mode 100644 frontend/refresh.css diff --git a/frontend/app.js b/frontend/app.js index 68628de..090ec72 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -7209,9 +7209,11 @@ function renderList() { if (pl) { const playAll = document.createElement('button'); playAll.textContent = '▶ Play all'; + playAll.className = 'la-primary'; playAll.onclick = () => { if (pl.videos.length) playFromList(pl.videos, 0, 'playlist:' + pl.id); }; const playFull = document.createElement('button'); playFull.textContent = '▶ Play in full'; + playFull.className = 'la-main'; playFull.title = 'Play every video start to finish — ignores saved positions and A-B loops, stops after the last one'; playFull.onclick = () => { if (!pl.videos.length) return; @@ -7220,6 +7222,7 @@ function renderList() { }; const serviceBtn = document.createElement('button'); serviceBtn.textContent = '🎤 Service mode'; + serviceBtn.className = 'la-main'; serviceBtn.title = 'Set-list view with screen wake lock'; serviceBtn.onclick = () => { if (!pl.videos || !pl.videos.length) { toast('Playlist is empty'); return; } @@ -7227,6 +7230,7 @@ function renderList() { }; const queueAll = document.createElement('button'); queueAll.textContent = '+ Queue'; + queueAll.className = 'la-main'; queueAll.onclick = () => { pl.videos.forEach((v) => addToQueue(v, { quiet: true })); toast('Added playlist to queue'); }; const rename = document.createElement('button'); rename.textContent = 'Rename'; diff --git a/frontend/index.html b/frontend/index.html index beb4aee..69c1f0d 100755 --- a/frontend/index.html +++ b/frontend/index.html @@ -22,6 +22,7 @@ +
@@ -169,47 +170,53 @@ 0:00
-
- - - - - - - - -
- - +
+ + + + + +
+
+ + +
+
+
+
Practice
+
+ + + + + + +
+
+
+
Sound & view
+
+ + + + + + +
- -
- - - - - - - - - - - - -
diff --git a/frontend/refresh.css b/frontend/refresh.css new file mode 100644 index 0000000..8445224 --- /dev/null +++ b/frontend/refresh.css @@ -0,0 +1,299 @@ +/* ============================================================================ + * Layout refresh: playlist header, player deck, now-playing actions, lyrics panel + * + * Loaded after styles.css. Uses #id-prefixed selectors so it wins over the + * older per-breakpoint rules without needing !important. The theme tokens + * (--bg-*, --accent*, --line, --text*) are reused so light / contrast themes + * keep working. + * ========================================================================== */ + +/* ---- 1. Playlist / list header: title on top, ONE primary action, tidy rest --- */ +.list-header { + flex-direction: column; + align-items: stretch; + gap: 14px; +} +.list-header h2 { + font-size: clamp(26px, 6.4vw, 34px); + line-height: 1.05; + letter-spacing: -0.03em; + overflow-wrap: anywhere; +} +#listActions { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + gap: 8px; +} +#listActions button { + flex: 0 0 auto; + height: 36px; + padding: 0 14px; + border-radius: 999px; + font-size: 13px; + white-space: nowrap; + display: inline-flex; align-items: center; gap: 6px; +} +#listActions button.la-primary { + flex: 1 1 100%; + height: 54px; + border: 0; + border-radius: 18px; + font-size: 16px; + font-weight: 800; + letter-spacing: 0.01em; + justify-content: center; + color: #fff; + background: linear-gradient(145deg, var(--accent-bright), var(--accent-deep)); + box-shadow: 0 16px 30px -14px var(--accent-glow); +} +#listActions button.la-primary:hover { color: #fff; filter: brightness(1.08); } +#listActions button.la-main { + flex: 1 1 calc(33.3% - 8px); + min-width: 0; + height: 46px; + border-radius: 14px; + justify-content: center; + color: var(--text); + background: var(--bg-3); + overflow: hidden; text-overflow: ellipsis; +} +/* Secondary actions sit on a quiet second line, visually below the main three. */ +#listActions button.la-main + button:not(.la-main) { margin-left: 0; } +#listActions button:not(.la-primary):not(.la-main) { + background: transparent; + color: var(--text-dim); + font-weight: 600; +} +#listActions button:not(.la-primary):not(.la-main):hover { color: var(--text); background: var(--bg-2); } +#listActions button.pinned { color: var(--accent-bright); border-color: var(--accent); background: transparent; } +#listActions button.la-main { padding: 0 6px; font-size: 13px; } +@media (min-width: 900px) { + #listActions button.la-primary { flex: 0 0 auto; height: 44px; padding: 0 26px; border-radius: 14px; } + #listActions button.la-main { flex: 0 0 auto; height: 44px; padding: 0 18px; } +} + +/* ---- 2. Player deck ------------------------------------------------------- */ +#controls { + padding: 18px 16px 16px; + border-radius: 24px; + display: flex; + flex-direction: column; + gap: 14px; +} +#controls .seek-row { gap: 10px; } +#controls .time { min-width: 40px; font-size: 12px; color: var(--text-dim); } + +#controls .deck-main { + display: grid; + grid-template-columns: 1fr 1fr 1.5fr 1fr 1fr; + align-items: center; + justify-items: center; + gap: 4px; + margin: 2px 0 0; +} +#controls .deck-main .ctrl { + width: 48px; height: 48px; min-width: 0; + border-radius: 50%; + border-color: transparent; + background: transparent; + font-size: 19px; +} +#controls .deck-main .ctrl:hover { background: var(--bg-3); } +#controls .deck-main .ctrl.active { color: var(--accent-bright); background: color-mix(in srgb, var(--accent) 16%, transparent); } +#controls .deck-main .ctrl.play { + width: 74px; height: 74px; + font-size: 26px; + background: linear-gradient(145deg, var(--accent-bright), var(--accent-deep)); + box-shadow: 0 18px 34px -12px var(--accent-glow), inset 0 1px 0 rgba(255, 255, 255, 0.25); +} + +#controls .deck-vol { + display: flex; align-items: center; gap: 10px; + padding: 0 4px; +} +#controls .deck-vol .ctrl { width: 38px; height: 38px; min-width: 0; border-radius: 50%; background: transparent; border-color: transparent; font-size: 17px; } +#controls .deck-vol input[type="range"] { flex: 1; width: auto; min-width: 0; } + +#controls .deck-groups { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} +#controls .dk-group { + min-width: 0; + padding: 10px 10px 10px; + border-radius: 18px; + background: color-mix(in srgb, var(--bg) 55%, transparent); + border: 1px solid var(--line-soft, var(--line)); +} +#controls .dk-title { + margin: 0 2px 8px; + font: 700 10px/1 var(--mono); + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-dim); +} +#controls .dk-tiles { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6px; +} +#controls .dk-tiles .ctrl { + width: 100%; min-width: 0; height: 44px; + border-radius: 13px; + font-size: 16px; +} +#controls .dk-tiles .ctrl.hidden { display: none; } +#controls .dk-sel { + grid-column: span 3; + display: flex; align-items: center; justify-content: space-between; gap: 8px; + height: 40px; padding: 0 4px 0 10px; + border-radius: 13px; + background: var(--bg-3); + border: 1px solid var(--line); +} +#controls .dk-sel select { + flex: 1; min-width: 0; + height: 34px; + border: 0; background: transparent; + text-align: right; text-align-last: right; + font-size: 14px; font-weight: 700; + padding: 0 4px; +} +@media (max-width: 480px) { + #controls .deck-groups { grid-template-columns: 1fr; } + #controls .dk-tiles { grid-template-columns: repeat(auto-fit, minmax(52px, 1fr)); } + #controls .dk-sel { grid-column: 1 / -1; } + #controls .deck-main .ctrl { width: 44px; height: 44px; } + #controls .deck-main .ctrl.play { width: 70px; height: 70px; } +} + +/* ---- 3. Now playing: name first, actions as clear tiles, extras as chips ---- */ +#nowPlayingMeta { + flex-direction: column; + align-items: stretch; + gap: 14px; + margin-top: 22px; +} +#nowPlayingMeta .np-title { + font-size: clamp(22px, 5.6vw, 28px); + line-height: 1.15; + letter-spacing: -0.025em; +} +#nowPlayingMeta .np-channel { margin-top: 4px; } +#nowPlayingMeta .np-actions { + display: flex; flex-wrap: wrap; gap: 8px; + max-width: none; +} +#nowPlayingMeta .np-actions .np-btn { + flex: 0 0 auto; + height: 38px; padding: 0 14px; + border-radius: 999px; + background: transparent; + color: var(--text-2); + font-size: 13px; + display: inline-flex; align-items: center; gap: 6px; +} +/* The three everyday actions are big equal tiles on their own line. */ +#nowPlayingMeta .np-actions #queueBtn, +#nowPlayingMeta .np-actions #saveBtn, +#nowPlayingMeta .np-actions #addPlaylistBtn { + order: -1; + flex: 1 1 calc(33.3% - 8px); + height: 52px; + justify-content: center; + border-radius: 16px; + color: var(--text); + background: var(--bg-3); + font-weight: 700; +} +#nowPlayingMeta .np-actions #saveBtn.done { + color: #7ee0a8; + background: rgba(126, 224, 168, 0.1); +} +#nowPlayingMeta .np-actions #brokenBtn { color: var(--text-dim); } + +/* ---- 4. Lyrics panel ------------------------------------------------------- */ +.notes-panel { border-radius: 24px; } +.notes-tabs { + gap: 6px; + padding: 10px 12px; + border-bottom: 0; +} +.notes-tab { + border: 0; + border-radius: 999px; + padding: 9px 14px; + letter-spacing: 0.1em; + background: transparent; +} +.notes-tab.active { + color: #fff; + background: color-mix(in srgb, var(--accent) 30%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent); +} +.notes-pane { padding: 4px 14px 16px; } + +#lyricsPane .lyrics-toolbar { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + margin-bottom: 14px; +} +#lyricsPane .lyrics-toolbar .np-btn { + height: 42px; padding: 0 8px; + border-radius: 13px; + justify-content: center; + font-size: 13px; + background: var(--bg-3); + color: var(--text); + display: inline-flex; align-items: center; gap: 6px; +} +#lyricsPane .lyrics-toolbar .notes-spacer { display: none; } +#lyricsPane .lyrics-toolbar .lyr-offset { + grid-column: 1 / 3; + height: 42px; + justify-content: space-between; + border-radius: 13px; padding: 3px; +} +#lyricsPane .lyr-off-btn { flex: none; height: 34px; padding: 0 12px; font-size: 12px; background: var(--bg-3); } +#lyricsPane .lyr-off-val { flex: 1; font-size: 13px; } +#lyricsPane .lyr-auto { + grid-column: 3; + height: 42px; justify-content: center; + border-radius: 13px; border: 1px solid var(--line); + font-size: 12px; font-weight: 600; +} +.lyr-auto input { accent-color: var(--accent); width: 16px; height: 16px; } + +.lyrics-list { padding-top: 6px; } +.lyr-line { + padding: 10px 12px; + border-radius: 14px; + gap: 14px; + align-items: center; +} +.lyr-time { + width: auto; min-width: 34px; + padding: 3px 7px; + border-radius: 8px; + background: var(--bg-1); + text-align: center; +} +.lyr-text { font-size: 16px; line-height: 1.4; } +.lyr-line.active { + background: color-mix(in srgb, var(--accent) 14%, var(--bg-3)); + box-shadow: inset 3px 0 0 var(--accent); +} +.lyr-line.active .lyr-text { font-size: 18px; } +.lyr-section { margin: 20px 0 6px 12px; } + +@media (max-width: 380px) { + #lyricsPane .lyrics-toolbar .np-btn { font-size: 12px; } + .notes-tab { padding: 8px 10px; letter-spacing: 0.06em; } +} + +/* Cards in the player column never shrink below their content. */ +#controls, #nowPlayingMeta, #notesPanel { flex-shrink: 0; } +#lyricsPane .lyr-auto { font-size: 11.5px; gap: 5px; padding: 0 4px; white-space: nowrap; } diff --git a/frontend/sw.js b/frontend/sw.js index 680a704..6fbf4aa 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -57,6 +57,7 @@ const SHELL = [ '/index.html', '/styles.css', '/service-ui.css', + '/refresh.css', '/flag-ui.js', '/async-guard.js', '/sw-update.js',