Now Playing: fix squashed stop button + queue persistence + crafted entrance

- Stop button fix: my round .np-btn { width/height 46px; border-radius:50% }
  override was also hitting .np-btn-stop (it carries both classes), squashing
  the 'Stop' text pill into a tiny circle. Exempted .np-btn.np-btn-stop back to
  an auto-width pill.
- Queue persistence: npPersistQueue() (called from renderNpQueue, the single
  mutation hook) saves the queue to localStorage; npRestoreQueue() on init
  repopulates the panel on reload WITHOUT auto-playing (index reset to -1).
  Queue no longer vanishes on refresh.
- Crafted entrance: controls stagger-fade/rise in when the modal opens
  (npRiseIn keyframe, delays cascading util->progress->controls->volume->
  upnext). Art container excluded so its transform stays free for the
  play-scale.

Frontend-only; Boulder verifying live.
pull/761/head
BoulderBadgeDad 3 weeks ago
parent ccfb3fb042
commit a8985b317f

@ -55,6 +55,9 @@ function initializeMediaPlayer() {
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(); });
// Restore a previously-saved queue (does not auto-play)
npRestoreQueue();
}
function toggleMediaPlayerExpansion() {
@ -2283,6 +2286,38 @@ function renderNpQueue() {
});
npUpdateUpNext();
npPersistQueue();
}
// ── Queue persistence across page reloads (localStorage) ──
const NP_QUEUE_STORAGE_KEY = 'soulsync-np-queue';
function npPersistQueue() {
try {
if (!npQueue.length) { localStorage.removeItem(NP_QUEUE_STORAGE_KEY); return; }
localStorage.setItem(NP_QUEUE_STORAGE_KEY, JSON.stringify({
queue: npQueue,
index: npQueueIndex,
savedAt: Date.now(),
}));
} catch (e) { /* quota / disabled storage — non-fatal */ }
}
// Restore the saved queue into the panel WITHOUT auto-playing (the user
// reloaded; resume playback is their choice via clicking a row).
function npRestoreQueue() {
try {
const raw = localStorage.getItem(NP_QUEUE_STORAGE_KEY);
if (!raw) return;
const data = JSON.parse(raw);
if (data && Array.isArray(data.queue) && data.queue.length) {
npQueue = data.queue;
// Don't claim a track is "playing" on a fresh load — nothing is.
npQueueIndex = -1;
renderNpQueue();
updateNpPrevNextButtons();
}
} catch (e) { /* corrupt entry — ignore */ }
}
// ── Queue drag-to-reorder ──

@ -48673,6 +48673,22 @@ textarea.enhanced-meta-field-input {
.np-btn-shuffle.active, .np-btn-repeat.active { color: rgb(var(--accent-light-rgb)) !important; }
.np-btn-shuffle.active::after, .np-btn-repeat.active::after { background: rgb(var(--accent-light-rgb)) !important; }
/* Stop is a text pill, NOT a circular transport button exempt it from the
round .np-btn sizing above (which was squashing "Stop" into a tiny circle). */
.np-btn.np-btn-stop {
width: auto !important; height: auto !important;
border-radius: 999px !important;
padding: 8px 20px !important;
background: rgba(255,255,255,0.04) !important;
border: 1px solid rgba(255,255,255,0.08) !important;
}
.np-btn.np-btn-stop:hover {
background: rgba(255,60,60,0.1) !important;
border-color: rgba(255,60,60,0.25) !important;
color: rgba(255,90,90,0.9) !important;
transform: none !important;
}
/* ── Visualizer bars: accent gradient ── */
.np-viz-bar {
background: linear-gradient(180deg, rgb(var(--accent-light-rgb)), rgba(var(--accent-rgb),0.35)) !important;
@ -48690,6 +48706,18 @@ textarea.enhanced-meta-field-input {
.np-close-btn:hover { color: #fff !important; background: rgba(255,255,255,0.1) !important; }
.np-lyrics-line.active { color: rgb(var(--accent-light-rgb)) !important; font-weight: 700 !important; }
/* Crafted entrance: controls stagger-fade in when modal opens
(Art container is intentionally excluded its transform is reserved for the
play-scale; it gets its own scale-in via the box transition.) */
.np-modal-overlay:not(.hidden) .np-track-info { animation: npRiseIn 0.55s cubic-bezier(0.16,1,0.3,1) 0.06s both; }
.np-modal-overlay:not(.hidden) .np-right > * { animation: npRiseIn 0.5s cubic-bezier(0.16,1,0.3,1) both; }
.np-modal-overlay:not(.hidden) .np-util-row { animation-delay: 0.08s; }
.np-modal-overlay:not(.hidden) .np-progress-section { animation-delay: 0.12s; }
.np-modal-overlay:not(.hidden) .np-controls-row { animation-delay: 0.16s; }
.np-modal-overlay:not(.hidden) .np-volume-row { animation-delay: 0.20s; }
.np-modal-overlay:not(.hidden) .np-upnext { animation-delay: 0.24s; }
@keyframes npRiseIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }
/* ── Click-art → music-synced visualizer takeover (Plexamp-style) ── */
.np-album-art-container { cursor: pointer; }
.np-art-hint {

Loading…
Cancel
Save