Spaces:
Running
Running
Давай сделаем красивый музыкальный плеер на HTMX и Tailwind визуально похожий на Spotify. Что нужно:
Browse files1) возможность выбрать папку с музыкой
2) треки должны отображаться плейлистом
3) их порядок можно менять мышкой
4) не нужно запускать трек при смене его порядка мышкой
5) переключение на следующий или предыдущий трек
6) регулировка громкости
- README.md +8 -5
- index.html +383 -18
- script.js +326 -0
- style.css +126 -18
README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: SoundWave HTMX 🎶
|
| 3 |
+
colorFrom: purple
|
| 4 |
+
colorTo: red
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
index.html
CHANGED
|
@@ -1,19 +1,384 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
+
|
| 2 |
+
<!DOCTYPE html>
|
| 3 |
+
<html lang="en">
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>SoundWave HTMX 🎶 - Spotify-like Music Player</title>
|
| 8 |
+
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 9 |
+
<link rel="stylesheet" href="style.css">
|
| 10 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 11 |
+
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
| 12 |
+
<script src="https://unpkg.com/htmx.org/dist/ext/sse.js"></script>
|
| 13 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 14 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 15 |
+
<style>
|
| 16 |
+
:root {
|
| 17 |
+
--primary-color: #1DB954;
|
| 18 |
+
--secondary-color: #191414;
|
| 19 |
+
--background-color: #121212;
|
| 20 |
+
--sidebar-color: #000000;
|
| 21 |
+
--player-color: #181818;
|
| 22 |
+
--card-bg: rgba(255, 255, 255, 0.05);
|
| 23 |
+
--card-hover: rgba(255, 255, 255, 0.1);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
body {
|
| 27 |
+
background: linear-gradient(135deg, #121212 0%, #1a1a1a 100%);
|
| 28 |
+
color: white;
|
| 29 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 30 |
+
height: 100vh;
|
| 31 |
+
overflow: hidden;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.glass-card {
|
| 35 |
+
background: var(--card-bg);
|
| 36 |
+
backdrop-filter: blur(10px);
|
| 37 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 38 |
+
border-radius: 12px;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.track-item {
|
| 42 |
+
transition: all 0.3s ease;
|
| 43 |
+
border-radius: 8px;
|
| 44 |
+
margin: 2px 0;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.track-item:hover {
|
| 48 |
+
background: var(--card-hover);
|
| 49 |
+
transform: translateY(-1px);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.track-item.active {
|
| 53 |
+
background: linear-gradient(90deg, rgba(29, 185, 84, 0.2), transparent);
|
| 54 |
+
border-left: 3px solid var(--primary-color);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.track-item.dragging {
|
| 58 |
+
opacity: 0.8;
|
| 59 |
+
background: rgba(29, 185, 84, 0.3);
|
| 60 |
+
transform: rotate(2deg);
|
| 61 |
+
z-index: 1000;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.track-item.drag-over {
|
| 65 |
+
border-top: 2px solid var(--primary-color);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.progress-bar {
|
| 69 |
+
background: rgba(255, 255, 255, 0.2);
|
| 70 |
+
border-radius: 4px;
|
| 71 |
+
height: 4px;
|
| 72 |
+
outline: none;
|
| 73 |
+
appearance: none;
|
| 74 |
+
cursor: pointer;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.progress-bar::-webkit-slider-thumb {
|
| 78 |
+
appearance: none;
|
| 79 |
+
width: 12px;
|
| 80 |
+
height: 12px;
|
| 81 |
+
border-radius: 50%;
|
| 82 |
+
background: var(--primary-color);
|
| 83 |
+
cursor: pointer;
|
| 84 |
+
opacity: 0;
|
| 85 |
+
transition: opacity 0.3s;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.progress-bar:hover::-webkit-slider-thumb {
|
| 89 |
+
opacity: 1;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.volume-slider {
|
| 93 |
+
background: rgba(255, 255, 255, 0.2);
|
| 94 |
+
border-radius: 4px;
|
| 95 |
+
height: 4px;
|
| 96 |
+
outline: none;
|
| 97 |
+
appearance: none;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.volume-slider::-webkit-slider-thumb {
|
| 101 |
+
appearance: none;
|
| 102 |
+
width: 14px;
|
| 103 |
+
height: 14px;
|
| 104 |
+
border-radius: 50%;
|
| 105 |
+
background: white;
|
| 106 |
+
cursor: pointer;
|
| 107 |
+
border: 2px solid var(--primary-color);
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.sidebar-gradient {
|
| 111 |
+
background: linear-gradient(180deg, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.9));
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.player-gradient {
|
| 115 |
+
background: linear-gradient(90deg, rgba(24, 24, 24, 0.95), rgba(18, 18, 18, 0.95));
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.button-spotify {
|
| 119 |
+
background: var(--primary-color);
|
| 120 |
+
transition: all 0.3s ease;
|
| 121 |
+
box-shadow: 0 4px 15px rgba(29, 185, 84, 0.3);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.button-spotify:hover {
|
| 125 |
+
background: #1ed760;
|
| 126 |
+
transform: translateY(-2px);
|
| 127 |
+
box-shadow: 0 6px 20px rgba(29, 185, 84, 0.4);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.scrollbar-custom::-webkit-scrollbar {
|
| 131 |
+
width: 8px;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.scrollbar-custom::-webkit-scrollbar-track {
|
| 135 |
+
background: rgba(255, 255, 255, 0.05);
|
| 136 |
+
border-radius: 4px;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.scrollbar-custom::-webkit-scrollbar-thumb {
|
| 140 |
+
background: rgba(255, 255, 255, 0.2);
|
| 141 |
+
border-radius: 4px;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
.scrollbar-custom::-webkit-scrollbar-thumb:hover {
|
| 145 |
+
background: rgba(255, 255, 255, 0.3);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.pulse-playing {
|
| 149 |
+
animation: pulse 2s infinite;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
@keyframes pulse {
|
| 153 |
+
0%, 100% { opacity: 1; }
|
| 154 |
+
50% { opacity: 0.5; }
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.playlist-container {
|
| 158 |
+
max-height: calc(100vh - 320px);
|
| 159 |
+
overflow-y: auto;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.drag-handle {
|
| 163 |
+
cursor: grab;
|
| 164 |
+
opacity: 0.5;
|
| 165 |
+
transition: opacity 0.2s;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.track-item:hover .drag-handle {
|
| 169 |
+
opacity: 1;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.drag-handle:active {
|
| 173 |
+
cursor: grabbing;
|
| 174 |
+
}
|
| 175 |
+
</style>
|
| 176 |
+
</head>
|
| 177 |
+
<body class="h-screen flex flex-col">
|
| 178 |
+
<!-- Header -->
|
| 179 |
+
<header class="p-4 glass-card mx-4 mt-4 rounded-xl">
|
| 180 |
+
<div class="flex justify-between items-center">
|
| 181 |
+
<div class="flex items-center space-x-4">
|
| 182 |
+
<button class="rounded-full p-2 hover:bg-white hover:bg-opacity-10 transition-colors">
|
| 183 |
+
<i data-feather="arrow-left"></i>
|
| 184 |
+
</button>
|
| 185 |
+
<button class="rounded-full p-2 hover:bg-white hover:bg-opacity-10 transition-colors">
|
| 186 |
+
<i data-feather="arrow-right"></i>
|
| 187 |
+
</button>
|
| 188 |
+
</div>
|
| 189 |
+
<div class="flex items-center space-x-4">
|
| 190 |
+
<button class="bg-white bg-opacity-10 hover:bg-opacity-20 px-4 py-2 rounded-full text-sm font-medium transition-all">
|
| 191 |
+
Explore Premium
|
| 192 |
+
</button>
|
| 193 |
+
<button class="rounded-full p-2 hover:bg-white hover:bg-opacity-10 transition-colors">
|
| 194 |
+
<i data-feather="user"></i>
|
| 195 |
+
</button>
|
| 196 |
+
</div>
|
| 197 |
+
</div>
|
| 198 |
+
</header>
|
| 199 |
+
|
| 200 |
+
<div class="flex flex-1 overflow-hidden px-4 pb-4">
|
| 201 |
+
<!-- Sidebar -->
|
| 202 |
+
<aside class="w-72 sidebar-gradient rounded-xl mr-4 p-6 flex flex-col">
|
| 203 |
+
<div class="mb-8">
|
| 204 |
+
<h1 class="text-2xl font-bold flex items-center bg-gradient-to-r from-white to-gray-300 bg-clip-text text-transparent">
|
| 205 |
+
<i data-feather="music" class="mr-3 text-green-400"></i>
|
| 206 |
+
SoundWave HTMX
|
| 207 |
+
</h1>
|
| 208 |
+
</div>
|
| 209 |
+
|
| 210 |
+
<nav class="space-y-2 mb-8">
|
| 211 |
+
<a href="#" class="flex items-center space-x-4 p-3 rounded-lg bg-white bg-opacity-10 hover:bg-opacity-20 transition-all">
|
| 212 |
+
<i data-feather="home"></i>
|
| 213 |
+
<span class="font-medium">Home</span>
|
| 214 |
+
</a>
|
| 215 |
+
<a href="#" class="flex items-center space-x-4 p-3 rounded-lg hover:bg-white hover:bg-opacity-10 transition-all">
|
| 216 |
+
<i data-feather="search"></i>
|
| 217 |
+
<span class="font-medium">Search</span>
|
| 218 |
+
</a>
|
| 219 |
+
<a href="#" class="flex items-center space-x-4 p-3 rounded-lg hover:bg-white hover:bg-opacity-10 transition-all">
|
| 220 |
+
<i data-feather="book-open"></i>
|
| 221 |
+
<span class="font-medium">Your Library</span>
|
| 222 |
+
</a>
|
| 223 |
+
</nav>
|
| 224 |
+
|
| 225 |
+
<div class="mt-auto space-y-4">
|
| 226 |
+
<button class="w-full flex items-center space-x-3 p-3 rounded-lg hover:bg-white hover:bg-opacity-10 transition-all">
|
| 227 |
+
<i data-feather="plus-square"></i>
|
| 228 |
+
<span class="font-medium">Create Playlist</span>
|
| 229 |
+
</button>
|
| 230 |
+
<button class="w-full flex items-center space-x-3 p-3 rounded-lg hover:bg-white hover:bg-opacity-10 transition-all">
|
| 231 |
+
<i data-feather="heart"></i>
|
| 232 |
+
<span class="font-medium">Liked Songs</span>
|
| 233 |
+
</button>
|
| 234 |
+
|
| 235 |
+
<hr class="border-white border-opacity-20">
|
| 236 |
+
|
| 237 |
+
<div class="space-y-2">
|
| 238 |
+
<button class="w-full flex items-center space-x-3 p-2 rounded hover:bg-white hover:bg-opacity-10 transition-all">
|
| 239 |
+
<i data-feather="download"></i>
|
| 240 |
+
<span class="text-sm">Install App</span>
|
| 241 |
+
</button>
|
| 242 |
+
<button class="w-full flex items-center space-x-3 p-2 rounded hover:bg-white hover:bg-opacity-10 transition-all">
|
| 243 |
+
<i data-feather="settings"></i>
|
| 244 |
+
<span class="text-sm">Settings</span>
|
| 245 |
+
</button>
|
| 246 |
+
</div>
|
| 247 |
+
</div>
|
| 248 |
+
</aside>
|
| 249 |
+
|
| 250 |
+
<!-- Main Content -->
|
| 251 |
+
<main class="flex-1 overflow-auto">
|
| 252 |
+
<div class="glass-card rounded-xl p-8 h-full">
|
| 253 |
+
<div class="mb-8">
|
| 254 |
+
<div class="flex items-center justify-between mb-6">
|
| 255 |
+
<h2 class="text-4xl font-bold bg-gradient-to-r from-white to-gray-300 bg-clip-text text-transparent">
|
| 256 |
+
Good afternoon
|
| 257 |
+
</h2>
|
| 258 |
+
<div class="flex space-x-3">
|
| 259 |
+
<button class="p-2 rounded-full hover:bg-white hover:bg-opacity-10 transition-colors">
|
| 260 |
+
<i data-feather="grid-3x3"></i>
|
| 261 |
+
</button>
|
| 262 |
+
<button class="p-2 rounded-full hover:bg-white hover:bg-opacity-10 transition-colors">
|
| 263 |
+
<i data-feather="list"></i>
|
| 264 |
+
</button>
|
| 265 |
+
</div>
|
| 266 |
+
</div>
|
| 267 |
+
|
| 268 |
+
<div class="flex items-center mb-8">
|
| 269 |
+
<button id="select-folder-btn" class="button-spotify px-6 py-3 rounded-full flex items-center font-medium">
|
| 270 |
+
<i data-feather="folder" class="mr-3"></i>
|
| 271 |
+
Select Music Folder
|
| 272 |
+
</button>
|
| 273 |
+
<input type="file" id="folder-input" webkitdirectory directory multiple class="hidden">
|
| 274 |
+
|
| 275 |
+
<div id="track-count" class="ml-6 text-sm text-gray-400">
|
| 276 |
+
No tracks loaded
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
|
| 281 |
+
<div class="glass-card rounded-xl p-6">
|
| 282 |
+
<div id="playlist-header" class="grid grid-cols-12 gap-4 px-4 py-3 text-gray-400 border-b border-white border-opacity-20 text-sm font-medium">
|
| 283 |
+
<div class="col-span-1 flex items-center">
|
| 284 |
+
<span>#</span>
|
| 285 |
+
<i data-feather="move" class="ml-2 w-4 h-4 drag-handle"></i>
|
| 286 |
+
</div>
|
| 287 |
+
<div class="col-span-5">Title</div>
|
| 288 |
+
<div class="col-span-3">Album</div>
|
| 289 |
+
<div class="col-span-2">Duration</div>
|
| 290 |
+
<div class="col-span-1"></div>
|
| 291 |
+
</div>
|
| 292 |
+
<div id="tracks-list" class="scrollbar-custom divide-y divide-white divide-opacity-10 playlist-container">
|
| 293 |
+
<!-- Tracks will be populated here -->
|
| 294 |
+
</div>
|
| 295 |
+
</div>
|
| 296 |
+
</div>
|
| 297 |
+
</main>
|
| 298 |
+
</div>
|
| 299 |
+
|
| 300 |
+
<!-- Player -->
|
| 301 |
+
<div id="player" class="player-gradient border-t border-white border-opacity-10 p-4">
|
| 302 |
+
<div class="max-w-7xl mx-auto grid grid-cols-3 gap-6 items-center">
|
| 303 |
+
<!-- Current Track Info -->
|
| 304 |
+
<div class="flex items-center space-x-4">
|
| 305 |
+
<div id="current-track-cover" class="bg-gradient-to-br from-purple-500 to-pink-500 w-16 h-16 rounded-lg shadow-lg flex items-center justify-center">
|
| 306 |
+
<i data-feather="music" class="text-white"></i>
|
| 307 |
+
</div>
|
| 308 |
+
<div class="min-w-0 flex-1">
|
| 309 |
+
<div id="current-track-title" class="font-semibold truncate">No track selected</div>
|
| 310 |
+
<div id="current-track-artist" class="text-sm text-gray-400 truncate">Unknown artist</div>
|
| 311 |
+
</div>
|
| 312 |
+
<button id="favorite-btn" class="text-gray-400 hover:text-white transition-colors">
|
| 313 |
+
<i data-feather="heart"></i>
|
| 314 |
+
</button>
|
| 315 |
+
</div>
|
| 316 |
+
|
| 317 |
+
<!-- Player Controls -->
|
| 318 |
+
<div class="flex flex-col items-center">
|
| 319 |
+
<div class="flex items-center space-x-6 mb-3">
|
| 320 |
+
<button id="shuffle-btn" class="text-gray-400 hover:text-white transition-colors">
|
| 321 |
+
<i data-feather="shuffle"></i>
|
| 322 |
+
</button>
|
| 323 |
+
<button id="prev-btn" class="text-white hover:text-green-400 transition-colors">
|
| 324 |
+
<i data-feather="skip-back"></i>
|
| 325 |
+
</button>
|
| 326 |
+
<button id="play-pause-btn" class="bg-white text-black rounded-full p-3 hover:bg-green-400 hover:text-white transition-all transform hover:scale-105">
|
| 327 |
+
<i data-feather="play" class="w-5 h-5"></i>
|
| 328 |
+
</button>
|
| 329 |
+
<button id="next-btn" class="text-white hover:text-green-400 transition-colors">
|
| 330 |
+
<i data-feather="skip-forward"></i>
|
| 331 |
+
</button>
|
| 332 |
+
<button id="repeat-btn" class="text-gray-400 hover:text-white transition-colors">
|
| 333 |
+
<i data-feather="repeat"></i>
|
| 334 |
+
</button>
|
| 335 |
+
</div>
|
| 336 |
+
|
| 337 |
+
<div class="w-full flex items-center space-x-3">
|
| 338 |
+
<span id="current-time" class="text-xs text-gray-400 min-w-[40px]">0:00</span>
|
| 339 |
+
<input type="range" id="progress-bar" class="progress-bar flex-1" value="0" min="0" max="100">
|
| 340 |
+
<span id="total-time" class="text-xs text-gray-400 min-w-[40px]">0:00</span>
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
|
| 344 |
+
<!-- Volume Control -->
|
| 345 |
+
<div class="flex items-center justify-end space-x-4">
|
| 346 |
+
<button id="lyrics-btn" class="text-gray-400 hover:text-white transition-colors">
|
| 347 |
+
<i data-feather="align-left"></i>
|
| 348 |
+
</button>
|
| 349 |
+
<button id="queue-btn" class="text-gray-400 hover:text-white transition-colors">
|
| 350 |
+
<i data-feather="list"></i>
|
| 351 |
+
</button>
|
| 352 |
+
<div class="flex items-center space-x-3">
|
| 353 |
+
<button id="volume-btn" class="text-gray-400 hover:text-white transition-colors">
|
| 354 |
+
<i data-feather="volume-2"></i>
|
| 355 |
+
</button>
|
| 356 |
+
<input type="range" id="volume-slider" class="volume-slider w-24" value="80" min="0" max="100">
|
| 357 |
+
</div>
|
| 358 |
+
</div>
|
| 359 |
+
</div>
|
| 360 |
+
</div>
|
| 361 |
+
|
| 362 |
+
<!-- HTMX for dynamic updates -->
|
| 363 |
+
<script src="script.js"></script>
|
| 364 |
+
<script>
|
| 365 |
+
// Enhanced feather icons replacement
|
| 366 |
+
feather.replace();
|
| 367 |
+
|
| 368 |
+
// Smooth transitions for interactive elements
|
| 369 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 370 |
+
// Add subtle animation to buttons
|
| 371 |
+
const buttons = document.querySelectorAll('button');
|
| 372 |
+
buttons.forEach(button => {
|
| 373 |
+
button.addEventListener('mouseenter', () => {
|
| 374 |
+
button.style.transform = 'translateY(-1px)';
|
| 375 |
+
});
|
| 376 |
+
button.addEventListener('mouseleave', () => {
|
| 377 |
+
button.style.transform = 'translateY(0)';
|
| 378 |
+
});
|
| 379 |
+
});
|
| 380 |
+
});
|
| 381 |
+
</script>
|
| 382 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 383 |
+
</body>
|
| 384 |
</html>
|
script.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Music Player State
|
| 2 |
+
const playerState = {
|
| 3 |
+
isPlaying: false,
|
| 4 |
+
currentTrackIndex: -1,
|
| 5 |
+
volume: 80,
|
| 6 |
+
progress: 0,
|
| 7 |
+
tracks: [],
|
| 8 |
+
draggedElement: null
|
| 9 |
+
};
|
| 10 |
+
|
| 11 |
+
// DOM Elements
|
| 12 |
+
const elements = {
|
| 13 |
+
selectFolderBtn: document.getElementById('select-folder-btn'),
|
| 14 |
+
folderInput: document.getElementById('folder-input'),
|
| 15 |
+
tracksList: document.getElementById('tracks-list'),
|
| 16 |
+
playPauseBtn: document.getElementById('play-pause-btn'),
|
| 17 |
+
prevBtn: document.getElementById('prev-btn'),
|
| 18 |
+
nextBtn: document.getElementById('next-btn'),
|
| 19 |
+
progressBar: document.getElementById('progress-bar'),
|
| 20 |
+
volumeSlider: document.getElementById('volume-slider'),
|
| 21 |
+
currentTime: document.getElementById('current-time'),
|
| 22 |
+
totalTime: document.getElementById('total-time'),
|
| 23 |
+
currentTrackTitle: document.getElementById('current-track-title'),
|
| 24 |
+
currentTrackArtist: document.getElementById('current-track-artist'),
|
| 25 |
+
shuffleBtn: document.getElementById('shuffle-btn'),
|
| 26 |
+
repeatBtn: document.getElementById('repeat-btn')
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
// Initialize the application
|
| 30 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 31 |
+
setupEventListeners();
|
| 32 |
+
updatePlayerUI();
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
// Set up event listeners
|
| 36 |
+
function setupEventListeners() {
|
| 37 |
+
// Folder selection
|
| 38 |
+
elements.selectFolderBtn.addEventListener('click', () => {
|
| 39 |
+
elements.folderInput.click();
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
elements.folderInput.addEventListener('change', handleFolderSelection);
|
| 43 |
+
|
| 44 |
+
// Player controls
|
| 45 |
+
elements.playPauseBtn.addEventListener('click', togglePlayPause);
|
| 46 |
+
elements.prevBtn.addEventListener('click', playPreviousTrack);
|
| 47 |
+
elements.nextBtn.addEventListener('click', playNextTrack);
|
| 48 |
+
elements.progressBar.addEventListener('input', handleProgressChange);
|
| 49 |
+
elements.volumeSlider.addEventListener('input', handleVolumeChange);
|
| 50 |
+
|
| 51 |
+
// Shuffle and repeat
|
| 52 |
+
elements.shuffleBtn.addEventListener('click', toggleShuffle);
|
| 53 |
+
elements.repeatBtn.addEventListener('click', toggleRepeat);
|
| 54 |
+
|
| 55 |
+
// Keyboard shortcuts
|
| 56 |
+
document.addEventListener('keydown', handleKeyboardShortcuts);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// Handle folder selection
|
| 60 |
+
function handleFolderSelection(event) {
|
| 61 |
+
const files = Array.from(event.target.files);
|
| 62 |
+
const audioFiles = files.filter(file => file.type.startsWith('audio/'));
|
| 63 |
+
|
| 64 |
+
if (audioFiles.length === 0) {
|
| 65 |
+
alert('No audio files found in the selected folder.');
|
| 66 |
+
return;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
playerState.tracks = audioFiles.map((file, index) => ({
|
| 70 |
+
id: index,
|
| 71 |
+
title: file.name.replace(/\.[^/.]+$/, ""), // Remove extension
|
| 72 |
+
artist: 'Unknown Artist',
|
| 73 |
+
album: 'Unknown Album',
|
| 74 |
+
duration: '0:00',
|
| 75 |
+
file: file,
|
| 76 |
+
url: URL.createObjectURL(file)
|
| 77 |
+
}));
|
| 78 |
+
|
| 79 |
+
renderPlaylist();
|
| 80 |
+
updatePlayerUI();
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// Render playlist
|
| 84 |
+
function renderPlaylist() {
|
| 85 |
+
elements.tracksList.innerHTML = '';
|
| 86 |
+
|
| 87 |
+
playerState.tracks.forEach((track, index) => {
|
| 88 |
+
const trackElement = document.createElement('div');
|
| 89 |
+
trackElement.className = 'track-item grid grid-cols-12 gap-4 p-3 items-center cursor-pointer';
|
| 90 |
+
trackElement.draggable = true;
|
| 91 |
+
trackElement.dataset.index = index;
|
| 92 |
+
|
| 93 |
+
trackElement.innerHTML = `
|
| 94 |
+
<div class="col-span-1 flex items-center">
|
| 95 |
+
<span class="track-number">${index + 1}</span>
|
| 96 |
+
<button class="play-button hidden ml-2 text-green-500">
|
| 97 |
+
<i data-feather="play"></i>
|
| 98 |
+
</button>
|
| 99 |
+
</div>
|
| 100 |
+
<div class="col-span-5 truncate">${track.title}</div>
|
| 101 |
+
<div class="col-span-3 truncate text-gray-400">${track.album}</div>
|
| 102 |
+
<div class="col-span-2 text-gray-400">${track.duration}</div>
|
| 103 |
+
<div class="col-span-1 flex justify-end">
|
| 104 |
+
<button class="more-button text-gray-400 hover:text-white">
|
| 105 |
+
<i data-feather="more-horizontal"></i>
|
| 106 |
+
</button>
|
| 107 |
+
</div>
|
| 108 |
+
`;
|
| 109 |
+
|
| 110 |
+
// Add drag and drop events
|
| 111 |
+
trackElement.addEventListener('dragstart', handleDragStart);
|
| 112 |
+
trackElement.addEventListener('dragover', handleDragOver);
|
| 113 |
+
trackElement.addEventListener('dragenter', handleDragEnter);
|
| 114 |
+
trackElement.addEventListener('dragleave', handleDragLeave);
|
| 115 |
+
trackElement.addEventListener('drop', handleDrop);
|
| 116 |
+
trackElement.addEventListener('dragend', handleDragEnd);
|
| 117 |
+
|
| 118 |
+
// Add click event to play track
|
| 119 |
+
trackElement.addEventListener('click', (e) => {
|
| 120 |
+
if (!e.target.closest('.more-button') && !e.target.closest('.play-button')) {
|
| 121 |
+
playTrack(index);
|
| 122 |
+
}
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
elements.tracksList.appendChild(trackElement);
|
| 126 |
+
});
|
| 127 |
+
|
| 128 |
+
feather.replace();
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// Drag and Drop Functions
|
| 132 |
+
function handleDragStart(e) {
|
| 133 |
+
playerState.draggedElement = this;
|
| 134 |
+
setTimeout(() => {
|
| 135 |
+
this.classList.add('dragging');
|
| 136 |
+
}, 0);
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
function handleDragOver(e) {
|
| 140 |
+
e.preventDefault();
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
function handleDragEnter(e) {
|
| 144 |
+
e.preventDefault();
|
| 145 |
+
this.classList.add('drag-over');
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
function handleDragLeave() {
|
| 149 |
+
this.classList.remove('drag-over');
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
function handleDrop(e) {
|
| 153 |
+
e.preventDefault();
|
| 154 |
+
this.classList.remove('drag-over');
|
| 155 |
+
|
| 156 |
+
if (playerState.draggedElement !== this) {
|
| 157 |
+
const draggedIndex = parseInt(playerState.draggedElement.dataset.index);
|
| 158 |
+
const targetIndex = parseInt(this.dataset.index);
|
| 159 |
+
|
| 160 |
+
// Reorder tracks array
|
| 161 |
+
const draggedTrack = playerState.tracks[draggedIndex];
|
| 162 |
+
playerState.tracks.splice(draggedIndex, 1);
|
| 163 |
+
playerState.tracks.splice(targetIndex, 0, draggedTrack);
|
| 164 |
+
|
| 165 |
+
// Update indices
|
| 166 |
+
playerState.tracks.forEach((track, index) => {
|
| 167 |
+
track.id = index;
|
| 168 |
+
});
|
| 169 |
+
|
| 170 |
+
// Update current track index if needed
|
| 171 |
+
if (playerState.currentTrackIndex === draggedIndex) {
|
| 172 |
+
playerState.currentTrackIndex = targetIndex;
|
| 173 |
+
} else if (playerState.currentTrackIndex === targetIndex) {
|
| 174 |
+
playerState.currentTrackIndex = draggedIndex;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
renderPlaylist();
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
function handleDragEnd() {
|
| 182 |
+
this.classList.remove('dragging');
|
| 183 |
+
playerState.draggedElement = null;
|
| 184 |
+
document.querySelectorAll('.track-item').forEach(item => {
|
| 185 |
+
item.classList.remove('drag-over', 'drag-over-after');
|
| 186 |
+
});
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// Player Control Functions
|
| 190 |
+
function togglePlayPause() {
|
| 191 |
+
playerState.isPlaying = !playerState.isPlaying;
|
| 192 |
+
updatePlayPauseButton();
|
| 193 |
+
|
| 194 |
+
if (playerState.isPlaying && playerState.currentTrackIndex === -1 && playerState.tracks.length > 0) {
|
| 195 |
+
playTrack(0);
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
function playTrack(index) {
|
| 200 |
+
if (index < 0 || index >= playerState.tracks.length) return;
|
| 201 |
+
|
| 202 |
+
playerState.currentTrackIndex = index;
|
| 203 |
+
playerState.isPlaying = true;
|
| 204 |
+
|
| 205 |
+
const track = playerState.tracks[index];
|
| 206 |
+
elements.currentTrackTitle.textContent = track.title;
|
| 207 |
+
elements.currentTrackArtist.textContent = track.artist;
|
| 208 |
+
|
| 209 |
+
updatePlayPauseButton();
|
| 210 |
+
renderPlaylist();
|
| 211 |
+
simulatePlayback();
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
function playNextTrack() {
|
| 215 |
+
if (playerState.tracks.length === 0) return;
|
| 216 |
+
|
| 217 |
+
let nextIndex = playerState.currentTrackIndex + 1;
|
| 218 |
+
if (nextIndex >= playerState.tracks.length) {
|
| 219 |
+
nextIndex = 0; // Loop to beginning
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
playTrack(nextIndex);
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
function playPreviousTrack() {
|
| 226 |
+
if (playerState.tracks.length === 0) return;
|
| 227 |
+
|
| 228 |
+
let prevIndex = playerState.currentTrackIndex - 1;
|
| 229 |
+
if (prevIndex < 0) {
|
| 230 |
+
prevIndex = playerState.tracks.length - 1; // Loop to end
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
playTrack(prevIndex);
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
function handleProgressChange() {
|
| 237 |
+
playerState.progress = parseInt(elements.progressBar.value);
|
| 238 |
+
updateTimeDisplay();
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
function handleVolumeChange() {
|
| 242 |
+
playerState.volume = parseInt(elements.volumeSlider.value);
|
| 243 |
+
elements.volumeSlider.style.background = `linear-gradient(to right, var(--primary-color) 0%, var(--primary-color) ${playerState.volume}%, rgba(255,255,255,0.3) ${playerState.volume}%, rgba(255,255,255,0.3) 100%)`;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
function toggleShuffle() {
|
| 247 |
+
elements.shuffleBtn.classList.toggle('text-green-500');
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
function toggleRepeat() {
|
| 251 |
+
elements.repeatBtn.classList.toggle('text-green-500');
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
// Update UI Functions
|
| 255 |
+
function updatePlayPauseButton() {
|
| 256 |
+
const icon = elements.playPauseBtn.querySelector('i');
|
| 257 |
+
if (playerState.isPlaying) {
|
| 258 |
+
icon.setAttribute('data-feather', 'pause');
|
| 259 |
+
} else {
|
| 260 |
+
icon.setAttribute('data-feather', 'play');
|
| 261 |
+
}
|
| 262 |
+
feather.replace();
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
function updateTimeDisplay() {
|
| 266 |
+
// In a real app, this would be based on actual track time
|
| 267 |
+
const totalSeconds = 210; // Example: 3:30
|
| 268 |
+
const currentSeconds = Math.floor((playerState.progress / 100) * totalSeconds);
|
| 269 |
+
|
| 270 |
+
const formatTime = (seconds) => {
|
| 271 |
+
const mins = Math.floor(seconds / 60);
|
| 272 |
+
const secs = seconds % 60;
|
| 273 |
+
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
| 274 |
+
};
|
| 275 |
+
|
| 276 |
+
elements.currentTime.textContent = formatTime(currentSeconds);
|
| 277 |
+
elements.totalTime.textContent = formatTime(totalSeconds);
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
function updatePlayerUI() {
|
| 281 |
+
// Update volume slider background
|
| 282 |
+
elements.volumeSlider.style.background = `linear-gradient(to right, var(--primary-color) 0%, var(--primary-color) ${playerState.volume}%, rgba(255,255,255,0.3) ${playerState.volume}%, rgba(255,255,255,0.3) 100%)`;
|
| 283 |
+
|
| 284 |
+
// Update progress bar
|
| 285 |
+
elements.progressBar.value = playerState.progress;
|
| 286 |
+
updateTimeDisplay();
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
// Simulate playback progress
|
| 290 |
+
function simulatePlayback() {
|
| 291 |
+
if (!playerState.isPlaying) return;
|
| 292 |
+
|
| 293 |
+
const interval = setInterval(() => {
|
| 294 |
+
if (!playerState.isPlaying) {
|
| 295 |
+
clearInterval(interval);
|
| 296 |
+
return;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
playerState.progress += 0.5;
|
| 300 |
+
if (playerState.progress >= 100) {
|
| 301 |
+
playerState.progress = 0;
|
| 302 |
+
playNextTrack();
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
updatePlayerUI();
|
| 306 |
+
}, 100);
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
// Keyboard Shortcuts
|
| 310 |
+
function handleKeyboardShortcuts(e) {
|
| 311 |
+
// Spacebar to play/pause
|
| 312 |
+
if (e.code === 'Space') {
|
| 313 |
+
e.preventDefault();
|
| 314 |
+
togglePlayPause();
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
// Left arrow for previous track
|
| 318 |
+
if (e.code === 'ArrowLeft') {
|
| 319 |
+
playPreviousTrack();
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
// Right arrow for next track
|
| 323 |
+
if (e.code === 'ArrowRight') {
|
| 324 |
+
playNextTrack();
|
| 325 |
+
}
|
| 326 |
+
}
|
style.css
CHANGED
|
@@ -1,28 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
body {
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
margin-top: 0;
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.
|
| 19 |
-
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Global Styles */
|
| 2 |
+
:root {
|
| 3 |
+
--primary-color: #1DB954;
|
| 4 |
+
--secondary-color: #191414;
|
| 5 |
+
--background-color: #121212;
|
| 6 |
+
--sidebar-color: #000000;
|
| 7 |
+
--player-color: #181818;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
body {
|
| 11 |
+
margin: 0;
|
| 12 |
+
padding: 0;
|
| 13 |
+
box-sizing: border-box;
|
| 14 |
+
background-color: var(--background-color);
|
| 15 |
+
color: white;
|
| 16 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 17 |
+
height: 100vh;
|
| 18 |
+
overflow: hidden;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/* Scrollbar Styling */
|
| 22 |
+
.playlist-container::-webkit-scrollbar {
|
| 23 |
+
width: 8px;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.playlist-container::-webkit-scrollbar-track {
|
| 27 |
+
background: rgba(255, 255, 255, 0.05);
|
| 28 |
+
border-radius: 4px;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.playlist-container::-webkit-scrollbar-thumb {
|
| 32 |
+
background: rgba(255, 255, 255, 0.1);
|
| 33 |
+
border-radius: 4px;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.playlist-container::-webkit-scrollbar-thumb:hover {
|
| 37 |
+
background: rgba(255, 255, 255, 0.2);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
/* Track Item Styles */
|
| 41 |
+
.track-item {
|
| 42 |
+
transition: all 0.2s ease;
|
| 43 |
+
cursor: grab;
|
| 44 |
}
|
| 45 |
|
| 46 |
+
.track-item:active {
|
| 47 |
+
cursor: grabbing;
|
|
|
|
| 48 |
}
|
| 49 |
|
| 50 |
+
.track-item.drag-over {
|
| 51 |
+
border-top: 2px solid var(--primary-color);
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
|
| 54 |
+
.track-item.drag-over-after {
|
| 55 |
+
border-bottom: 2px solid var(--primary-color);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
+
/* Progress Bar Customization */
|
| 59 |
+
.progress-bar {
|
| 60 |
+
-webkit-appearance: none;
|
| 61 |
+
appearance: none;
|
| 62 |
+
height: 4px;
|
| 63 |
+
background: rgba(255, 255, 255, 0.3);
|
| 64 |
+
outline: none;
|
| 65 |
+
border-radius: 2px;
|
| 66 |
}
|
| 67 |
+
|
| 68 |
+
.progress-bar::-webkit-slider-thumb {
|
| 69 |
+
-webkit-appearance: none;
|
| 70 |
+
appearance: none;
|
| 71 |
+
width: 12px;
|
| 72 |
+
height: 12px;
|
| 73 |
+
border-radius: 50%;
|
| 74 |
+
background: var(--primary-color);
|
| 75 |
+
cursor: pointer;
|
| 76 |
+
opacity: 0;
|
| 77 |
+
transition: opacity 0.2s;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.progress-bar:hover::-webkit-slider-thumb {
|
| 81 |
+
opacity: 1;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
/* Volume Slider Customization */
|
| 85 |
+
.volume-slider {
|
| 86 |
+
-webkit-appearance: none;
|
| 87 |
+
appearance: none;
|
| 88 |
+
height: 4px;
|
| 89 |
+
background: rgba(255, 255, 255, 0.3);
|
| 90 |
+
outline: none;
|
| 91 |
+
border-radius: 2px;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.volume-slider::-webkit-slider-thumb {
|
| 95 |
+
-webkit-appearance: none;
|
| 96 |
+
appearance: none;
|
| 97 |
+
width: 12px;
|
| 98 |
+
height: 12px;
|
| 99 |
+
border-radius: 50%;
|
| 100 |
+
background: white;
|
| 101 |
+
cursor: pointer;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
/* Button Hover Effects */
|
| 105 |
+
button:hover {
|
| 106 |
+
transform: scale(1.05);
|
| 107 |
+
transition: transform 0.2s;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
/* Responsive Design */
|
| 111 |
+
@media (max-width: 768px) {
|
| 112 |
+
.playlist-header {
|
| 113 |
+
display: none;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.track-item {
|
| 117 |
+
padding: 1rem 0;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
#player {
|
| 121 |
+
padding: 1rem;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
#player .grid {
|
| 125 |
+
grid-template-columns: 1fr;
|
| 126 |
+
gap: 1rem;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
#player > div:nth-child(2) {
|
| 130 |
+
order: 3;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
#player > div:nth-child(3) {
|
| 134 |
+
order: 2;
|
| 135 |
+
}
|
| 136 |
+
}
|