flen-crypto commited on
Commit
0bd5ed6
·
verified ·
1 Parent(s): 0ebe237

fix the api code so it operates correctly and add the additional pages - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +507 -18
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Mrflen
3
- emoji: 🌖
4
- colorFrom: blue
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: mrflen
3
+ emoji: 🐳
4
+ colorFrom: green
5
+ colorTo: purple
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,508 @@
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
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>Mr.FLEN Audius Library</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
9
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>
12
+ <script src="https://unpkg.com/feather-icons"></script>
13
+ <link rel="stylesheet" href="/base.css" />
14
+ <style>
15
+ :root {
16
+ --glow: #5cf;
17
+ --ink: #0a0a0a;
18
+ --glass: rgba(255, 255, 255, 0.06);
19
+ --neon-pink: #ff2a6d;
20
+ --neon-blue: #05d9e8;
21
+ --neon-purple: #d16bff;
22
+ }
23
+
24
+ body {
25
+ background: radial-gradient(1200px 600px at 20% -10%, rgba(0, 255, 255, 0.2), transparent 60%), #05060a;
26
+ color: #e8f0ff;
27
+ font-family: 'Inter', system-ui, sans-serif;
28
+ min-height: 100vh;
29
+ }
30
+
31
+ .glass {
32
+ backdrop-filter: saturate(1.4) blur(12px);
33
+ background: var(--glass);
34
+ border: 1px solid rgba(255, 255, 255, 0.1);
35
+ border-radius: 16px;
36
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
37
+ }
38
+
39
+ .neon-glow {
40
+ box-shadow: 0 0 5px var(--neon-blue), 0 0 10px var(--neon-blue), 0 0 15px var(--neon-blue);
41
+ }
42
+
43
+ .neon-text {
44
+ text-shadow: 0 0 5px currentColor, 0 0 10px currentColor;
45
+ }
46
+
47
+ .track-card {
48
+ transition: all 0.3s ease;
49
+ }
50
+
51
+ .track-card:hover {
52
+ transform: translateY(-4px);
53
+ box-shadow: 0 10px 25px rgba(92, 207, 255, 0.15);
54
+ }
55
+
56
+ .play-button {
57
+ transition: all 0.3s ease;
58
+ }
59
+
60
+ .play-button:hover {
61
+ transform: scale(1.1);
62
+ color: var(--neon-blue);
63
+ }
64
+
65
+ .skeleton {
66
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
67
+ background-size: 200% 100%;
68
+ animation: loading 1.5s infinite;
69
+ }
70
+
71
+ @keyframes loading {
72
+ 0% { background-position: 200% 0; }
73
+ 100% { background-position: -200% 0; }
74
+ }
75
+
76
+ .toast {
77
+ animation: slideIn 0.3s forwards, slideOut 0.3s forwards 2.7s;
78
+ }
79
+
80
+ @keyframes slideIn {
81
+ from { transform: translateX(100%); opacity: 0; }
82
+ to { transform: translateX(0); opacity: 1; }
83
+ }
84
+
85
+ @keyframes slideOut {
86
+ from { transform: translateX(0); opacity: 1; }
87
+ to { transform: translateX(100%); opacity: 0; }
88
+ }
89
+ </style>
90
+ </head>
91
+ <body class="min-h-screen flex flex-col">
92
+ <!-- Header -->
93
+ <header class="glass sticky top-0 z-50 p-4 mb-6">
94
+ <div class="container mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
95
+ <div class="flex items-center gap-3">
96
+ <div class="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 to-blue-400 flex items-center justify-center neon-glow">
97
+ <span class="text-xl">🎵</span>
98
+ </div>
99
+ <h1 class="text-2xl font-bold neon-text text-blue-300">Mr.FLEN Library</h1>
100
+ </div>
101
+
102
+ <div class="relative w-full md:w-96">
103
+ <input
104
+ id="search"
105
+ type="text"
106
+ placeholder="Search Audius (Ctrl/⌘-K)"
107
+ autocomplete="off"
108
+ class="w-full py-2 px-4 pr-10 rounded-full bg-black/30 border border-white/10 focus:border-blue-400 focus:ring-2 focus:ring-blue-500/30 focus:outline-none transition-all"
109
+ />
110
+ <i data-feather="search" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4"></i>
111
+ </div>
112
+
113
+ <nav class="flex gap-2">
114
+ <button class="px-4 py-2 rounded-lg bg-white/5 hover:bg-white/10 transition-all">Library</button>
115
+ <button class="px-4 py-2 rounded-lg bg-white/5 hover:bg-white/10 transition-all">Likes</button>
116
+ <button class="px-4 py-2 rounded-lg bg-white/5 hover:bg-white/10 transition-all">Playlists</button>
117
+ <button class="px-4 py-2 rounded-lg bg-white/5 hover:bg-white/10 transition-all">Discover</button>
118
+ </nav>
119
+ </div>
120
+ </header>
121
+
122
+ <!-- Main Content -->
123
+ <main class="flex-1 container mx-auto px-4 pb-24">
124
+ <div id="results" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
125
+ <!-- Results will be populated here -->
126
+ </div>
127
+
128
+ <div id="empty-state" class="hidden flex-col items-center justify-center py-20 text-center">
129
+ <div class="w-24 h-24 rounded-full bg-white/5 flex items-center justify-center mb-6">
130
+ <i data-feather="music" class="w-12 h-12 text-blue-400"></i>
131
+ </div>
132
+ <h2 class="text-2xl font-bold mb-2">No tracks found</h2>
133
+ <p class="text-gray-400 max-w-md">Search for music on Audius to start listening to Mr.FLEN's library</p>
134
+ </div>
135
+ </main>
136
+
137
+ <!-- Player Bar -->
138
+ <footer class="glass fixed bottom-0 left-0 right-0 p-4 border-t border-white/10">
139
+ <div class="container mx-auto flex items-center gap-4">
140
+ <div class="flex items-center gap-3 flex-1 min-w-0">
141
+ <img id="now-playing-art" src="" class="w-14 h-14 rounded-lg object-cover hidden">
142
+ <div class="min-w-0 flex-1">
143
+ <div id="now-playing-title" class="font-semibold truncate">Not playing</div>
144
+ <div id="now-playing-artist" class="text-sm text-gray-400 truncate">Search for music to begin</div>
145
+ </div>
146
+ </div>
147
+
148
+ <div class="flex items-center gap-2">
149
+ <button id="prev-btn" class="p-2 rounded-full hover:bg-white/10">
150
+ <i data-feather="skip-back" class="w-5 h-5"></i>
151
+ </button>
152
+ <button id="play-pause-btn" class="p-3 rounded-full bg-blue-500 hover:bg-blue-400">
153
+ <i data-feather="play" class="w-5 h-5" id="play-icon"></i>
154
+ </button>
155
+ <button id="next-btn" class="p-2 rounded-full hover:bg-white/10">
156
+ <i data-feather="skip-forward" class="w-5 h-5"></i>
157
+ </button>
158
+ </div>
159
+
160
+ <div class="hidden md:flex items-center gap-2 flex-1 max-w-md">
161
+ <span class="text-xs text-gray-400" id="current-time">0:00</span>
162
+ <input type="range" id="progress" class="flex-1 h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer" value="0">
163
+ <span class="text-xs text-gray-400" id="duration">0:00</span>
164
+ </div>
165
+
166
+ <div class="flex items-center gap-2">
167
+ <button class="p-2 rounded-full hover:bg-white/10">
168
+ <i data-feather="heart" class="w-5 h-5"></i>
169
+ </button>
170
+ <div class="flex items-center gap-1">
171
+ <i data-feather="volume" class="w-4 h-4"></i>
172
+ <input type="range" class="w-20 h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer" value="80">
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </footer>
177
+
178
+ <!-- Toast Container -->
179
+ <div id="toast-container" class="fixed top-4 right-4 z-50 space-y-2"></div>
180
+
181
+ <script>
182
+ // Initialize animations and icons
183
+ AOS.init();
184
+ feather.replace();
185
+ const { animate } = anime;
186
+ </script>
187
+
188
+ <script type="module">
189
+ /** ENV (replace before deploy) **/
190
+ const AUDIUS_APP_NAME = "MrFLEN-Library";
191
+ const AUDIUS_API_KEY = "YOUR_AUDIUS_API_KEY"; // required (client safe, read-only)
192
+ // BACKEND-ONLY: const AUDIUS_API_SECRET = "YOUR_AUDIUS_API_SECRET";
193
+
194
+ // State management
195
+ let currentTrackIndex = 0;
196
+ let searchResults = [];
197
+ let abortController = null;
198
+ let currentHost = null;
199
+ let isPlaying = false;
200
+
201
+ // DOM Elements
202
+ const resultsContainer = document.querySelector('#results');
203
+ const emptyState = document.querySelector('#empty-state');
204
+ const player = new Audio();
205
+ const searchInput = document.querySelector('#search');
206
+ const playPauseBtn = document.querySelector('#play-pause-btn');
207
+ const playIcon = document.querySelector('#play-icon');
208
+ const prevBtn = document.querySelector('#prev-btn');
209
+ const nextBtn = document.querySelector('#next-btn');
210
+ const progressBar = document.querySelector('#progress');
211
+ const currentTimeEl = document.querySelector('#current-time');
212
+ const durationEl = document.querySelector('#duration');
213
+ const nowPlayingArt = document.querySelector('#now-playing-art');
214
+ const nowPlayingTitle = document.querySelector('#now-playing-title');
215
+ const nowPlayingArtist = document.querySelector('#now-playing-artist');
216
+ const toastContainer = document.querySelector('#toast-container');
217
+
218
+ /** Try SDK first; fall back to REST **/
219
+ let sdk;
220
+ try {
221
+ // If bundling, use: import AudiusSdk from '@audius/sdk'
222
+ // For this demo we will REST if import fails.
223
+ // sdk = await import('https://cdn.skypack.dev/@audius/sdk').then(m => m.default({ appName: AUDIUS_APP_NAME, apiKey: AUDIUS_API_KEY }));
224
+ } catch(e) {
225
+ console.log("SDK not available, using REST API");
226
+ }
227
+
228
+ // Utility functions
229
+ function formatTime(seconds) {
230
+ const mins = Math.floor(seconds / 60);
231
+ const secs = Math.floor(seconds % 60);
232
+ return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
233
+ }
234
+
235
+ function showToast(message, type = 'info') {
236
+ const toast = document.createElement('div');
237
+ toast.className = `toast glass px-4 py-3 rounded-lg flex items-center gap-2 ${type === 'error' ? 'text-red-300' : 'text-blue-300'}`;
238
+ toast.innerHTML = `
239
+ <i data-feather="${type === 'error' ? 'alert-circle' : 'info'}" class="w-5 h-5"></i>
240
+ <span>${message}</span>
241
+ `;
242
+ toastContainer.appendChild(toast);
243
+ feather.replace();
244
+
245
+ setTimeout(() => {
246
+ toast.remove();
247
+ }, 3000);
248
+ }
249
+
250
+ async function pickHost() {
251
+ if (currentHost) return currentHost;
252
+
253
+ try {
254
+ const res = await fetch('https://api.audius.co');
255
+ const { data } = await res.json();
256
+ currentHost = data[Math.floor(Math.random() * data.length)];
257
+ return currentHost;
258
+ } catch (error) {
259
+ showToast('Failed to connect to Audius', 'error');
260
+ throw error;
261
+ }
262
+ }
263
+
264
+ async function searchTracks(query) {
265
+ if (abortController) {
266
+ abortController.abort();
267
+ }
268
+
269
+ abortController = new AbortController();
270
+
271
+ try {
272
+ // Show loading state
273
+ resultsContainer.innerHTML = '';
274
+ for (let i = 0; i < 8; i++) {
275
+ resultsContainer.innerHTML += `
276
+ <div class="track-card glass p-4 animate-pulse">
277
+ <div class="skeleton w-full h-40 rounded-lg mb-4"></div>
278
+ <div class="skeleton h-5 w-3/4 mb-2"></div>
279
+ <div class="skeleton h-4 w-1/2"></div>
280
+ </div>
281
+ `;
282
+ }
283
+
284
+ if (sdk) {
285
+ const { data } = await sdk.tracks.searchTracks({ query });
286
+ return data || [];
287
+ } else {
288
+ const host = await pickHost();
289
+ const url = `${host}/v1/tracks/search?query=${encodeURIComponent(query)}&app_name=${encodeURIComponent(AUDIUS_APP_NAME)}`;
290
+ const res = await fetch(url, {
291
+ headers: { 'Accept': 'application/json' },
292
+ signal: abortController.signal
293
+ });
294
+ const json = await res.json();
295
+ return json.data || [];
296
+ }
297
+ } catch (error) {
298
+ if (error.name !== 'AbortError') {
299
+ showToast('Search failed', 'error');
300
+ console.error('Search error:', error);
301
+ }
302
+ return [];
303
+ }
304
+ }
305
+
306
+ async function streamUrl(trackId) {
307
+ try {
308
+ if (sdk) {
309
+ return await sdk.tracks.getTrackStreamUrl({ trackId });
310
+ } else {
311
+ const host = await pickHost();
312
+ return `${host}/v1/tracks/${trackId}/stream?app_name=${encodeURIComponent(AUDIUS_APP_NAME)}`;
313
+ }
314
+ } catch (error) {
315
+ showToast('Failed to get stream URL', 'error');
316
+ console.error('Stream URL error:', error);
317
+ throw error;
318
+ }
319
+ }
320
+
321
+ async function playTrack(track, index) {
322
+ try {
323
+ currentTrackIndex = index;
324
+ const streamUrl = await streamUrl(track.id);
325
+
326
+ player.src = streamUrl;
327
+ player.play();
328
+ isPlaying = true;
329
+ playIcon.setAttribute('data-feather', 'pause');
330
+ feather.replace();
331
+
332
+ // Update now playing info
333
+ nowPlayingArt.src = track.artwork?.['150x150'] || '';
334
+ nowPlayingArt.classList.remove('hidden');
335
+ nowPlayingTitle.textContent = track.title;
336
+ nowPlayingArtist.textContent = `by ${track.user.handle}`;
337
+
338
+ // Preload next track for seamless playback
339
+ if (searchResults[index + 1]) {
340
+ const nextTrackUrl = await streamUrl(searchResults[index + 1].id);
341
+ // Just preload, don't assign to player
342
+ }
343
+ } catch (error) {
344
+ showToast('Failed to play track', 'error');
345
+ console.error('Play error:', error);
346
+ }
347
+ }
348
+
349
+ function renderResults(tracks) {
350
+ if (tracks.length === 0) {
351
+ resultsContainer.classList.add('hidden');
352
+ emptyState.classList.remove('hidden');
353
+ return;
354
+ }
355
+
356
+ resultsContainer.classList.remove('hidden');
357
+ emptyState.classList.add('hidden');
358
+
359
+ searchResults = tracks;
360
+
361
+ resultsContainer.innerHTML = tracks.map((track, index) => `
362
+ <div class="track-card glass p-4" data-aos="fade-up">
363
+ <div class="relative mb-4 group">
364
+ <img
365
+ src="${track.artwork?.['150x150'] || ''}"
366
+ alt="${track.title}"
367
+ class="w-full aspect-square object-cover rounded-lg"
368
+ loading="lazy"
369
+ >
370
+ <button
371
+ class="play-button absolute inset-0 w-full h-full flex items-center justify-center bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity rounded-lg"
372
+ data-index="${index}"
373
+ >
374
+ <div class="w-12 h-12 rounded-full bg-blue-500/90 flex items-center justify-center">
375
+ <i data-feather="play" class="w-6 h-6 ml-1 text-white"></i>
376
+ </div>
377
+ </button>
378
+ </div>
379
+ <h3 class="font-semibold truncate mb-1">${track.title}</h3>
380
+ <p class="text-sm text-gray-400 truncate">by ${track.user.handle}</p>
381
+ <div class="flex items-center justify-between mt-3">
382
+ <span class="text-xs text-gray-500">${formatTime(track.duration)}</span>
383
+ <button class="p-2 rounded-full hover:bg-white/10">
384
+ <i data-feather="heart" class="w-4 h-4"></i>
385
+ </button>
386
+ </div>
387
+ </div>
388
+ `).join('');
389
+
390
+ feather.replace();
391
+
392
+ // Add event listeners to play buttons
393
+ document.querySelectorAll('.play-button').forEach(btn => {
394
+ btn.addEventListener('click', () => {
395
+ const index = parseInt(btn.dataset.index);
396
+ playTrack(tracks[index], index);
397
+ });
398
+ });
399
+ }
400
+
401
+ // Event listeners
402
+ let searchTimeout;
403
+ searchInput.addEventListener('input', (e) => {
404
+ const query = e.target.value.trim();
405
+
406
+ clearTimeout(searchTimeout);
407
+
408
+ if (query.length < 2) {
409
+ resultsContainer.innerHTML = '';
410
+ emptyState.classList.remove('hidden');
411
+ return;
412
+ }
413
+
414
+ searchTimeout = setTimeout(async () => {
415
+ const tracks = await searchTracks(query);
416
+ renderResults(tracks);
417
+ }, 250);
418
+ });
419
+
420
+ // Keyboard shortcuts
421
+ document.addEventListener('keydown', (e) => {
422
+ // Cmd/Ctrl+K to focus search
423
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
424
+ e.preventDefault();
425
+ searchInput.focus();
426
+ }
427
+
428
+ // Spacebar to play/pause
429
+ if (e.code === 'Space' && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
430
+ e.preventDefault();
431
+ if (isPlaying) {
432
+ player.pause();
433
+ } else if (player.src) {
434
+ player.play();
435
+ }
436
+ }
437
+
438
+ // Arrow keys for search navigation (future enhancement)
439
+ });
440
+
441
+ // Player controls
442
+ playPauseBtn.addEventListener('click', () => {
443
+ if (player.src) {
444
+ if (isPlaying) {
445
+ player.pause();
446
+ } else {
447
+ player.play();
448
+ }
449
+ } else if (searchResults.length > 0) {
450
+ playTrack(searchResults[0], 0);
451
+ }
452
+ });
453
+
454
+ prevBtn.addEventListener('click', () => {
455
+ if (searchResults.length > 0) {
456
+ const newIndex = (currentTrackIndex - 1 + searchResults.length) % searchResults.length;
457
+ playTrack(searchResults[newIndex], newIndex);
458
+ }
459
+ });
460
+
461
+ nextBtn.addEventListener('click', () => {
462
+ if (searchResults.length > 0) {
463
+ const newIndex = (currentTrackIndex + 1) % searchResults.length;
464
+ playTrack(searchResults[newIndex], newIndex);
465
+ }
466
+ });
467
+
468
+ // Player events
469
+ player.addEventListener('play', () => {
470
+ isPlaying = true;
471
+ playIcon.setAttribute('data-feather', 'pause');
472
+ feather.replace();
473
+ });
474
+
475
+ player.addEventListener('pause', () => {
476
+ isPlaying = false;
477
+ playIcon.setAttribute('data-feather', 'play');
478
+ feather.replace();
479
+ });
480
+
481
+ player.addEventListener('timeupdate', () => {
482
+ if (player.duration) {
483
+ const percent = (player.currentTime / player.duration) * 100;
484
+ progressBar.value = percent;
485
+ currentTimeEl.textContent = formatTime(player.currentTime);
486
+ durationEl.textContent = formatTime(player.duration);
487
+ }
488
+ });
489
+
490
+ player.addEventListener('ended', () => {
491
+ // Auto-play next track
492
+ if (searchResults.length > 0) {
493
+ const newIndex = (currentTrackIndex + 1) % searchResults.length;
494
+ playTrack(searchResults[newIndex], newIndex);
495
+ }
496
+ });
497
+
498
+ progressBar.addEventListener('input', (e) => {
499
+ if (player.duration) {
500
+ player.currentTime = (e.target.value / 100) * player.duration;
501
+ }
502
+ });
503
+
504
+ // Initialize
505
+ feather.replace();
506
+ </script>
507
+ </body>
508
  </html>