feat(player): docked queue overlay with reorder, remove, jump, and clear-upcoming

Add a Queue toggle to the docked player bar opening a centered editable queue
overlay. New additive QueueService.ClearUpcoming keeps the playing track while
dropping the rest. Current track is non-removable.
This commit is contained in:
daniel-c-harvey
2026-06-19 15:18:25 -04:00
parent 4317a2f9e7
commit fe3819f378
10 changed files with 413 additions and 5 deletions
@@ -750,3 +750,158 @@ body:has(.waveform-visualizer-control-overlay) {
color: var(--mud-palette-text-primary);
opacity: 0.85;
}
/* =============================================================================
QUEUE OVERLAY + LIST (Phase 17 wave 17.2 — docked queue panel)
The overlay is a direct lift of the visualizer-control modal (Phase 15 §4): a centered MudOverlay
whose scrim tint + z-index + body-scroll lock match that idiom exactly. The panel chrome (square
corners, lighter-navy ground, thin light border) is the NowPlayingCard treatment (§5). MudOverlay
portals out of the component subtree to the body, so these are plain GLOBAL rules — CSS isolation
cannot reach portaled content.
============================================================================= */
/* Raise the overlay above the sticky header (100), the fixed player dock (1200), and the minimized
FAB (1300) — same stacking decision as the visualizer overlay so the scrim tints the whole viewport. */
.deepdrft-queue-overlay {
z-index: 1400 !important;
}
/* Mild modal tint from the shared scrim token. The doubled selector (0,2,0) outranks MudBlazor's own
.mud-overlay-dark (0,1,0) regardless of stylesheet load order. */
.deepdrft-queue-overlay .mud-overlay-scrim.mud-overlay-dark {
background-color: rgba(var(--deepdrft-scrim-rgb), var(--deepdrft-modal-scrim-alpha));
}
.deepdrft-queue-overlay .mud-overlay-content {
max-height: 90vh;
overflow: visible;
}
/* Lock body scroll while the queue overlay is open (matches the visualizer overlay). */
body:has(.deepdrft-queue-overlay) {
overflow: hidden;
}
/* The mostly-square panel (§3.2: min(90vw, 520px)). NowPlayingCard chrome: square corners, lighter-navy
ground, thin light border. Internal column: fixed header over a scrollable list body. */
.deepdrft-queue-modal {
display: flex;
flex-direction: column;
width: min(90vw, 520px);
height: min(90vw, 520px);
max-height: 90vh;
background: var(--deepdrft-panel-ground);
border: 1px solid var(--deepdrft-border-light);
border-radius: 0;
backdrop-filter: blur(8px);
overflow: hidden;
}
.deepdrft-queue-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.85rem 1rem;
border-bottom: 1px solid var(--deepdrft-border-light);
}
/* Mono uppercase eyebrow — the NowPlayingCard .np-label typography, recoloured light (static). */
.deepdrft-queue-modal-title {
font-family: var(--deepdrft-font-mono);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--deepdrft-white);
opacity: 0.85;
}
.deepdrft-queue-modal-body {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
padding: 0.5rem 0.5rem 0.75rem;
}
/* ── The list itself (consumed by QueueList in both modes; styled here once). ── */
.deepdrft-queue-list {
display: flex;
flex-direction: column;
}
.deepdrft-queue-zone {
display: flex;
flex-direction: column;
}
.deepdrft-queue-row {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.45rem 0.5rem;
border-radius: 4px;
color: var(--deepdrft-white);
transition: background 0.15s ease;
}
.deepdrft-queue-row:hover {
background: color-mix(in srgb, var(--deepdrft-white) 6%, transparent);
}
/* Current track: a subtle green wash + left accent, matching the green = active principle. */
.deepdrft-queue-row-current {
background: color-mix(in srgb, var(--deepdrft-green-accent) 14%, transparent);
box-shadow: inset 2px 0 0 0 var(--deepdrft-green-accent);
}
.deepdrft-queue-drag-handle {
cursor: grab;
opacity: 0.45;
flex: 0 0 auto;
}
.deepdrft-queue-position {
font-family: var(--deepdrft-font-mono);
font-size: 0.72rem;
opacity: 0.6;
min-width: 1.4rem;
text-align: right;
flex: 0 0 auto;
}
/* Row body grows + truncates; clicking it jumps playback (OQ2). */
.deepdrft-queue-body {
display: flex;
flex-direction: column;
gap: 0.1rem;
flex: 1 1 auto;
min-width: 0;
cursor: pointer;
}
.deepdrft-queue-title {
font-size: 0.92rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.deepdrft-queue-artist {
font-size: 0.74rem;
opacity: 0.6;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.deepdrft-queue-nowplaying,
.deepdrft-queue-remove {
flex: 0 0 auto;
}
/* Active (open) state for the bar's Queue toggle — a soft green chip behind the glyph, matching the
visualizer toggle's on-state idiom. */
.deepdrft-queue-toggle-active {
background: color-mix(in srgb, var(--deepdrft-green-accent) 22%, transparent);
border-radius: 6px;
}