PsyEyes commited on
Commit
7421366
·
verified ·
1 Parent(s): ef3f869

Давай сделаем красивый музыкальный плеер на HTMX и Tailwind визуально похожий на Spotify. Что нужно:

Browse files

1) возможность выбрать папку с музыкой
2) треки должны отображаться плейлистом
3) их порядок можно менять мышкой
4) не нужно запускать трек при смене его порядка мышкой
5) переключение на следующий или предыдущий трек
6) регулировка громкости

Files changed (4) hide show
  1. README.md +8 -5
  2. index.html +383 -18
  3. script.js +326 -0
  4. style.css +126 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Soundwave Htmx
3
- emoji: 🦀
4
- colorFrom: gray
5
- colorTo: pink
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
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
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
 
 
 
 
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
+ }