A clean, modern music player with smooth transitions, keyboard controls, and a premium UX feel.
Built with:
- React
- Bun
- TailwindCSS
- Framer Motion
- shadcn/ui
this is my playlist!
- Play / Pause / Next / Previous
- Multiple collections (fetched from
/public/metadata.json) - Shuffle / Loop / Loop One modes

- Fade-in on track change
- Fade-out on pause
- Prevents harsh audio cuts → gives premium feel
| Key | Action |
|---|---|
Space |
Play / Pause |
, |
Previous track |
. |
Next track |
L |
Change mode |
- Vertical hover slider (top-right)
- Real-time volume updates
- Does NOT restart track (fixed behavior)
- Blurred dynamic background from track cover
- Responsive layout (desktop + mobile)
- Square normalized album art (no layout breaking)
- Smooth transitions using Framer Motion
project-root/
│
├── public/
│ ├── metadata.json # collections + tracks
│ ├── cover/ # album covers
│ └── track/ # audio files
│
├── src/
│ ├── components/
│ │ └── ui/ # shadcn components
│ │
│ ├── lib/
│ │ └── utils.ts # helpers (formatTime, types)
│ │
│ ├── App.tsx # main player logic (everything here)
│ └── main.tsx
│
└── README.md
[
{
"name": "Collection Name",
"cover": "/cover/example.jpg",
"tracks": [
{
"title": "Track Name",
"artist": "Artist",
"src": "/track/file.mp3",
"cover": "/cover/track.jpg"
}
]
}
]- Central
<audio>element controlled viauseRef - All playback logic interacts directly with it
useEffect(() => {
audio.src = currentTrack.src;
audio.volume = 0;
audio.play();
// gradual volume increase
}, [currentIndex, active]);👉 Runs only when track changes
👉 Prevents restart issues by NOT depending on volume
togglePlay();- Gradually reduces volume
- Pauses only after volume reaches ~0
useEffect(() => {
audioRef.current.volume = volume;
}, [volume]);👉 Decoupled from playback logic 👉 Fixes restart bug
window.addEventListener("keydown", ...)- Global listener
- Only active when a collection is selected
- Top bar → collections + volume
- Center → album + metadata
- Bottom → controls + progress
Everything is locked to h-screen → no scrolling.
/public/screenshots/
Example:
/public/screenshots/player.png
/public/screenshots/collections.png
## 📸 Screenshots
### Player View

### Collections View
- Do NOT include
volumein track-change effect deps → will restart audio - Always clean intervals in fade logic
- Audio state must stay single source of truth (
audioRef)
bun install
bun run dev- Crossfade between tracks 🔥
- Persist last played track
- Media Session API (lock screen controls)
- Playlist editing UI
This project focuses on:
- minimal UI
- smooth UX
- no unnecessary abstractions
Everything is kept inside App.tsx intentionally for clarity and control.
MIT