From 76ff98261b60879ae42b79fdc71fca1d31622c66 Mon Sep 17 00:00:00 2001
From: Broque Thomas <26755000+Nezreka@users.noreply.github.com>
Date: Tue, 14 Apr 2026 19:50:18 -0700
Subject: [PATCH] Move media player from sidebar to floating bottom-right mini
player
The sidebar player was a poor use of vertical real estate and created the
collapsed-state layout issues. The mini player is now a fixed 360px widget
at bottom-right (above the bell/help buttons), matching the convention of
most streaming apps.
Changes:
- Removed media player from sidebar; sidebar spacer now pushes support/version
section to bottom as before
- New .mini-player-body horizontal layout: album art | track info | controls
- Added prev/next skip buttons (mini-nav-btn) with same skip logic as the
Now Playing modal; updateNpPrevNextButtons() now syncs both sets
- .media-player.idle now display:none (widget hides entirely when no track)
- Progress bar is a flush full-width line at the top of the widget
- Volume slider kept as hidden DOM element for JS compatibility; volume is
set via the Now Playing modal
- Toast container moved up to bottom:174px to stay above the mini player
- expand-hint button updated to four-corner expand icon, opens NP modal
- All volumeSlider and click-exclusion references updated for null-safety
---
webui/index.html | 124 +++++++++++++++++++----------------------
webui/static/script.js | 35 ++++++++----
webui/static/style.css | 99 +++++++++++++++++++-------------
3 files changed, 141 insertions(+), 117 deletions(-)
diff --git a/webui/index.html b/webui/index.html
index e343363b..53ef0c3d 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -255,73 +255,6 @@
-
-
-
Support SoulSync
@@ -7455,6 +7388,63 @@
+
+
+
0
diff --git a/webui/static/script.js b/webui/static/script.js
index a68c6fb5..51f26bc7 100644
--- a/webui/static/script.js
+++ b/webui/static/script.js
@@ -3059,7 +3059,7 @@ function initializeMediaPlayer() {
// Set initial volume
audioPlayer.volume = 0.7; // 70%
- volumeSlider.value = 70;
+ if (volumeSlider) volumeSlider.value = 70;
}
// Track title click handled by initExpandedPlayer's media-player click handler
@@ -3067,7 +3067,7 @@ function initializeMediaPlayer() {
// Media controls
playButton.addEventListener('click', handlePlayPause);
stopButton.addEventListener('click', handleStop);
- volumeSlider.addEventListener('input', handleVolumeChange);
+ if (volumeSlider) volumeSlider.addEventListener('input', handleVolumeChange);
// Progress bar controls
const progressBar = document.getElementById('progress-bar');
@@ -3083,7 +3083,13 @@ function initializeMediaPlayer() {
}
// Update volume slider styling
- volumeSlider.addEventListener('input', updateVolumeSliderAppearance);
+ if (volumeSlider) volumeSlider.addEventListener('input', updateVolumeSliderAppearance);
+
+ // Mini player prev / next buttons
+ const miniPrevBtn = document.getElementById('mini-prev-btn');
+ const miniNextBtn = document.getElementById('mini-next-btn');
+ if (miniPrevBtn) miniPrevBtn.addEventListener('click', (e) => { e.stopPropagation(); playPreviousInQueue(); });
+ if (miniNextBtn) miniNextBtn.addEventListener('click', (e) => { e.stopPropagation(); playNextInQueue(); });
}
function toggleMediaPlayerExpansion() {
@@ -3325,6 +3331,7 @@ function handleProgressBarChange(event) {
function updateVolumeSliderAppearance() {
const slider = document.getElementById('volume-slider');
+ if (!slider) return;
const value = slider.value;
slider.style.setProperty('--volume-percent', `${value}%`);
}
@@ -4169,7 +4176,7 @@ function initExpandedPlayer() {
mediaPlayer.style.cursor = 'pointer';
mediaPlayer.addEventListener('click', (e) => {
// Don't open modal when clicking controls (let expand-hint through)
- if (e.target.closest('.play-button, .stop-button, .volume-slider, .volume-control, .progress-bar, .volume-icon') && !e.target.closest('.expand-hint')) return;
+ if (e.target.closest('.play-button, .stop-button, .volume-slider, .volume-control, .progress-bar, .volume-icon, .mini-nav-btn') && !e.target.closest('.expand-hint')) return;
if (currentTrack) openNowPlayingModal();
});
}
@@ -4821,16 +4828,20 @@ function renderNpQueue() {
}
function updateNpPrevNextButtons() {
+ const canPrev = npQueueIndex > 0 || (audioPlayer && audioPlayer.currentTime > 3);
+ const canNext = npQueue.length > 0 && (npShuffleOn ? npQueue.length > 1 : (npQueueIndex < npQueue.length - 1 || npRepeatMode === 'all'));
+
+ // Full Now Playing modal buttons
const prevBtn = document.getElementById('np-prev-btn');
const nextBtn = document.getElementById('np-next-btn');
- if (prevBtn) {
- const canPrev = npQueueIndex > 0 || (audioPlayer && audioPlayer.currentTime > 3);
- prevBtn.disabled = !canPrev;
- }
- if (nextBtn) {
- const canNext = npQueue.length > 0 && (npShuffleOn ? npQueue.length > 1 : (npQueueIndex < npQueue.length - 1 || npRepeatMode === 'all'));
- nextBtn.disabled = !canNext;
- }
+ if (prevBtn) prevBtn.disabled = !canPrev;
+ if (nextBtn) nextBtn.disabled = !canNext;
+
+ // Mini player buttons
+ const miniPrevBtn = document.getElementById('mini-prev-btn');
+ const miniNextBtn = document.getElementById('mini-next-btn');
+ if (miniPrevBtn) miniPrevBtn.disabled = !canPrev;
+ if (miniNextBtn) miniNextBtn.disabled = !canNext;
}
function handlePlayerKeyboardShortcuts(event) {
diff --git a/webui/static/style.css b/webui/static/style.css
index f1a2a2cd..94f8863a 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -506,67 +506,90 @@ body {
);
}
-/* Media Player Section - Premium Compact Design */
+/* ── Floating Mini Media Player — fixed bottom-right ─────────────────────── */
.media-player {
+ position: fixed;
+ bottom: 88px;
+ right: 16px;
+ width: 360px;
background: linear-gradient(180deg,
rgba(24, 24, 24, 0.97) 0%,
rgba(16, 16, 16, 0.99) 50%,
rgba(10, 10, 10, 1.0) 100%);
- backdrop-filter: blur(16px) saturate(1.2);
- border: 1px solid rgba(255, 255, 255, 0.06);
- border-top: 1px solid rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(20px) saturate(1.3);
+ border: 1px solid rgba(255, 255, 255, 0.07);
+ border-top: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 14px;
- margin: 8px 14px;
- padding: 0;
overflow: hidden;
- transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
+ transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.25s ease;
box-shadow:
- 0 8px 24px rgba(0, 0, 0, 0.45),
- 0 2px 8px rgba(0, 0, 0, 0.3),
+ 0 12px 40px rgba(0, 0, 0, 0.55),
+ 0 4px 12px rgba(0, 0, 0, 0.35),
inset 0 1px 0 rgba(255, 255, 255, 0.07);
- position: relative;
- min-height: 120px;
- flex-shrink: 0;
+ z-index: 99998;
}
.media-player:hover {
- border-color: rgba(var(--accent-rgb), 0.18);
- border-top-color: rgba(var(--accent-rgb), 0.22);
+ border-color: rgba(var(--accent-rgb), 0.2);
+ border-top-color: rgba(var(--accent-rgb), 0.25);
box-shadow:
- 0 10px 28px rgba(0, 0, 0, 0.5),
- 0 4px 14px rgba(var(--accent-rgb), 0.1),
+ 0 16px 48px rgba(0, 0, 0, 0.6),
+ 0 4px 16px rgba(var(--accent-rgb), 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
-/* Compact idle state — collapsed when nothing is playing */
+/* Hidden when nothing is playing — widget disappears entirely */
.media-player.idle {
- min-height: 0;
- padding: 0;
- margin: 4px 14px;
-}
-
-.media-player.idle .player-top-progress,
-.media-player.idle .media-header,
-.media-player.idle .media-controls-row,
-.media-player.idle .media-expanded {
display: none;
}
-.media-player.idle .no-track-message {
- padding: 10px 14px;
- font-size: 0.75rem;
- gap: 8px;
+/* Mini player body — horizontal: art | info | controls */
+.mini-player-body {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px 10px;
+ cursor: pointer;
}
-.media-player.idle .no-track-message svg {
- width: 18px;
- height: 18px;
+/* Compact prev/next nav buttons */
+.mini-nav-btn {
+ width: 26px;
+ height: 26px;
+ border-radius: 50%;
+ background: transparent;
+ border: none;
+ color: rgba(255, 255, 255, 0.45);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.18s ease, background 0.18s ease;
+ padding: 0;
+ flex-shrink: 0;
}
-/* Top progress bar - thin line across full width */
+.mini-nav-btn:hover:not(:disabled) {
+ color: rgba(255, 255, 255, 0.9);
+ background: rgba(255, 255, 255, 0.08);
+}
+
+.mini-nav-btn:disabled {
+ opacity: 0.22;
+ cursor: not-allowed;
+}
+
+/* Controls cluster in the mini player */
+.mini-player-controls {
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ flex-shrink: 0;
+}
+
+/* Top progress bar - flush full-width line at the very top of the mini player */
.player-top-progress {
- padding: 0 14px;
- padding-top: 10px;
+ padding: 0;
}
.player-top-progress .progress-bar-container {
@@ -5034,10 +5057,10 @@ body.helper-mode-active #dashboard-activity-feed:hover {
NOTIFICATION SYSTEM — Compact toasts + bell button + notification panel
================================================================================== */
-/* Toast container — bottom-right, above the bell/helper buttons */
+/* Toast container — bottom-right, above the mini player */
.toast-container {
position: fixed;
- bottom: 84px;
+ bottom: 174px;
right: 24px;
z-index: 99999;
pointer-events: none;