+
AudioQueue Test
+
+ Loaded {testSongs.length} songs from API
+
+
+
+ Current Song: {state.currentSong?.title || "None"}
+
+
+
+ Status: {state.isPlaying ? "Playing" : "Paused"}
+ {state.isLoading && " (Loading...)"}
+
+
+
+ Progress: {formatTime(state.progress)} /{" "}
+ {formatTime(state.duration)}
+
+
+
+ Queue: {state.queue.length} songs
+
+
+
+ Current Index: {state.currentIndex}
+
+
+
+ Navigation:
+ Previous: {state.hasPreviousSong ? "Available" : "Disabled"}
+ {state.progress > 3 && " (→ Start)"} | Next:{" "}
+ {state.hasNextSong ? "Available" : "Disabled"}
+
+
+
+ Volume: {Math.round(state.volume * 100)}%
+
+
+
+ Repeat Mode: {state.repeatMode}
+
+
+
+ Shuffle: {state.isShuffled ? "On" : "Off"}
+
+
+ {state.error && (
+
+ Error: {state.error}
+
+ )}
+
+
+
Playback :
+
+
+
+
+
+
+
+
+
+
+
+
Queue :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Persistence:
+
+
+
+
+
+
+
Volume:
+ actions.setVolume(parseInt(e.target.value) / 100)}
+ />
+
+
+
+
Seek:
+ {
+ const seekTime = (state.duration * parseInt(e.target.value)) / 100;
+ actions.seek(seekTime);
+ }}
+ disabled={!state.currentSong}
+ />
+
+
+
+
Test Songs:
+
+ {testSongs.map((song) => (
+ -
+ {song.title}
+
+
+ ))}
+
+
+
+
+
Current Queue:
+
+ {state.queue.map((item, index) => (
+ -
+ {item.song.title}
+ {item.isQueued && " (User Queued)"}
+ {item.originalIndex !== undefined &&
+ ` [Orig: ${item.originalIndex}]`}
+ {index === state.currentIndex && " ← Current"}
+
+ ))}
+
+
+
+ );
+};
+
+export default AudioQueueTest;
diff --git a/client/src/components/CoverLightbox/CoverLightbox.module.css b/client/src/components/CoverLightbox/CoverLightbox.module.css
new file mode 100644
index 0000000..3875feb
--- /dev/null
+++ b/client/src/components/CoverLightbox/CoverLightbox.module.css
@@ -0,0 +1,73 @@
+.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.6);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ animation: fadeIn var(--transition-speed) ease-out;
+ cursor: pointer;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.lightbox {
+ position: relative;
+ animation: slideIn var(--transition-speed) ease-out;
+ cursor: default;
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95) translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+ }
+}
+
+.closeButton {
+ position: absolute;
+ top: 1.6rem;
+ right: 1.6rem;
+ background: var(--color-panel-gray);
+ border: 1px solid var(--color-panel-border);
+ border-radius: var(--border-radius-sm);
+ width: 3.2rem;
+ height: 3.2rem;
+ color: var(--color-white);
+ font-size: 1.8rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all var(--transition-speed) ease;
+ backdrop-filter: blur(4px);
+}
+
+.closeButton:hover {
+ background: var(--color-panel-border);
+}
+
+.coverImage {
+ width: 75rem;
+ height: 75rem;
+ object-fit: contain;
+ border-radius: var(--border-radius-sm);
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5),
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+}
diff --git a/client/src/components/CoverLightbox/CoverLightbox.tsx b/client/src/components/CoverLightbox/CoverLightbox.tsx
new file mode 100644
index 0000000..dd1e28d
--- /dev/null
+++ b/client/src/components/CoverLightbox/CoverLightbox.tsx
@@ -0,0 +1,50 @@
+import { memo, useEffect } from "react";
+import { LuX } from "react-icons/lu";
+import styles from "./CoverLightbox.module.css";
+
+interface CoverLightboxProps {
+ isOpen: boolean;
+ onClose: () => void;
+ imageUrl: string;
+ altText: string;
+}
+
+const CoverLightbox: React.FC