Icon-only playlist header and filter-row actions, one-row transport, resumable update download

- Playlist title wraps within 90% of the width; rename and share are icons beside it,
  and tapping the title opens the rename box showing the full name
- Offline, pin, select and delete are icon buttons to the right of the filter input
- Transport is one row again: prev, back 10, play, forward 10, next
- Update download stages each file as it arrives so a retry only fetches what is
  missing, keeps 4 requests in flight and allows 5 minutes
This commit is contained in:
Jonathan Sykes
2026-10-02 20:59:51 +08:00
parent 236c129b90
commit 985063648f
6 changed files with 266 additions and 54 deletions

View File

@@ -551,3 +551,50 @@
.search-meta.hidden { display: none; }
.search-meta .sm-btn { flex: none; border: 1px solid var(--line); background: transparent; color: var(--text); border-radius: 999px; padding: 6px 12px; font: inherit; font-weight: 600; cursor: pointer; }
.search-meta .sm-btn:disabled { opacity: .55; cursor: default; }
/* ---- Playlist header: wrapping title (≤90%) + icon actions; icon row beside the filter ---- */
.list-title-row { display: flex; align-items: flex-start; gap: 6px; min-width: 0; }
.list-title-row h2 { flex: 0 1 auto; max-width: 90%; min-width: 0; overflow-wrap: anywhere; }
.list-title-row h2.title-clickable { cursor: pointer; }
.list-title-row h2.title-clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
.list-title-actions { flex: 0 0 auto; display: flex; flex-direction: column; gap: 4px; margin-left: auto; }
.list-title-actions:empty { display: none; }
.list-filter-bar { display: flex; align-items: center; gap: 6px; }
.list-filter-bar input { flex: 1 1 auto; min-width: 0; width: auto; }
.list-filter-actions { flex: 0 0 auto; display: flex; gap: 6px; }
.list-filter-actions:empty { display: none; }
.list-icon-btn {
flex: none; width: 40px; height: 40px; padding: 0;
display: grid; place-items: center;
border-radius: 12px; border: 1px solid var(--line);
background: var(--bg-2); color: var(--text-2); cursor: pointer;
transition: color 0.16s, border-color 0.16s, background 0.16s;
}
.list-title-actions .list-icon-btn { width: 34px; height: 34px; border-radius: 10px; }
.list-icon-btn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.list-icon-btn:hover { color: var(--text); border-color: var(--accent); }
.list-icon-btn.pinned { color: var(--accent-bright); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.list-icon-btn.done { color: #3ecf8e; border-color: color-mix(in srgb, #3ecf8e 55%, transparent); opacity: 1; }
.list-icon-btn.busy { width: auto; min-width: 40px; padding: 0 8px; }
.list-icon-btn .lib-progress { font: 700 11px var(--mono); color: var(--accent-bright); }
.list-icon-btn.danger:hover { color: #ff5a4f; border-color: #ff5a4f; }
.list-icon-btn:disabled, .list-icon-btn.disabled { cursor: default; pointer-events: none; }
.list-icon-btn.disabled:not(.done):not(.busy) { opacity: 0.45; }
/* ---- Transport: one row — prev · −10 · play · +10 · next ---- */
#controls .deck-main { grid-template-columns: repeat(5, auto); gap: 14px; }
#controls .deck-main #prevBtn { order: 1; }
#controls .deck-main #back10Btn { order: 2; }
#controls .deck-main #playBtn { order: 3; }
#controls .deck-main #fwd10Btn { order: 4; }
#controls .deck-main #nextBtn { order: 5; }
@media (max-width: 400px) { #controls .deck-main { gap: 6px; } }
/* Rename modal: wrapping box so a long playlist name shows in full */
.modal textarea.rename-input {
display: block; width: 100%; resize: none; overflow: hidden;
background: var(--bg-3); border: 1px solid var(--line); border-radius: 13px;
padding: 13px 15px; color: var(--text); font: 600 16px/1.35 var(--ui);
outline: none; overflow-wrap: anywhere; transition: border-color 0.16s, box-shadow 0.16s;
}
.modal textarea.rename-input:focus { border-color: var(--accent); box-shadow: 0 0 0 4px rgba(255,75,50,0.12); }