Multimedix commited on
Commit
f7dac30
·
verified ·
1 Parent(s): 5ac32c3

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +764 -19
index.html CHANGED
@@ -1,19 +1,764 @@
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="de">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>RadioFlow | Dein Deutsches Online Radio</title>
7
+
8
+ <!-- Google Fonts Import -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet">
12
+
13
+ <!-- Material Icons Import -->
14
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet">
15
+
16
+ <style>
17
+ :root {
18
+ --primary: #6366f1;
19
+ --secondary: #ec4899;
20
+ --accent: #8b5cf6;
21
+ --dark-bg: #0f172a;
22
+ --card-bg: rgba(30, 41, 59, 0.7);
23
+ --text-main: #f8fafc;
24
+ --text-muted: #94a3b8;
25
+ --glass-border: rgba(255, 255, 255, 0.1);
26
+ --glass-shine: rgba(255, 255, 255, 0.05);
27
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
28
+ }
29
+
30
+ * {
31
+ margin: 0;
32
+ padding: 0;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ body {
37
+ font-family: 'Outfit', sans-serif;
38
+ background-color: var(--dark-bg);
39
+ background-image:
40
+ radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
41
+ radial-gradient(circle at 90% 80%, rgba(236, 72, 153, 0.15) 0%, transparent 40%);
42
+ color: var(--text-main);
43
+ min-height: 100vh;
44
+ display: flex;
45
+ flex-direction: column;
46
+ overflow-x: hidden;
47
+ }
48
+
49
+ /* --- Header --- */
50
+ header {
51
+ padding: 1.5rem 2rem;
52
+ display: flex;
53
+ justify-content: space-between;
54
+ align-items: center;
55
+ backdrop-filter: blur(10px);
56
+ background: rgba(15, 23, 42, 0.6);
57
+ border-bottom: 1px solid var(--glass-border);
58
+ position: sticky;
59
+ top: 0;
60
+ z-index: 100;
61
+ }
62
+
63
+ .logo {
64
+ font-size: 1.8rem;
65
+ font-weight: 800;
66
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
67
+ -webkit-background-clip: text;
68
+ -webkit-text-fill-color: transparent;
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 0.5rem;
72
+ }
73
+
74
+ .logo span {
75
+ font-size: 1.5rem;
76
+ }
77
+
78
+ .built-with {
79
+ font-size: 0.9rem;
80
+ color: var(--text-muted);
81
+ text-decoration: none;
82
+ padding: 0.5rem 1rem;
83
+ border: 1px solid var(--glass-border);
84
+ border-radius: 20px;
85
+ transition: var(--transition);
86
+ }
87
+
88
+ .built-with:hover {
89
+ border-color: var(--primary);
90
+ color: var(--primary);
91
+ background: var(--glass-shine);
92
+ }
93
+
94
+ /* --- Main Content --- */
95
+ main {
96
+ flex: 1;
97
+ padding: 2rem;
98
+ max-width: 1400px;
99
+ margin: 0 auto;
100
+ width: 100%;
101
+ padding-bottom: 160px; /* Space for player */
102
+ }
103
+
104
+ .hero {
105
+ text-align: center;
106
+ margin-bottom: 3rem;
107
+ animation: fadeIn 0.8s ease-out;
108
+ }
109
+
110
+ .hero h1 {
111
+ font-size: 3rem;
112
+ margin-bottom: 0.5rem;
113
+ line-height: 1.2;
114
+ }
115
+
116
+ .hero p {
117
+ color: var(--text-muted);
118
+ font-size: 1.2rem;
119
+ }
120
+
121
+ /* --- Filter Tags --- */
122
+ .filters {
123
+ display: flex;
124
+ justify-content: center;
125
+ gap: 1rem;
126
+ margin-bottom: 2.5rem;
127
+ flex-wrap: wrap;
128
+ }
129
+
130
+ .filter-btn {
131
+ background: var(--card-bg);
132
+ border: 1px solid var(--glass-border);
133
+ color: var(--text-muted);
134
+ padding: 0.6rem 1.5rem;
135
+ border-radius: 30px;
136
+ cursor: pointer;
137
+ transition: var(--transition);
138
+ font-weight: 500;
139
+ backdrop-filter: blur(5px);
140
+ }
141
+
142
+ .filter-btn:hover, .filter-btn.active {
143
+ background: var(--primary);
144
+ color: white;
145
+ border-color: var(--primary);
146
+ box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
147
+ }
148
+
149
+ /* --- Card Grid --- */
150
+ .station-grid {
151
+ display: grid;
152
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
153
+ gap: 2rem;
154
+ }
155
+
156
+ .card {
157
+ background: var(--card-bg);
158
+ border: 1px solid var(--glass-border);
159
+ border-radius: 20px;
160
+ overflow: hidden;
161
+ transition: var(--transition);
162
+ position: relative;
163
+ cursor: pointer;
164
+ backdrop-filter: blur(10px);
165
+ display: flex;
166
+ flex-direction: column;
167
+ }
168
+
169
+ .card:hover {
170
+ transform: translateY(-8px);
171
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
172
+ border-color: rgba(255, 255, 255, 0.2);
173
+ }
174
+
175
+ .card.playing {
176
+ border-color: var(--secondary);
177
+ box-shadow: 0 0 20px rgba(236, 72, 153, 0.2);
178
+ }
179
+
180
+ .card-image-wrapper {
181
+ position: relative;
182
+ width: 100%;
183
+ padding-top: 60%; /* Aspect Ratio */
184
+ overflow: hidden;
185
+ }
186
+
187
+ .card-image {
188
+ position: absolute;
189
+ top: 0;
190
+ left: 0;
191
+ width: 100%;
192
+ height: 100%;
193
+ object-fit: cover;
194
+ transition: transform 0.5s ease;
195
+ }
196
+
197
+ .card:hover .card-image {
198
+ transform: scale(1.1);
199
+ }
200
+
201
+ .card-overlay {
202
+ position: absolute;
203
+ top: 0;
204
+ left: 0;
205
+ width: 100%;
206
+ height: 100%;
207
+ background: rgba(0, 0, 0, 0.3);
208
+ display: flex;
209
+ justify-content: center;
210
+ align-items: center;
211
+ opacity: 0;
212
+ transition: var(--transition);
213
+ }
214
+
215
+ .card:hover .card-overlay {
216
+ opacity: 1;
217
+ }
218
+
219
+ .play-btn-overlay {
220
+ background: white;
221
+ color: var(--primary);
222
+ width: 60px;
223
+ height: 60px;
224
+ border-radius: 50%;
225
+ display: flex;
226
+ justify-content: center;
227
+ align-items: center;
228
+ font-size: 2rem;
229
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
230
+ transform: scale(0.8);
231
+ transition: var(--transition);
232
+ }
233
+
234
+ .card:hover .play-btn-overlay {
235
+ transform: scale(1);
236
+ }
237
+
238
+ .card-info {
239
+ padding: 1.5rem;
240
+ flex: 1;
241
+ display: flex;
242
+ flex-direction: column;
243
+ justify-content: space-between;
244
+ }
245
+
246
+ .station-genre {
247
+ font-size: 0.75rem;
248
+ text-transform: uppercase;
249
+ letter-spacing: 1px;
250
+ color: var(--secondary);
251
+ font-weight: 700;
252
+ margin-bottom: 0.5rem;
253
+ }
254
+
255
+ .station-name {
256
+ font-size: 1.25rem;
257
+ font-weight: 600;
258
+ margin-bottom: 0.5rem;
259
+ line-height: 1.3;
260
+ }
261
+
262
+ .station-meta {
263
+ display: flex;
264
+ align-items: center;
265
+ gap: 0.5rem;
266
+ color: var(--text-muted);
267
+ font-size: 0.85rem;
268
+ }
269
+
270
+ /* --- Player Bar --- */
271
+ .player-bar {
272
+ position: fixed;
273
+ bottom: 2rem;
274
+ left: 50%;
275
+ transform: translateX(-50%);
276
+ width: 90%;
277
+ max-width: 800px;
278
+ background: rgba(15, 23, 42, 0.85);
279
+ backdrop-filter: blur(20px);
280
+ border: 1px solid var(--glass-border);
281
+ border-radius: 24px;
282
+ padding: 1rem 2rem;
283
+ display: flex;
284
+ align-items: center;
285
+ justify-content: space-between;
286
+ box-shadow: 0 20px 50px rgba(0,0,0,0.5);
287
+ z-index: 1000;
288
+ transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
289
+ opacity: 0;
290
+ pointer-events: none;
291
+ }
292
+
293
+ .player-bar.active {
294
+ opacity: 1;
295
+ transform: translateX(-50%) translateY(0);
296
+ pointer-events: all;
297
+ }
298
+
299
+ .player-info {
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 1rem;
303
+ width: 40%;
304
+ }
305
+
306
+ .player-art {
307
+ width: 50px;
308
+ height: 50px;
309
+ border-radius: 12px;
310
+ background: #333;
311
+ object-fit: cover;
312
+ box-shadow: 0 4px 10px rgba(0,0,0,0.2);
313
+ }
314
+
315
+ .player-text {
316
+ display: flex;
317
+ flex-direction: column;
318
+ overflow: hidden;
319
+ }
320
+
321
+ .player-title {
322
+ font-weight: 600;
323
+ white-space: nowrap;
324
+ overflow: hidden;
325
+ text-overflow: ellipsis;
326
+ }
327
+
328
+ .player-status {
329
+ font-size: 0.75rem;
330
+ color: var(--accent);
331
+ display: flex;
332
+ align-items: center;
333
+ gap: 5px;
334
+ }
335
+
336
+ /* Visualizer Bars */
337
+ .visualizer {
338
+ display: flex;
339
+ gap: 2px;
340
+ height: 12px;
341
+ align-items: flex-end;
342
+ }
343
+
344
+ .bar {
345
+ width: 3px;
346
+ background: var(--secondary);
347
+ animation: bounce 0.5s infinite ease-in-out alternate;
348
+ }
349
+
350
+ .bar:nth-child(2) { animation-delay: 0.1s; height: 60%; }
351
+ .bar:nth-child(3) { animation-delay: 0.2s; height: 80%; }
352
+ .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
353
+
354
+ @keyframes bounce {
355
+ 0% { height: 20%; opacity: 0.5; }
356
+ 100% { height: 100%; opacity: 1; }
357
+ }
358
+
359
+ .paused .bar {
360
+ animation-play-state: paused;
361
+ height: 20%;
362
+ opacity: 0.3;
363
+ }
364
+
365
+ .player-controls {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 1.5rem;
369
+ }
370
+
371
+ .ctrl-btn {
372
+ background: none;
373
+ border: none;
374
+ color: var(--text-main);
375
+ cursor: pointer;
376
+ transition: var(--transition);
377
+ display: flex;
378
+ align-items: center;
379
+ justify-content: center;
380
+ }
381
+
382
+ .ctrl-btn:hover {
383
+ color: var(--primary);
384
+ }
385
+
386
+ .play-pause-btn {
387
+ width: 50px;
388
+ height: 50px;
389
+ border-radius: 50%;
390
+ background: var(--primary);
391
+ color: white;
392
+ box-shadow: 0 0 15px rgba(99, 102, 241, 0.5);
393
+ }
394
+
395
+ .play-pause-btn:hover {
396
+ background: var(--secondary);
397
+ color: white;
398
+ transform: scale(1.1);
399
+ }
400
+
401
+ .volume-container {
402
+ display: flex;
403
+ align-items: center;
404
+ gap: 0.5rem;
405
+ width: 100px;
406
+ }
407
+
408
+ input[type="range"] {
409
+ -webkit-appearance: none;
410
+ width: 100%;
411
+ height: 4px;
412
+ border-radius: 2px;
413
+ background: rgba(255,255,255,0.2);
414
+ outline: none;
415
+ }
416
+
417
+ input[type="range"]::-webkit-slider-thumb {
418
+ -webkit-appearance: none;
419
+ width: 12px;
420
+ height: 12px;
421
+ border-radius: 50%;
422
+ background: white;
423
+ cursor: pointer;
424
+ transition: var(--transition);
425
+ }
426
+
427
+ input[type="range"]::-webkit-slider-thumb:hover {
428
+ transform: scale(1.2);
429
+ }
430
+
431
+ /* --- Animations --- */
432
+ @keyframes fadeIn {
433
+ from { opacity: 0; transform: translateY(20px); }
434
+ to { opacity: 1; transform: translateY(0); }
435
+ }
436
+
437
+ /* --- Responsive --- */
438
+ @media (max-width: 768px) {
439
+ .hero h1 { font-size: 2rem; }
440
+ .player-bar {
441
+ flex-direction: column;
442
+ gap: 1rem;
443
+ bottom: 1rem;
444
+ padding: 1rem;
445
+ }
446
+ .player-info { width: 100%; justify-content: center; text-align: center; }
447
+ .player-controls { width: 100%; justify-content: space-between; }
448
+ .volume-container { width: 80px; }
449
+ }
450
+ </style>
451
+ </head>
452
+ <body>
453
+
454
+ <header>
455
+ <div class="logo">
456
+ <span class="material-icons-round">radio</span>
457
+ RadioFlow
458
+ </div>
459
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
460
+ Built with anycoder
461
+ </a>
462
+ </header>
463
+
464
+ <main>
465
+ <div class="hero">
466
+ <h1>Entdecke den Sound Deutschlands</h1>
467
+ <p>Top Sender, beste Qualität. Echtzeit-Streaming.</p>
468
+ </div>
469
+
470
+ <div class="filters">
471
+ <button class="filter-btn active" onclick="filterStations('all')">Alle</button>
472
+ <button class="filter-btn" onclick="filterStations('Pop')">Pop</button>
473
+ <button class="filter-btn" onclick="filterStations('Rock')">Rock</button>
474
+ <button class="filter-btn" onclick="filterStations('News')">News</button>
475
+ <button class="filter-btn" onclick="filterStations('Techno')">Techno</button>
476
+ </div>
477
+
478
+ <div class="station-grid" id="stationGrid">
479
+ <!-- Cards will be injected here via JS -->
480
+ </div>
481
+ </main>
482
+
483
+ <!-- Player Bar -->
484
+ <div class="player-bar" id="playerBar">
485
+ <div class="player-info">
486
+ <img src="" alt="Logo" class="player-art" id="playerArt">
487
+ <div class="player-text">
488
+ <div class="player-title" id="playerTitle">Sender Name</div>
489
+ <div class="player-status">
490
+ <div class="visualizer paused" id="visualizer">
491
+ <div class="bar"></div>
492
+ <div class="bar"></div>
493
+ <div class="bar"></div>
494
+ <div class="bar"></div>
495
+ </div>
496
+ <span id="playerStateText">Live</span>
497
+ </div>
498
+ </div>
499
+ </div>
500
+
501
+ <div class="player-controls">
502
+ <button class="ctrl-btn" onclick="stopRadio()" title="Stop">
503
+ <span class="material-icons-round">stop</span>
504
+ </button>
505
+ <button class="ctrl-btn play-pause-btn" onclick="togglePlay()" id="mainPlayBtn">
506
+ <span class="material-icons-round" id="mainPlayIcon">play_arrow</span>
507
+ </button>
508
+
509
+ <div class="volume-container">
510
+ <span class="material-icons-round" style="font-size: 18px; color: var(--text-muted);">volume_up</span>
511
+ <input type="range" min="0" max="1" step="0.1" value="0.8" oninput="setVolume(this.value)">
512
+ </div>
513
+ </div>
514
+ </div>
515
+
516
+ <script>
517
+ // Radio Station Data
518
+ const stations = [
519
+ {
520
+ name: "Antenne Bayern",
521
+ genre: "Pop",
522
+ url: "https://stream.antenne.de/antenne",
523
+ image: "https://picsum.photos/seed/antenne/300/180"
524
+ },
525
+ {
526
+ name: "Bayern 3",
527
+ genre: "Pop",
528
+ url: "https://br-br3-live.cast.addradio.de/br/br3/live/mp3/128/stream.mp3",
529
+ image: "https://picsum.photos/seed/bayern3/300/180"
530
+ },
531
+ {
532
+ name: "1LIVE",
533
+ genre: "Pop",
534
+ url: "https://wdr-1live-live.icecast.wdr.de/wdr/1live/live/mp3/128/stream.mp3",
535
+ image: "https://picsum.photos/seed/1live/300/180"
536
+ },
537
+ {
538
+ name: "Deutschlandfunk",
539
+ genre: "News",
540
+ url: "https://st01.dlf.de/dlf/01/mpeg128/dlf/",
541
+ image: "https://picsum.photos/seed/dlf/300/180"
542
+ },
543
+ {
544
+ name: "NDR 2",
545
+ genre: "Pop",
546
+ url: "https://ndr-ndr2-niedersachsen.cast.addradio.de/ndr/ndr2/niedersachsen/mp3/128/stream.mp3",
547
+ image: "https://picsum.photos/seed/ndr2/300/180"
548
+ },
549
+ {
550
+ name: "SWR3",
551
+ genre: "Pop",
552
+ url: "https://swr-swr3-live.cast.addradio.de/swr/swr3/live/mp3/128/stream.mp3",
553
+ image: "https://picsum.photos/seed/swr3/300/180"
554
+ },
555
+ {
556
+ name: "Rock Antenne",
557
+ genre: "Rock",
558
+ url: "https://stream.rockantenne.de/rockantenne",
559
+ image: "https://picsum.photos/seed/rockantenne/300/180"
560
+ },
561
+ {
562
+ name: "YOU FM",
563
+ genre: "Techno",
564
+ url: "https://hr-youfm-live.cast.addradio.de/hr/youfm/live/mp3/128/stream.mp3",
565
+ image: "https://picsum.photos/seed/youfm/300/180"
566
+ },
567
+ {
568
+ name: "Radio Paloma",
569
+ genre: "Pop",
570
+ url: "https://stream.radio-paloma.de/rp128.mp3",
571
+ image: "https://picsum.photos/seed/paloma/300/180"
572
+ },
573
+ {
574
+ name: "BLACK BEATS",
575
+ genre: "Techno",
576
+ url: "https://stream.blackbeatslive.de",
577
+ image: "https://picsum.photos/seed/blackbeats/300/180"
578
+ },
579
+ {
580
+ name: "Klassik Radio",
581
+ genre: "Rock",
582
+ url: "https://stream.klassikradio.de/live/mp3-128",
583
+ image: "https://picsum.photos/seed/klassik/300/180"
584
+ },
585
+ {
586
+ name: "B5 aktuell",
587
+ genre: "News",
588
+ url: "https://br-b5-aktuell-live.cast.addradio.de/br/b5/aktuell/live/mp3/128/stream.mp3",
589
+ image: "https://picsum.photos/seed/b5aktuell/300/180"
590
+ }
591
+ ];
592
+
593
+ // State
594
+ let currentStation = null;
595
+ let isPlaying = false;
596
+ let audio = new Audio();
597
+ audio.crossOrigin = "anonymous"; // Try to handle CORS for visuals if possible
598
+
599
+ // DOM Elements
600
+ const grid = document.getElementById('stationGrid');
601
+ const playerBar = document.getElementById('playerBar');
602
+ const playerTitle = document.getElementById('playerTitle');
603
+ const playerArt = document.getElementById('playerArt');
604
+ const mainPlayBtn = document.getElementById('mainPlayBtn');
605
+ const mainPlayIcon = document.getElementById('mainPlayIcon');
606
+ const visualizer = document.getElementById('visualizer');
607
+ const playerStateText = document.getElementById('playerStateText');
608
+
609
+ // Initialize
610
+ function init() {
611
+ renderStations(stations);
612
+
613
+ // Audio Error Handling
614
+ audio.onerror = () => {
615
+ console.error("Stream error");
616
+ playerStateText.innerText = "Streamfehler";
617
+ playerStateText.style.color = "#ef4444";
618
+ isPlaying = false;
619
+ updatePlayButton();
620
+ };
621
+
622
+ audio.onplaying = () => {
623
+ playerStateText.innerText = "Live";
624
+ playerStateText.style.color = "var(--accent)";
625
+ };
626
+ }
627
+
628
+ // Render Cards
629
+ function renderStations(data) {
630
+ grid.innerHTML = '';
631
+ data.forEach((station, index) => {
632
+ const card = document.createElement('div');
633
+ card.className = 'card';
634
+ card.onclick = () => loadStation(station, index);
635
+
636
+ // Check if this is the currently playing station
637
+ if(currentStation && currentStation.name === station.name) {
638
+ card.classList.add('playing');
639
+ }
640
+
641
+ card.innerHTML = `
642
+ <div class="card-image-wrapper">
643
+ <img src="${station.image}" alt="${station.name}" class="card-image">
644
+ <div class="card-overlay">
645
+ <div class="play-btn-overlay">
646
+ <span class="material-icons-round">play_arrow</span>
647
+ </div>
648
+ </div>
649
+ </div>
650
+ <div class="card-info">
651
+ <div>
652
+ <div class="station-genre">${station.genre}</div>
653
+ <div class="station-name">${station.name}</div>
654
+ </div>
655
+ <div class="station-meta">
656
+ <span class="material-icons-round" style="font-size:14px">headphones</span> 128 kbps
657
+ </div>
658
+ </div>
659
+ `;
660
+ grid.appendChild(card);
661
+ });
662
+ }
663
+
664
+ // Filter Logic
665
+ function filterStations(category) {
666
+ // Update buttons
667
+ document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
668
+ event.target.classList.add('active');
669
+
670
+ if (category === 'all') {
671
+ renderStations(stations);
672
+ } else {
673
+ const filtered = stations.filter(s => s.genre === category);
674
+ renderStations(filtered);
675
+ }
676
+ }
677
+
678
+ // Load Station
679
+ function loadStation(station, index) {
680
+ // UI Update: Highlight active card
681
+ document.querySelectorAll('.card').forEach(c => c.classList.remove('playing'));
682
+ // Re-rendering grid is expensive, but for this scale it ensures the class is correct after filters
683
+ // Alternatively, find the specific DOM element. Let's just re-render to keep state simple.
684
+ renderStations(
685
+ document.querySelector('.filter-btn.active').innerText === 'Alle'
686
+ ? stations
687
+ : stations.filter(s => s.genre === document.querySelector('.filter-btn.active').innerText)
688
+ );
689
+
690
+ // Highlight the clicked one manually to avoid flicker
691
+ const cards = document.querySelectorAll('.card');
692
+ if(cards[index]) cards[index].classList.add('playing');
693
+
694
+ // Audio Logic
695
+ if (currentStation && currentStation.url === station.url) {
696
+ togglePlay();
697
+ return;
698
+ }
699
+
700
+ currentStation = station;
701
+ playerTitle.innerText = station.name;
702
+ playerArt.src = station.image;
703
+
704
+ audio.src = station.url;
705
+ audio.load();
706
+ audio.play().then(() => {
707
+ isPlaying = true;
708
+ updatePlayButton();
709
+ playerBar.classList.add('active');
710
+ }).catch(e => {
711
+ console.log("Auto-play prevented or error", e);
712
+ isPlaying = false;
713
+ updatePlayButton();
714
+ playerBar.classList.add('active'); // Show player even on error to indicate selection
715
+ });
716
+ }
717
+
718
+ // Toggle Play/Pause
719
+ function togglePlay() {
720
+ if (!currentStation) return;
721
+
722
+ if (isPlaying) {
723
+ audio.pause();
724
+ isPlaying = false;
725
+ } else {
726
+ audio.play();
727
+ isPlaying = true;
728
+ }
729
+ updatePlayButton();
730
+ }
731
+
732
+ // Stop
733
+ function stopRadio() {
734
+ audio.pause();
735
+ audio.currentTime = 0;
736
+ isPlaying = false;
737
+ updatePlayButton();
738
+ playerBar.classList.remove('active');
739
+ document.querySelectorAll('.card').forEach(c => c.classList.remove('playing'));
740
+ currentStation = null;
741
+ }
742
+
743
+ // Volume
744
+ function setVolume(val) {
745
+ audio.volume = val;
746
+ }
747
+
748
+ // Update Icons & Visualizer
749
+ function updatePlayButton() {
750
+ if (isPlaying) {
751
+ mainPlayIcon.innerText = 'pause';
752
+ visualizer.classList.remove('paused');
753
+ } else {
754
+ mainPlayIcon.innerText = 'play_arrow';
755
+ visualizer.classList.add('paused');
756
+ }
757
+ }
758
+
759
+ // Run Init
760
+ init();
761
+
762
+ </script>
763
+ </body>
764
+ </html>