Multimedix commited on
Commit
63c86ce
·
verified ·
1 Parent(s): 8fe09c4

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +976 -1112
index.html CHANGED
@@ -1,1131 +1,995 @@
1
  <!DOCTYPE html>
2
  <html lang="de">
3
-
4
  <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Radio Player Deutschland</title>
8
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
- <style>
10
- * {
11
- margin: 0;
12
- padding: 0;
13
- box-sizing: border-box;
14
- }
15
-
16
- :root {
17
- --primary-color: #1e3c72;
18
- --secondary-color: #2a5298;
19
- --accent-color: #ff6b6b;
20
- --text-light: #ffffff;
21
- --text-dark: #333333;
22
- --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
23
- --card-bg: rgba(255, 255, 255, 0.1);
24
- --glass-bg: rgba(255, 255, 255, 0.1);
25
- --glass-border: rgba(255, 255, 255, 0.2);
26
- --success-color: #4caf50;
27
- --error-color: #f44336;
28
- }
29
-
30
- body {
31
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
32
- background: var(--bg-gradient);
33
- min-height: 100vh;
34
- color: var(--text-light);
35
- overflow-x: hidden;
36
- position: relative;
37
- }
38
-
39
- /* Animated Background */
40
- .bg-animation {
41
- position: fixed;
42
- width: 100%;
43
- height: 100%;
44
- top: 0;
45
- left: 0;
46
- z-index: -1;
47
- opacity: 0.3;
48
- }
49
-
50
- .bg-animation span {
51
- position: absolute;
52
- display: block;
53
- width: 20px;
54
- height: 20px;
55
- background: rgba(255, 255, 255, 0.2);
56
- animation: move 25s linear infinite;
57
- bottom: -150px;
58
- }
59
-
60
- .bg-animation span:nth-child(1) {
61
- left: 25%;
62
- width: 80px;
63
- height: 80px;
64
- animation-delay: 0s;
65
- }
66
-
67
- .bg-animation span:nth-child(2) {
68
- left: 10%;
69
- width: 20px;
70
- height: 20px;
71
- animation-delay: 2s;
72
- animation-duration: 12s;
73
- }
74
-
75
- .bg-animation span:nth-child(3) {
76
- left: 70%;
77
- width: 20px;
78
- height: 20px;
79
- animation-delay: 4s;
80
- }
81
-
82
- .bg-animation span:nth-child(4) {
83
- left: 40%;
84
- width: 60px;
85
- height: 60px;
86
- animation-delay: 0s;
87
- animation-duration: 18s;
88
- }
89
-
90
- .bg-animation span:nth-child(5) {
91
- left: 65%;
92
- width: 20px;
93
- height: 20px;
94
- animation-delay: 0s;
95
- }
96
-
97
- .bg-animation span:nth-child(6) {
98
- left: 75%;
99
- width: 110px;
100
- height: 110px;
101
- animation-delay: 3s;
102
- }
103
-
104
- .bg-animation span:nth-child(7) {
105
- left: 35%;
106
- width: 150px;
107
- height: 150px;
108
- animation-delay: 7s;
109
- }
110
-
111
- .bg-animation span:nth-child(8) {
112
- left: 50%;
113
- width: 25px;
114
- height: 25px;
115
- animation-delay: 15s;
116
- animation-duration: 45s;
117
- }
118
-
119
- .bg-animation span:nth-child(9) {
120
- left: 20%;
121
- width: 15px;
122
- height: 15px;
123
- animation-delay: 2s;
124
- animation-duration: 35s;
125
- }
126
-
127
- .bg-animation span:nth-child(10) {
128
- left: 85%;
129
- width: 150px;
130
- height: 150px;
131
- animation-delay: 0s;
132
- animation-duration: 11s;
133
- }
134
-
135
- @keyframes move {
136
- 0% {
137
- transform: translateY(0) rotate(0deg);
138
- opacity: 1;
139
- border-radius: 0;
140
- }
141
-
142
- 100% {
143
- transform: translateY(-1000px) rotate(720deg);
144
- opacity: 0;
145
- border-radius: 50%;
146
- }
147
- }
148
-
149
- /* Header */
150
- header {
151
- background: var(--glass-bg);
152
- backdrop-filter: blur(10px);
153
- border-bottom: 1px solid var(--glass-border);
154
- padding: 1rem 0;
155
- position: sticky;
156
- top: 0;
157
- z-index: 100;
158
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
159
- }
160
-
161
- .header-content {
162
- max-width: 1200px;
163
- margin: 0 auto;
164
- padding: 0 2rem;
165
- display: flex;
166
- justify-content: space-between;
167
- align-items: center;
168
- }
169
-
170
- .logo {
171
- display: flex;
172
- align-items: center;
173
- gap: 1rem;
174
- font-size: 1.5rem;
175
- font-weight: bold;
176
- }
177
-
178
- .logo i {
179
- font-size: 2rem;
180
- color: var(--accent-color);
181
- animation: pulse 2s infinite;
182
- }
183
-
184
- @keyframes pulse {
185
-
186
- 0%,
187
- 100% {
188
- transform: scale(1);
189
- }
190
-
191
- 50% {
192
- transform: scale(1.1);
193
- }
194
- }
195
-
196
- .header-links a {
197
- color: var(--text-light);
198
- text-decoration: none;
199
- margin-left: 2rem;
200
- transition: color 0.3s;
201
- }
202
-
203
- .header-links a:hover {
204
- color: var(--accent-color);
205
- }
206
-
207
- /* Main Container */
208
- .container {
209
- max-width: 1200px;
210
- margin: 2rem auto;
211
- padding: 0 2rem;
212
- }
213
-
214
- /* Main Player */
215
- .main-player {
216
- background: var(--glass-bg);
217
- backdrop-filter: blur(10px);
218
- border-radius: 20px;
219
- padding: 2rem;
220
- margin-bottom: 2rem;
221
- border: 1px solid var(--glass-border);
222
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
223
- }
224
-
225
- .player-header {
226
- text-align: center;
227
- margin-bottom: 2rem;
228
- }
229
-
230
- .station-info {
231
- display: flex;
232
- align-items: center;
233
- justify-content: center;
234
- gap: 1rem;
235
- margin-bottom: 1rem;
236
- }
237
-
238
- .station-logo {
239
- width: 80px;
240
- height: 80px;
241
- border-radius: 50%;
242
- background: linear-gradient(135deg, #667eea, #764ba2);
243
- display: flex;
244
- align-items: center;
245
- justify-content: center;
246
- font-size: 2rem;
247
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
248
- }
249
-
250
- .station-details h2 {
251
- font-size: 1.8rem;
252
- margin-bottom: 0.5rem;
253
- }
254
-
255
- .station-details p {
256
- opacity: 0.8;
257
- }
258
-
259
- /* Connection Status */
260
- .connection-status {
261
- display: inline-flex;
262
- align-items: center;
263
- gap: 0.5rem;
264
- padding: 0.5rem 1rem;
265
- border-radius: 20px;
266
- font-size: 0.9rem;
267
- margin-top: 0.5rem;
268
- background: rgba(255, 255, 255, 0.1);
269
- }
270
-
271
- .connection-status.connected {
272
- background: rgba(76, 175, 80, 0.2);
273
- color: var(--success-color);
274
- }
275
-
276
- .connection-status.connecting {
277
- background: rgba(255, 193, 7, 0.2);
278
- color: #ffc107;
279
- }
280
-
281
- .connection-status.error {
282
- background: rgba(244, 67, 54, 0.2);
283
- color: var(--error-color);
284
- }
285
-
286
- .status-dot {
287
- width: 8px;
288
- height: 8px;
289
- border-radius: 50%;
290
- background: currentColor;
291
- animation: blink 1.5s infinite;
292
- }
293
-
294
- @keyframes blink {
295
- 0%, 100% { opacity: 1; }
296
- 50% { opacity: 0.3; }
297
- }
298
-
299
- /* Visualizer */
300
- .visualizer {
301
- height: 100px;
302
- display: flex;
303
- align-items: flex-end;
304
- justify-content: center;
305
- gap: 4px;
306
- margin: 2rem 0;
307
- }
308
-
309
- .bar {
310
- width: 4px;
311
- background: linear-gradient(to top, var(--accent-color), #feca57);
312
- border-radius: 2px;
313
- animation: dance 0.6s ease-in-out infinite;
314
- }
315
-
316
- .bar:nth-child(1) {
317
- animation-delay: 0s;
318
- height: 20px;
319
- }
320
-
321
- .bar:nth-child(2) {
322
- animation-delay: 0.1s;
323
- height: 30px;
324
- }
325
-
326
- .bar:nth-child(3) {
327
- animation-delay: 0.2s;
328
- height: 25px;
329
- }
330
-
331
- .bar:nth-child(4) {
332
- animation-delay: 0.3s;
333
- height: 40px;
334
- }
335
-
336
- .bar:nth-child(5) {
337
- animation-delay: 0.4s;
338
- height: 35px;
339
- }
340
-
341
- .bar:nth-child(6) {
342
- animation-delay: 0.5s;
343
- height: 45px;
344
- }
345
-
346
- .bar:nth-child(7) {
347
- animation-delay: 0.6s;
348
- height: 30px;
349
- }
350
-
351
- .bar:nth-child(8) {
352
- animation-delay: 0.7s;
353
- height: 25px;
354
- }
355
-
356
- .bar:nth-child(9) {
357
- animation-delay: 0.8s;
358
- height: 35px;
359
- }
360
-
361
- .bar:nth-child(10) {
362
- animation-delay: 0.9s;
363
- height: 40px;
364
- }
365
-
366
- .bar:nth-child(11) {
367
- animation-delay: 1s;
368
- height: 30px;
369
- }
370
-
371
- .bar:nth-child(12) {
372
- animation-delay: 1.1s;
373
- height: 25px;
374
- }
375
-
376
- .visualizer.paused .bar {
377
- animation-play-state: paused;
378
- opacity: 0.3;
379
- }
380
-
381
- @keyframes dance {
382
-
383
- 0%,
384
- 100% {
385
- transform: scaleY(1);
386
- }
387
-
388
- 50% {
389
- transform: scaleY(1.5);
390
- }
391
- }
392
-
393
- /* Controls */
394
- .controls {
395
- display: flex;
396
- justify-content: center;
397
- align-items: center;
398
- gap: 2rem;
399
- margin: 2rem 0;
400
- }
401
-
402
- .control-btn {
403
- background: rgba(255, 255, 255, 0.1);
404
- border: 2px solid var(--glass-border);
405
- color: var(--text-light);
406
- width: 60px;
407
- height: 60px;
408
- border-radius: 50%;
409
- display: flex;
410
- align-items: center;
411
- justify-content: center;
412
- cursor: pointer;
413
- transition: all 0.3s;
414
- font-size: 1.2rem;
415
- position: relative;
416
- }
417
-
418
- .control-btn:hover {
419
- background: rgba(255, 255, 255, 0.2);
420
- transform: scale(1.1);
421
- }
422
-
423
- .control-btn.play-pause {
424
- width: 80px;
425
- height: 80px;
426
- background: var(--accent-color);
427
- border-color: var(--accent-color);
428
- font-size: 1.5rem;
429
- }
430
-
431
- .control-btn.play-pause:hover {
432
- background: #ff5252;
433
- border-color: #ff5252;
434
- }
435
-
436
- .control-btn.loading {
437
- pointer-events: none;
438
- opacity: 0.7;
439
- }
440
-
441
- .control-btn.loading::after {
442
- content: '';
443
- position: absolute;
444
- width: 20px;
445
- height: 20px;
446
- border: 2px solid transparent;
447
- border-top-color: white;
448
- border-radius: 50%;
449
- animation: spin 1s linear infinite;
450
- }
451
-
452
- @keyframes spin {
453
- to { transform: rotate(360deg); }
454
- }
455
-
456
- /* Volume Control */
457
- .volume-control {
458
- display: flex;
459
- align-items: center;
460
- gap: 1rem;
461
- justify-content: center;
462
- margin-top: 1rem;
463
- }
464
-
465
- .volume-slider {
466
- width: 200px;
467
- height: 6px;
468
- background: rgba(255, 255, 255, 0.2);
469
- border-radius: 3px;
470
- outline: none;
471
- -webkit-appearance: none;
472
- }
473
-
474
- .volume-slider::-webkit-slider-thumb {
475
- -webkit-appearance: none;
476
- width: 20px;
477
- height: 20px;
478
- background: var(--accent-color);
479
- border-radius: 50%;
480
- cursor: pointer;
481
- }
482
-
483
- .volume-slider::-moz-range-thumb {
484
- width: 20px;
485
- height: 20px;
486
- background: var(--accent-color);
487
- border-radius: 50%;
488
- cursor: pointer;
489
- border: none;
490
- }
491
-
492
- /* Station Grid */
493
- .stations-grid {
494
- display: grid;
495
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
496
- gap: 1.5rem;
497
- margin-bottom: 2rem;
498
- }
499
-
500
- .station-card {
501
- background: var(--glass-bg);
502
- backdrop-filter: blur(10px);
503
- border: 1px solid var(--glass-border);
504
- border-radius: 15px;
505
- padding: 1.5rem;
506
- cursor: pointer;
507
- transition: all 0.3s;
508
- position: relative;
509
- overflow: hidden;
510
- }
511
-
512
- .station-card::before {
513
- content: '';
514
- position: absolute;
515
- top: 0;
516
- left: -100%;
517
- width: 100%;
518
- height: 100%;
519
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
520
- transition: left 0.5s;
521
- }
522
-
523
- .station-card:hover::before {
524
- left: 100%;
525
- }
526
-
527
- .station-card:hover {
528
- transform: translateY(-5px);
529
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
530
- background: rgba(255, 255, 255, 0.15);
531
- }
532
-
533
- .station-card.active {
534
- background: rgba(255, 107, 107, 0.2);
535
- border-color: var(--accent-color);
536
- }
537
-
538
- .station-card.error {
539
- border-color: var(--error-color);
540
- background: rgba(244, 67, 54, 0.1);
541
- }
542
-
543
- .station-card-header {
544
- display: flex;
545
- align-items: center;
546
- gap: 1rem;
547
- margin-bottom: 1rem;
548
- }
549
-
550
- .station-card-logo {
551
- width: 50px;
552
- height: 50px;
553
- border-radius: 10px;
554
- background: linear-gradient(135deg, #667eea, #764ba2);
555
- display: flex;
556
- align-items: center;
557
- justify-content: center;
558
- font-size: 1.5rem;
559
- }
560
-
561
- .station-card-info h3 {
562
- font-size: 1.2rem;
563
- margin-bottom: 0.25rem;
564
- }
565
-
566
- .station-card-info p {
567
- font-size: 0.9rem;
568
- opacity: 0.8;
569
- }
570
-
571
- .station-genre {
572
- display: inline-block;
573
- background: rgba(255, 255, 255, 0.1);
574
- padding: 0.25rem 0.75rem;
575
- border-radius: 20px;
576
- font-size: 0.85rem;
577
- margin-top: 0.5rem;
578
- }
579
-
580
- .station-status {
581
- position: absolute;
582
- top: 10px;
583
- right: 10px;
584
- width: 8px;
585
- height: 8px;
586
- border-radius: 50%;
587
- background: #4caf50;
588
- }
589
-
590
- .station-status.error {
591
- background: var(--error-color);
592
- }
593
-
594
- /* Categories */
595
- .categories {
596
- display: flex;
597
- gap: 1rem;
598
- margin-bottom: 2rem;
599
- flex-wrap: wrap;
600
- }
601
-
602
- .category-btn {
603
- background: var(--glass-bg);
604
- backdrop-filter: blur(10px);
605
- border: 1px solid var(--glass-border);
606
- color: var(--text-light);
607
- padding: 0.75rem 1.5rem;
608
- border-radius: 25px;
609
- cursor: pointer;
610
- transition: all 0.3s;
611
- font-size: 0.95rem;
612
- }
613
-
614
- .category-btn:hover {
615
- background: rgba(255, 255, 255, 0.2);
616
- transform: translateY(-2px);
617
- }
618
-
619
- .category-btn.active {
620
- background: var(--accent-color);
621
- border-color: var(--accent-color);
622
- }
623
-
624
- /* Footer */
625
- footer {
626
- background: var(--glass-bg);
627
- backdrop-filter: blur(10px);
628
- border-top: 1px solid var(--glass-border);
629
- padding: 2rem 0;
630
- margin-top: 3rem;
631
- text-align: center;
632
- }
633
-
634
- .footer-content {
635
- max-width: 1200px;
636
- margin: 0 auto;
637
- padding: 0 2rem;
638
- }
639
-
640
- .social-links {
641
- display: flex;
642
- justify-content: center;
643
- gap: 1.5rem;
644
- margin-top: 1rem;
645
- }
646
-
647
- .social-links a {
648
- color: var(--text-light);
649
- font-size: 1.5rem;
650
- transition: color 0.3s, transform 0.3s;
651
- }
652
-
653
- .social-links a:hover {
654
- color: var(--accent-color);
655
- transform: translateY(-3px);
656
- }
657
-
658
- /* Responsive Design */
659
- @media (max-width: 768px) {
660
- .header-content {
661
- flex-direction: column;
662
- gap: 1rem;
663
- }
664
-
665
- .header-links {
666
- display: none;
667
- }
668
-
669
- .stations-grid {
670
- grid-template-columns: 1fr;
671
- }
672
-
673
- .controls {
674
- gap: 1rem;
675
- }
676
-
677
- .control-btn {
678
- width: 50px;
679
- height: 50px;
680
- }
681
-
682
- .control-btn.play-pause {
683
- width: 70px;
684
- height: 70px;
685
- }
686
-
687
- .volume-slider {
688
- width: 150px;
689
- }
690
- }
691
-
692
- /* Toast Notification */
693
- .toast {
694
- position: fixed;
695
- bottom: 2rem;
696
- right: 2rem;
697
- background: rgba(0, 0, 0, 0.8);
698
- color: white;
699
- padding: 1rem 1.5rem;
700
- border-radius: 10px;
701
- transform: translateX(400px);
702
- transition: transform 0.3s;
703
- z-index: 1000;
704
- max-width: 300px;
705
- }
706
-
707
- .toast.show {
708
- transform: translateX(0);
709
- }
710
-
711
- .toast.error {
712
- background: var(--error-color);
713
- }
714
-
715
- .toast.success {
716
- background: var(--success-color);
717
- }
718
-
719
- /* Fallback Player Info */
720
- .fallback-info {
721
- background: rgba(255, 193, 7, 0.1);
722
- border: 1px solid rgba(255, 193, 7, 0.3);
723
- border-radius: 10px;
724
- padding: 1rem;
725
- margin-top: 1rem;
726
- text-align: center;
727
- font-size: 0.9rem;
728
- }
729
- </style>
730
- </head>
731
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
732
  <body>
733
- <!-- Animated Background -->
734
- <div class="bg-animation">
735
- <span></span>
736
- <span></span>
737
- <span></span>
738
- <span></span>
739
- <span></span>
740
- <span></span>
741
- <span></span>
742
- <span></span>
743
- <span></span>
744
- <span></span>
745
- </div>
746
-
747
- <!-- Header -->
748
- <header>
749
- <div class="header-content">
750
- <div class="logo">
751
- <i class="fas fa-radio"></i>
752
- <span>Radio Player Deutschland</span>
753
- </div>
754
- <div class="header-links">
755
- <a href="#" onclick="showToast('Startseite', 'success'); return false;">Start</a>
756
- <a href="#" onclick="showToast('Beliebte Sender', 'success'); return false;">Beliebt</a>
757
- <a href="#" onclick="showToast('Neue Sender', 'success'); return false;">Neu</a>
758
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
759
- </div>
760
- </div>
761
- </header>
762
-
763
- <!-- Main Container -->
764
- <div class="container">
765
- <!-- Main Player -->
766
- <div class="main-player">
767
- <div class="player-header">
768
- <div class="station-info">
769
- <div class="station-logo">
770
- <i class="fas fa-broadcast-tower"></i>
771
- </div>
772
- <div class="station-details">
773
- <h2 id="currentStation">Wähle einen Sender</h2>
774
- <p id="currentGenre">Genre</p>
775
- <div class="connection-status" id="connectionStatus">
776
- <span class="status-dot"></span>
777
- <span id="statusText">Bereit</span>
778
  </div>
779
- </div>
780
- </div>
781
- </div>
782
-
783
- <!-- Visualizer -->
784
- <div class="visualizer paused" id="visualizer">
785
- <div class="bar"></div>
786
- <div class="bar"></div>
787
- <div class="bar"></div>
788
- <div class="bar"></div>
789
- <div class="bar"></div>
790
- <div class="bar"></div>
791
- <div class="bar"></div>
792
- <div class="bar"></div>
793
- <div class="bar"></div>
794
- <div class="bar"></div>
795
- <div class="bar"></div>
796
- <div class="bar"></div>
797
- </div>
798
-
799
- <!-- Controls -->
800
- <div class="controls">
801
- <button class="control-btn" onclick="previousStation()">
802
- <i class="fas fa-backward"></i>
803
- </button>
804
- <button class="control-btn" onclick="toggleMute()">
805
- <i class="fas fa-volume-down"></i>
806
  </button>
807
- <button class="control-btn play-pause" id="playPauseBtn" onclick="togglePlayPause()">
808
- <i class="fas fa-play"></i>
809
  </button>
810
- <button class="control-btn" onclick="toggleMute()">
811
- <i class="fas fa-volume-up"></i>
812
  </button>
813
- <button class="control-btn" onclick="nextStation()">
814
- <i class="fas fa-forward"></i>
 
 
 
815
  </button>
816
- </div>
817
-
818
- <!-- Volume Control -->
819
- <div class="volume-control">
820
- <i class="fas fa-volume-down"></i>
821
- <input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="70">
822
- <i class="fas fa-volume-up"></i>
823
- <span style="margin-left: 1rem;" id="volumeValue">70%</span>
824
- </div>
825
-
826
- <div class="fallback-info" id="fallbackInfo" style="display: none;">
827
- <i class="fas fa-info-circle"></i> Simulierter Modus - Die Radio-Streams werden simuliert
828
- </div>
829
- </div>
830
 
831
- <!-- Categories -->
832
- <div class="categories">
833
- <button class="category-btn active" onclick="filterStations('all')">Alle</button>
834
- <button class="category-btn" onclick="filterStations('pop')">Pop</button>
835
- <button class="category-btn" onclick="filterStations('rock')">Rock</button>
836
- <button class="category-btn" onclick="filterStations('jazz')">Jazz</button>
837
- <button class="category-btn" onclick="filterStations('klassik')">Klassik</button>
838
- <button class="category-btn" onclick="filterStations('news')">Nachrichten</button>
839
  </div>
840
 
841
- <!-- Stations Grid -->
842
- <div class="stations-grid" id="stationsGrid">
843
- <!-- Stations will be dynamically added here -->
844
- </div>
845
- </div>
846
-
847
- <!-- Footer -->
848
- <footer>
849
- <div class="footer-content">
850
- <p>&copy; 2024 Radio Player Deutschland. Alle Rechte vorbehalten.</p>
851
- <div class="social-links">
852
- <a href="#" onclick="showToast('Facebook', 'success'); return false;"><i class="fab fa-facebook"></i></a>
853
- <a href="#" onclick="showToast('Twitter', 'success'); return false;"><i class="fab fa-twitter"></i></a>
854
- <a href="#" onclick="showToast('Instagram', 'success'); return false;"><i class="fab fa-instagram"></i></a>
855
- <a href="#" onclick="showToast('YouTube', 'success'); return false;"><i class="fab fa-youtube"></i></a>
856
- </div>
857
- </div>
858
- </footer>
859
-
860
- <!-- Toast Notification -->
861
- <div class="toast" id="toast"></div>
862
-
863
- <!-- Audio Element -->
864
- <audio id="audioPlayer" crossorigin="anonymous"></audio>
865
-
866
- <script>
867
- // Radio Stations Data mit alternativen URLs
868
- const stations = [
869
- {
870
- id: 1,
871
- name: 'Deutschlandfunk',
872
- genre: 'Nachrichten',
873
- url: 'https://st01.dlf.de/dlf/01/128/mp3/stream.mp3',
874
- fallbackUrl: 'https://icecastmdr-ice.akamaized.net/mdr/128k/mp3/live',
875
- category: 'news',
876
- icon: '📻'
877
- },
878
- {
879
- id: 2,
880
- name: 'Bayern 3',
881
- genre: 'Pop',
882
- url: 'https://br-br3-live.cast.addradio.de/br/br3/live/mp3/128/stream.mp3',
883
- fallbackUrl: 'https://mp3channels.webradio.antenne.de/antenne-bayern',
884
- category: 'pop',
885
- icon: '🎵'
886
- },
887
- {
888
- id: 3,
889
- name: 'WDR 4',
890
- genre: 'Schlager',
891
- url: 'https://wdr-4-live.icecast.wdr.de/wdr/wdr4/live/mp3/128/stream.mp3',
892
- fallbackUrl: 'https://mp3channels.webradio.antennekempten.de/antenne-kempten',
893
- category: 'pop',
894
- icon: '🎶'
895
- },
896
- {
897
- id: 4,
898
- name: 'NDR 2',
899
- genre: 'Pop/Rock',
900
- url: 'https://ndr-ndr2-niedersachsen.cast.addradio.de/ndr/ndr2/niedersachsen/mp3/128/stream.mp3',
901
- fallbackUrl: 'https://stream.radiohamburg.de/rhh-live/mp3-128',
902
- category: 'rock',
903
- icon: '🎸'
904
- },
905
- {
906
- id: 5,
907
- name: 'SWR3',
908
- genre: 'Pop',
909
- url: 'https://swr-swr3-live.cast.addradio.de/swr/swr3/live/mp3/128/stream.mp3',
910
- fallbackUrl: 'https://mp3.ffh.de/radioffh/hqlivestream.mp3',
911
- category: 'pop',
912
- icon: '🎼'
913
- },
914
- {
915
- id: 6,
916
- name: 'HR3',
917
- genre: 'Pop',
918
- url: 'https://hr-hr3-live.cast.addradio.de/hr/hr3/live/mp3/128/stream.mp3',
919
- fallbackUrl: 'https://streams.rpr1.de/rpr-mp3-128',
920
- category: 'pop',
921
- icon: '🎤'
922
- },
923
- {
924
- id: 7,
925
- name: 'MDR Jump',
926
- genre: 'Rock/Pop',
927
- url: 'https://mdr-jump-live.cast.addradio.de/mdr/mdr-jump/live/mp3/128/stream.mp3',
928
- fallbackUrl: 'https://stream.rockantenne.de/rockantenne',
929
- category: 'rock',
930
- icon: '🎧'
931
- },
932
- {
933
- id: 8,
934
- name: 'RPR1',
935
- genre: 'Pop',
936
- url: 'https://streams.rpr1.de/rpr-mp3-128',
937
- fallbackUrl: 'https://mp3channels.webradio.antenne.de/antenne-bayern',
938
- category: 'pop',
939
- icon: '🎹'
940
- },
941
- {
942
- id: 9,
943
- name: 'Radio Bremen',
944
- genre: 'Nachrichten',
945
- url: 'https://rb-media.rb-radio.de/rb/bremen/live/mp3/128/stream.mp3',
946
- fallbackUrl: 'https://st01.dlf.de/dlf/01/128/mp3/stream.mp3',
947
- category: 'news',
948
- icon: '📰'
949
- },
950
- {
951
- id: 10,
952
- name: 'SR 1',
953
- genre: 'Pop',
954
- url: 'https://sr-sr1-live.cast.addradio.de/sr/sr1/live/mp3/128/stream.mp3',
955
- fallbackUrl: 'https://stream.sunshine-live.de/live/mp3-256',
956
- category: 'pop',
957
- icon: '🎺'
958
- },
959
- {
960
- id: 11,
961
- name: 'JazzRadio',
962
- genre: 'Jazz',
963
- url: 'http://stream.jazzradio.de/jazzradio/mp3-192',
964
- fallbackUrl: 'https://stream.lounge-radio.com/mp3',
965
- category: 'jazz',
966
- icon: '🎷'
967
- },
968
- {
969
- id: 12,
970
- name: 'BR-Klassik',
971
- genre: 'Klassik',
972
- url: 'https://br-brklassik-live.cast.addradio.de/br/brklassik/live/mp3/128/stream.mp3',
973
- fallbackUrl: 'https://mp3stream.klassikradio.de/klassikradio',
974
- category: 'klassik',
975
- icon: '🎻'
976
- },
977
- {
978
- id: 13,
979
- name: 'DLF Kultur',
980
- genre: 'Kultur',
981
- url: 'https://st02.dlf.de/dlf/02/128/mp3/stream.mp3',
982
- fallbackUrl: 'https://st01.dlf.de/dlf/01/128/mp3/stream.mp3',
983
- category: 'news',
984
- icon: '🎭'
985
- },
986
- {
987
- id: 14,
988
- name: 'Antenne Bayern',
989
- genre: 'Pop',
990
- url: 'https://mp3channels.webradio.antenne.de/antenne-bayern',
991
- fallbackUrl: 'https://mp3.ffh.de/radioffh/hqlivestream.mp3',
992
- category: 'pop',
993
- icon: '📡'
994
- },
995
- {
996
- id: 15,
997
- name: 'Radio Hamburg',
998
- genre: 'Pop',
999
- url: 'https://stream.radiohamburg.de/rhh-live/mp3-128',
1000
- fallbackUrl: 'https://mp3channels.webradio.antenne.de/antenne-bayern',
1001
- category: 'pop',
1002
- icon: ''
1003
- },
1004
- {
1005
- id: 16,
1006
- name: 'FFH',
1007
- genre: 'Pop',
1008
- url: 'https://mp3.ffh.de/radioffh/hqlivestream.mp3',
1009
- fallbackUrl: 'https://streams.rpr1.de/rpr-mp3-128',
1010
- category: 'pop',
1011
- icon: '🎪'
1012
- }
1013
- ];
1014
-
1015
- let currentStationIndex = -1;
1016
- let isPlaying = false;
1017
- let currentCategory = 'all';
1018
- let isFallbackMode = false;
1019
- let simulationInterval = null;
1020
-
1021
- // Initialize
1022
- document.addEventListener('DOMContentLoaded', function() {
1023
- renderStations();
1024
- setupVolumeControl();
1025
- setupAudioEvents();
1026
- });
1027
-
1028
- // Setup Audio Events
1029
- function setupAudioEvents() {
1030
- const audioPlayer = document.getElementById('audioPlayer');
1031
-
1032
- audioPlayer.addEventListener('loadstart', () => {
1033
- updateConnectionStatus('connecting', 'Verbinden...');
1034
- });
1035
-
1036
- audioPlayer.addEventListener('canplay', () => {
1037
- updateConnectionStatus('connected', 'Verbunden');
1038
- if (isPlaying) {
1039
- audioPlayer.play();
1040
  }
1041
- });
1042
-
1043
- audioPlayer.addEventListener('error', (e) => {
1044
- handleAudioError(e);
1045
- });
1046
- }
 
 
 
1047
 
1048
- // Handle Audio Error
1049
- function handleAudioError(e) {
1050
  const audioPlayer = document.getElementById('audioPlayer');
1051
-
1052
- if (!isFallbackMode && currentStationIndex >= 0) {
1053
- // Versuche Fallback-URL
1054
- const station = stations[currentStationIndex];
1055
- if (station.fallbackUrl && audioPlayer.src !== station.fallbackUrl) {
1056
- showToast('Hauptstream nicht erreichbar, versuche Alternative...', 'error');
1057
- audioPlayer.src = station.fallbackUrl;
1058
- audioPlayer.load();
1059
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1060
  }
1061
  }
1062
-
1063
- // Wenn auch Fallback nicht funktioniert, aktiviere Simulationsmodus
1064
- if (!isFallbackMode) {
1065
- isFallbackMode = true;
1066
- document.getElementById('fallbackInfo').style.display = 'block';
1067
- updateConnectionStatus('connected', 'Simulationsmodus');
1068
- startSimulation();
1069
- showToast('Streams nicht verfügbar - Simulationsmodus aktiviert', 'error');
1070
- }
1071
- }
1072
-
1073
- // Start Simulation
1074
- function startSimulation() {
1075
- const visualizer = document.getElementById('visualizer');
1076
- visualizer.classList.remove('paused');
1077
-
1078
- simulationInterval = setInterval(() => {
1079
- if (isPlaying) {
1080
- // Simuliere wechselnde Song-Infos
1081
- const songs = [
1082
- '🎵 Aktuelles Lied - Künstler',
1083
- '🎶 Nächster Hit - Bandname',
1084
- '🎤 Klassiker - Legendärer Künstler',
1085
- '🎸 Rock Hymne - Famous Band'
1086
- ];
1087
- const randomSong = songs[Math.floor(Math.random() * songs.length)];
1088
- document.getElementById('currentGenre').textContent = randomSong;
1089
- }
1090
- }, 5000);
1091
- }
1092
-
1093
- // Update Connection Status
1094
- function updateConnectionStatus(status, text) {
1095
- const statusEl = document.getElementById('connectionStatus');
1096
- const statusText = document.getElementById('statusText');
1097
-
1098
- statusEl.className = `connection-status ${status}`;
1099
- statusText.textContent = text;
1100
- }
1101
-
1102
- // Render Stations
1103
- function renderStations() {
1104
- const grid = document.getElementById('stationsGrid');
1105
- grid.innerHTML = '';
1106
-
1107
- const filteredStations = currentCategory === 'all'
1108
- ? stations
1109
- : stations.filter(s => s.category === currentCategory);
1110
-
1111
- filteredStations.forEach((station, index) => {
1112
- const card = document.createElement('div');
1113
- card.className = 'station-card';
1114
- card.innerHTML = `
1115
- <div class="station-status"></div>
1116
- <div class="station-card-header">
1117
- <div class="station-card-logo">${station.icon}</div>
1118
- <div class="station-card-info">
1119
- <h3>${station.name}</h3>
1120
- <p>${station.genre}</p>
1121
- </div>
1122
- </div>
1123
- <span class="station-genre">${station.category}</span>
1124
- `;
1125
- card.onclick = () => selectStation(station);
1126
- grid.appendChild(card);
1127
- });
1128
- }
1129
 
1130
- // Select Station
1131
- function selectStation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Deutsches Online Radio | Öffentlich-rechtliche Sender</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <style>
9
+ /* Modern CSS Reset & Custom Properties */
10
+ :root {
11
+ --primary-color: #0a0a1a;
12
+ --secondary-color: #1a1a2e;
13
+ --accent-color: #00d4ff;
14
+ --text-primary: #ffffff;
15
+ --text-secondary: #b0b0d0;
16
+ --glass-bg: rgba(255, 255, 255, 0.08);
17
+ --glass-border: rgba(255, 255, 255, 0.1);
18
+ --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
19
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
20
+ --gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
21
+ --error-color: #ff4757;
22
+ --success-color: #2ed573;
23
+ }
24
+
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ body {
32
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
33
+ background: var(--primary-color);
34
+ color: var(--text-primary);
35
+ min-height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ line-height: 1.6;
39
+ overflow-x: hidden;
40
+ }
41
+
42
+ /* Header */
43
+ header {
44
+ background: var(--glass-bg);
45
+ backdrop-filter: blur(12px);
46
+ -webkit-backdrop-filter: blur(12px);
47
+ border-bottom: 1px solid var(--glass-border);
48
+ padding: 1.5rem 2rem;
49
+ display: flex;
50
+ justify-content: space-between;
51
+ align-items: center;
52
+ position: sticky;
53
+ top: 0;
54
+ z-index: 100;
55
+ box-shadow: var(--glass-shadow);
56
+ }
57
+
58
+ header h1 {
59
+ font-size: clamp(1.2rem, 4vw, 1.8rem);
60
+ background: var(--gradient);
61
+ -webkit-background-clip: text;
62
+ -webkit-text-fill-color: transparent;
63
+ background-clip: text;
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.5rem;
67
+ }
68
+
69
+ .anycoder-link {
70
+ color: var(--accent-color);
71
+ text-decoration: none;
72
+ font-size: 0.9rem;
73
+ padding: 0.5rem 1rem;
74
+ border-radius: 20px;
75
+ background: var(--glass-bg);
76
+ border: 1px solid var(--glass-border);
77
+ transition: var(--transition);
78
+ }
79
+
80
+ .anycoder-link:hover {
81
+ background: rgba(0, 212, 255, 0.1);
82
+ transform: translateY(-2px);
83
+ }
84
+
85
+ /* Main Content */
86
+ main {
87
+ flex: 1;
88
+ padding: 2rem;
89
+ max-width: 1400px;
90
+ margin: 0 auto;
91
+ width: 100%;
92
+ }
93
+
94
+ /* Search Section */
95
+ .search-section {
96
+ margin-bottom: 2rem;
97
+ position: relative;
98
+ }
99
+
100
+ .search-input {
101
+ width: 100%;
102
+ max-width: 600px;
103
+ padding: 1rem 1.5rem;
104
+ font-size: 1rem;
105
+ background: var(--glass-bg);
106
+ border: 1px solid var(--glass-border);
107
+ border-radius: 50px;
108
+ color: var(--text-primary);
109
+ outline: none;
110
+ transition: var(--transition);
111
+ backdrop-filter: blur(10px);
112
+ -webkit-backdrop-filter: blur(10px);
113
+ }
114
+
115
+ .search-input::placeholder {
116
+ color: var(--text-secondary);
117
+ }
118
+
119
+ .search-input:focus {
120
+ border-color: var(--accent-color);
121
+ box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.2);
122
+ }
123
+
124
+ /* Filter Buttons */
125
+ .filter-buttons {
126
+ display: flex;
127
+ gap: 0.5rem;
128
+ margin-bottom: 2rem;
129
+ flex-wrap: wrap;
130
+ }
131
+
132
+ .filter-btn {
133
+ padding: 0.5rem 1.5rem;
134
+ background: var(--glass-bg);
135
+ border: 1px solid var(--glass-border);
136
+ border-radius: 25px;
137
+ color: var(--text-secondary);
138
+ cursor: pointer;
139
+ transition: var(--transition);
140
+ font-size: 0.9rem;
141
+ backdrop-filter: blur(10px);
142
+ }
143
+
144
+ .filter-btn:hover {
145
+ background: rgba(255, 255, 255, 0.15);
146
+ color: var(--text-primary);
147
+ }
148
+
149
+ .filter-btn.active {
150
+ background: var(--accent-color);
151
+ color: var(--primary-color);
152
+ border-color: var(--accent-color);
153
+ }
154
+
155
+ /* Stations Grid */
156
+ .stations-grid {
157
+ display: grid;
158
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
159
+ gap: 1.5rem;
160
+ margin-bottom: 2rem;
161
+ }
162
+
163
+ /* Station Card */
164
+ .station-card {
165
+ background: var(--glass-bg);
166
+ border: 1px solid var(--glass-border);
167
+ border-radius: 20px;
168
+ padding: 1.5rem;
169
+ cursor: pointer;
170
+ transition: var(--transition);
171
+ position: relative;
172
+ overflow: hidden;
173
+ backdrop-filter: blur(10px);
174
+ -webkit-backdrop-filter: blur(10px);
175
+ }
176
+
177
+ .station-card::before {
178
+ content: '';
179
+ position: absolute;
180
+ top: 0;
181
+ left: 0;
182
+ right: 0;
183
+ height: 4px;
184
+ background: var(--gradient);
185
+ transform: scaleX(0);
186
+ transition: transform 0.3s ease;
187
+ }
188
+
189
+ .station-card:hover {
190
+ transform: translateY(-5px);
191
+ box-shadow: var(--glass-shadow);
192
+ border-color: rgba(0, 212, 255, 0.3);
193
+ }
194
+
195
+ .station-card:hover::before {
196
+ transform: scaleX(1);
197
+ }
198
+
199
+ .station-card.playing {
200
+ border-color: var(--accent-color);
201
+ background: rgba(0, 212, 255, 0.05);
202
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ .station-card.playing::before {
205
+ transform: scaleX(1);
206
+ background: var(--accent-color);
207
+ }
208
+
209
+ .station-header {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 1rem;
213
+ margin-bottom: 1rem;
214
+ }
215
+
216
+ .station-logo {
217
+ width: 60px;
218
+ height: 60px;
219
+ border-radius: 12px;
220
+ object-fit: cover;
221
+ background: var(--secondary-color);
222
+ border: 1px solid var(--glass-border);
223
+ }
224
+
225
+ .station-info h3 {
226
+ font-size: 1.1rem;
227
+ margin-bottom: 0.25rem;
228
+ }
229
+
230
+ .station-genre {
231
+ font-size: 0.85rem;
232
+ color: var(--text-secondary);
233
+ background: rgba(255, 255, 255, 0.05);
234
+ padding: 0.25rem 0.75rem;
235
+ border-radius: 12px;
236
+ display: inline-block;
237
+ }
238
+
239
+ .station-description {
240
+ font-size: 0.9rem;
241
+ color: var(--text-secondary);
242
+ margin-bottom: 1rem;
243
+ display: -webkit-box;
244
+ -webkit-line-clamp: 2;
245
+ -webkit-box-orient: vertical;
246
+ overflow: hidden;
247
+ }
248
+
249
+ .station-actions {
250
+ display: flex;
251
+ justify-content: space-between;
252
+ align-items: center;
253
+ }
254
+
255
+ .play-indicator {
256
+ display: none;
257
+ align-items: center;
258
+ gap: 0.5rem;
259
+ font-size: 0.8rem;
260
+ color: var(--accent-color);
261
+ }
262
+
263
+ .station-card.playing .play-indicator {
264
+ display: flex;
265
+ }
266
+
267
+ .equalizer {
268
+ display: flex;
269
+ gap: 2px;
270
+ height: 16px;
271
+ align-items: flex-end;
272
+ }
273
+
274
+ .equalizer-bar {
275
+ width: 3px;
276
+ background: var(--accent-color);
277
+ border-radius: 2px;
278
+ animation: equalize 1s ease-in-out infinite alternate;
279
+ }
280
+
281
+ .equalizer-bar:nth-child(1) { animation-delay: 0s; height: 40%; }
282
+ .equalizer-bar:nth-child(2) { animation-delay: 0.1s; height: 60%; }
283
+ .equalizer-bar:nth-child(3) { animation-delay: 0.2s; height: 80%; }
284
+ .equalizer-bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
285
+
286
+ @keyframes equalize {
287
+ 0% { transform: scaleY(0.5); }
288
+ 100% { transform: scaleY(1); }
289
+ }
290
+
291
+ .favorite-btn {
292
+ background: none;
293
+ border: none;
294
+ color: var(--text-secondary);
295
+ font-size: 1.2rem;
296
+ cursor: pointer;
297
+ transition: var(--transition);
298
+ padding: 0.5rem;
299
+ }
300
+
301
+ .favorite-btn:hover {
302
+ color: var(--accent-color);
303
+ transform: scale(1.1);
304
+ }
305
+
306
+ .favorite-btn.active {
307
+ color: #ff6b6b;
308
+ }
309
+
310
+ /* Player Bar */
311
+ .player-bar {
312
+ position: fixed;
313
+ bottom: 0;
314
+ left: 0;
315
+ right: 0;
316
+ background: var(--glass-bg);
317
+ backdrop-filter: blur(20px);
318
+ -webkit-backdrop-filter: blur(20px);
319
+ border-top: 1px solid var(--glass-border);
320
+ padding: 1rem 2rem;
321
+ transform: translateY(100%);
322
+ transition: var(--transition);
323
+ z-index: 200;
324
+ box-shadow: var(--glass-shadow);
325
+ }
326
+
327
+ .player-bar.active {
328
+ transform: translateY(0);
329
+ }
330
+
331
+ .player-content {
332
+ max-width: 1400px;
333
+ margin: 0 auto;
334
+ display: grid;
335
+ grid-template-columns: 1fr auto 1fr;
336
+ gap: 2rem;
337
+ align-items: center;
338
+ }
339
+
340
+ .now-playing {
341
+ display: flex;
342
+ align-items: center;
343
+ gap: 1rem;
344
+ }
345
+
346
+ .now-playing-logo {
347
+ width: 50px;
348
+ height: 50px;
349
+ border-radius: 10px;
350
+ background: var(--secondary-color);
351
+ border: 1px solid var(--glass-border);
352
+ }
353
+
354
+ .now-playing-info h4 {
355
+ font-size: 1rem;
356
+ margin-bottom: 0.25rem;
357
+ }
358
+
359
+ .now-playing-info p {
360
+ font-size: 0.85rem;
361
+ color: var(--text-secondary);
362
+ }
363
+
364
+ .player-controls {
365
+ display: flex;
366
+ align-items: center;
367
+ gap: 1rem;
368
+ }
369
+
370
+ .control-btn {
371
+ background: var(--glass-bg);
372
+ border: 1px solid var(--glass-border);
373
+ color: var(--text-primary);
374
+ width: 50px;
375
+ height: 50px;
376
+ border-radius: 50%;
377
+ cursor: pointer;
378
+ display: flex;
379
+ align-items: center;
380
+ justify-content: center;
381
+ transition: var(--transition);
382
+ font-size: 1.2rem;
383
+ }
384
+
385
+ .control-btn:hover {
386
+ background: var(--accent-color);
387
+ color: var(--primary-color);
388
+ transform: scale(1.1);
389
+ }
390
+
391
+ .volume-control {
392
+ display: flex;
393
+ align-items: center;
394
+ gap: 1rem;
395
+ justify-self: end;
396
+ }
397
+
398
+ .volume-slider {
399
+ width: 120px;
400
+ height: 4px;
401
+ -webkit-appearance: none;
402
+ appearance: none;
403
+ background: var(--glass-bg);
404
+ border-radius: 2px;
405
+ outline: none;
406
+ cursor: pointer;
407
+ }
408
+
409
+ .volume-slider::-webkit-slider-thumb {
410
+ -webkit-appearance: none;
411
+ appearance: none;
412
+ width: 16px;
413
+ height: 16px;
414
+ background: var(--accent-color);
415
+ border-radius: 50%;
416
+ cursor: pointer;
417
+ transition: var(--transition);
418
+ }
419
+
420
+ .volume-slider::-webkit-slider-thumb:hover {
421
+ transform: scale(1.2);
422
+ }
423
+
424
+ .volume-slider::-moz-range-thumb {
425
+ width: 16px;
426
+ height: 16px;
427
+ background: var(--accent-color);
428
+ border-radius: 50%;
429
+ cursor: pointer;
430
+ border: none;
431
+ }
432
+
433
+ /* Loading Spinner */
434
+ .loading-spinner {
435
+ display: none;
436
+ position: fixed;
437
+ top: 50%;
438
+ left: 50%;
439
+ transform: translate(-50%, -50%);
440
+ z-index: 300;
441
+ }
442
+
443
+ .loading-spinner.active {
444
+ display: block;
445
+ }
446
+
447
+ .spinner {
448
+ width: 60px;
449
+ height: 60px;
450
+ border: 3px solid var(--glass-bg);
451
+ border-top: 3px solid var(--accent-color);
452
+ border-radius: 50%;
453
+ animation: spin 1s linear infinite;
454
+ }
455
+
456
+ @keyframes spin {
457
+ 0% { transform: rotate(0deg); }
458
+ 100% { transform: rotate(360deg); }
459
+ }
460
+
461
+ /* Toast Notifications */
462
+ .toast {
463
+ position: fixed;
464
+ top: 2rem;
465
+ right: 2rem;
466
+ background: var(--glass-bg);
467
+ border: 1px solid var(--glass-border);
468
+ border-radius: 12px;
469
+ padding: 1rem 1.5rem;
470
+ color: var(--text-primary);
471
+ transform: translateX(400px);
472
+ transition: var(--transition);
473
+ z-index: 400;
474
+ backdrop-filter: blur(10px);
475
+ -webkit-backdrop-filter: blur(10px);
476
+ display: flex;
477
+ align-items: center;
478
+ gap: 0.75rem;
479
+ }
480
+
481
+ .toast.show {
482
+ transform: translateX(0);
483
+ }
484
+
485
+ .toast.error {
486
+ border-color: var(--error-color);
487
+ }
488
+
489
+ .toast.success {
490
+ border-color: var(--success-color);
491
+ }
492
+
493
+ /* Empty State */
494
+ .empty-state {
495
+ text-align: center;
496
+ padding: 4rem 2rem;
497
+ color: var(--text-secondary);
498
+ display: none;
499
+ }
500
+
501
+ .empty-state.show {
502
+ display: block;
503
+ }
504
+
505
+ .empty-state i {
506
+ font-size: 4rem;
507
+ margin-bottom: 1rem;
508
+ opacity: 0.5;
509
+ }
510
+
511
+ /* Responsive Design */
512
+ @media (max-width: 768px) {
513
+ header {
514
+ padding: 1rem;
515
+ }
516
+
517
+ main {
518
+ padding: 1rem;
519
+ }
520
+
521
+ .player-content {
522
+ grid-template-columns: 1fr;
523
+ gap: 1rem;
524
+ text-align: center;
525
+ }
526
+
527
+ .volume-control {
528
+ justify-self: center;
529
+ }
530
+
531
+ .stations-grid {
532
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
533
+ gap: 1rem;
534
+ }
535
+
536
+ .search-input {
537
+ font-size: 0.9rem;
538
+ padding: 0.75rem 1.25rem;
539
+ }
540
+ }
541
+
542
+ @media (max-width: 480px) {
543
+ .stations-grid {
544
+ grid-template-columns: 1fr;
545
+ }
546
+
547
+ .player-bar {
548
+ padding: 1rem;
549
+ }
550
+
551
+ .control-btn {
552
+ width: 40px;
553
+ height: 40px;
554
+ font-size: 1rem;
555
+ }
556
+ }
557
+
558
+ /* Accessibility */
559
+ @media (prefers-reduced-motion: reduce) {
560
+ * {
561
+ animation-duration: 0.01ms !important;
562
+ animation-iteration-count: 1 !important;
563
+ transition-duration: 0.01ms !important;
564
+ }
565
+ }
566
+
567
+ /* Focus styles */
568
+ button:focus,
569
+ input:focus {
570
+ outline: 2px solid var(--accent-color);
571
+ outline-offset: 2px;
572
+ }
573
+
574
+ /* Scrollbar styling */
575
+ ::-webkit-scrollbar {
576
+ width: 8px;
577
+ }
578
+
579
+ ::-webkit-scrollbar-track {
580
+ background: var(--primary-color);
581
+ }
582
+
583
+ ::-webkit-scrollbar-thumb {
584
+ background: var(--accent-color);
585
+ border-radius: 4px;
586
+ }
587
+
588
+ ::-webkit-scrollbar-thumb:hover {
589
+ background: #00a8cc;
590
+ }
591
+ </style>
592
+ </head>
593
  <body>
594
+ <header>
595
+ <h1><i class="fas fa-broadcast-tower"></i> Deutsches Online Radio</h1>
596
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
597
+ Built with anycoder
598
+ </a>
599
+ </header>
600
+
601
+ <main>
602
+ <section class="search-section">
603
+ <input type="text" class="search-input" id="searchInput" placeholder="🔍 Sender nach Name, Genre oder Region suchen...">
604
+ </section>
605
+
606
+ <section class="filter-buttons" id="filterButtons">
607
+ <button class="filter-btn active" data-filter="all">Alle</button>
608
+ <button class="filter-btn" data-filter="national">National</button>
609
+ <button class="filter-btn" data-filter="regional">Regional</button>
610
+ <button class="filter-btn" data-filter="news">News</button>
611
+ <button class="filter-btn" data-filter="music">Musik</button>
612
+ <button class="filter-btn" data-filter="kultur">Kultur</button>
613
+ </section>
614
+
615
+ <section class="stations-grid" id="stationsGrid"></section>
616
+
617
+ <section class="empty-state" id="emptyState">
618
+ <i class="fas fa-search"></i>
619
+ <h3>Keine Sender gefunden</h3>
620
+ <p>Probiere es mit einem anderen Suchbegriff oder Filter.</p>
621
+ </section>
622
+ </main>
623
+
624
+ <footer class="player-bar" id="playerBar">
625
+ <div class="player-content">
626
+ <div class="now-playing" id="nowPlaying">
627
+ <img src="" alt="" class="now-playing-logo" id="nowPlayingLogo">
628
+ <div class="now-playing-info">
629
+ <h4 id="nowPlayingName">Kein Sender ausgewählt</h4>
630
+ <p id="nowPlayingGenre">Wähle einen Sender zum Abspielen</p>
631
+ </div>
 
 
 
 
 
 
 
632
  </div>
633
+
634
+ <div class="player-controls">
635
+ <button class="control-btn" id="prevBtn" title="Vorheriger Sender">
636
+ <i class="fas fa-step-backward"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
  </button>
638
+ <button class="control-btn" id="playPauseBtn" title="Play/Pause">
639
+ <i class="fas fa-play" id="playPauseIcon"></i>
640
  </button>
641
+ <button class="control-btn" id="nextBtn" title="Nächster Sender">
642
+ <i class="fas fa-step-forward"></i>
643
  </button>
644
+ </div>
645
+
646
+ <div class="volume-control">
647
+ <button class="control-btn" id="muteBtn" title="Stummschalten">
648
+ <i class="fas fa-volume-up" id="volumeIcon"></i>
649
  </button>
650
+ <input type="range" class="volume-slider" id="volumeSlider" min="0" max="100" value="70">
651
+ </div>
652
+ </div>
653
+ </footer>
 
 
 
 
 
 
 
 
 
 
654
 
655
+ <div class="loading-spinner" id="loadingSpinner">
656
+ <div class="spinner"></div>
 
 
 
 
 
 
657
  </div>
658
 
659
+ <div class="toast" id="toast"></div>
660
+
661
+ <audio id="audioPlayer"></audio>
662
+
663
+ <script>
664
+ // Station Data - Official Public Broadcasters
665
+ const stations = [
666
+ {
667
+ id: 'dlf',
668
+ name: 'Deutschlandfunk',
669
+ description: 'Nachrichten, Politik und Wissenschaft',
670
+ streamUrl: 'https://stream.deutschlandradio.de/dlf/04/dlfdia.cast',
671
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Deutschlandfunk_Logo_2017.svg/300px-Deutschlandfunk_Logo_2017.svg.png',
672
+ category: 'national',
673
+ genre: 'news'
674
+ },
675
+ {
676
+ id: 'dlfkultur',
677
+ name: 'Deutschlandfunk Kultur',
678
+ description: 'Kultur, Literatur und Gesellschaft',
679
+ streamUrl: 'https://stream.deutschlandradio.de/dlfkultur/04/dlfkulturdia.cast',
680
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Deutschlandfunk_Kultur_Logo_2017.svg/300px-Deutschlandfunk_Kultur_Logo_2017.svg.png',
681
+ category: 'national',
682
+ genre: 'kultur'
683
+ },
684
+ {
685
+ id: 'dlfnova',
686
+ name: 'Deutschlandfunk Nova',
687
+ description: 'Jugendradio mit Musik und Talk',
688
+ streamUrl: 'https://stream.deutschlandradio.de/dlfnova/04/dlfnovadia.cast',
689
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Deutschlandfunk_Nova_Logo_2017.svg/300px-Deutschlandfunk_Nova_Logo_2017.svg.png',
690
+ category: 'national',
691
+ genre: 'music'
692
+ },
693
+ {
694
+ id: 'wdr2',
695
+ name: 'WDR 2',
696
+ description: 'Regionalradio für NRW mit Nachrichten',
697
+ streamUrl: 'https://wdr-wdr2-koeln.icecastssl.wdr.de/wdr/wdr2/koeln/mp3/128/stream.mp3',
698
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/WDR_2_Logo_2016.svg/300px-WDR_2_Logo_2016.svg.png',
699
+ category: 'regional',
700
+ genre: 'news'
701
+ },
702
+ {
703
+ id: 'wdr5',
704
+ name: 'WDR 5',
705
+ description: 'Informationen und Hintergründe',
706
+ streamUrl: 'https://wdr-wdr5-live.icecastssl.wdr.de/wdr/wdr5/live/mp3/128/stream.mp3',
707
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/WDR_5_Logo_2016.svg/300px-WDR_5_Logo_2016.svg.png',
708
+ category: 'national',
709
+ genre: 'news'
710
+ },
711
+ {
712
+ id: 'ndrinfo',
713
+ name: 'NDR Info',
714
+ description: 'Nachrichten aus Norddeutschland',
715
+ streamUrl: 'https://ndr-ndrinfo-hh.sslcast.addradio.de/ndr/ndrinfo/hh/mp3/128/stream.mp3',
716
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/5e/NDR_Info_logo.svg/300px-NDR_Info_logo.svg.png',
717
+ category: 'regional',
718
+ genre: 'news'
719
+ },
720
+ {
721
+ id: 'ndr2',
722
+ name: 'NDR 2',
723
+ description: 'Die beste Musik für Norddeutschland',
724
+ streamUrl: 'https://ndr-ndr2-hh.sslcast.addradio.de/ndr/ndr2/hh/mp3/128/stream.mp3',
725
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/NDR_2_logo.svg/300px-NDR_2_logo.svg.png',
726
+ category: 'regional',
727
+ genre: 'music'
728
+ },
729
+ {
730
+ id: 'br24',
731
+ name: 'BR24',
732
+ description: 'Bayerisches Nachrichtenradio',
733
+ streamUrl: 'https://br-br24-live.sslcast.addradio.de/br/br24/live/mp3/128/stream.mp3',
734
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/BR24_logo.svg/300px-BR24_logo.svg.png',
735
+ category: 'regional',
736
+ genre: 'news'
737
+ },
738
+ {
739
+ id: 'bayern1',
740
+ name: 'Bayern 1',
741
+ description: 'Die beste Musik für Bayern',
742
+ streamUrl: 'https://br-bayern1-obb.sslcast.addradio.de/br/bayern1/obb/mp3/128/stream.mp3',
743
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/Bayern_1_logo.svg/300px-Bayern_1_logo.svg.png',
744
+ category: 'regional',
745
+ genre: 'music'
746
+ },
747
+ {
748
+ id: 'bayern3',
749
+ name: 'Bayern 3',
750
+ description: 'Das junge Radio für Bayern',
751
+ streamUrl: 'https://br-bayern3-live.sslcast.addradio.de/br/bayern3/live/mp3/128/stream.mp3',
752
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Bayern_3_logo.svg/300px-Bayern_3_logo.svg.png',
753
+ category: 'regional',
754
+ genre: 'music'
755
+ },
756
+ {
757
+ id: 'swr1bw',
758
+ name: 'SWR1 Baden-Württemberg',
759
+ description: 'Das Radio für Baden-Württemberg',
760
+ streamUrl: 'https://swr-swr1-bw.cast.addradio.de/swr/swr1/bw/mp3/128/stream.mp3',
761
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/5a/SWR_1_Logo_2015.svg/300px-SWR_1_Logo_2015.svg.png',
762
+ category: 'regional',
763
+ genre: 'music'
764
+ },
765
+ {
766
+ id: 'swr3',
767
+ name: 'SWR3',
768
+ description: 'Das junge Radio für BW und RP',
769
+ streamUrl: 'https://swr-swr3-live.cast.addradio.de/swr/swr3/live/mp3/128/stream.mp3',
770
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/SWR3_logo.svg/300px-SWR3_logo.svg.png',
771
+ category: 'national',
772
+ genre: 'music'
773
+ },
774
+ {
775
+ id: 'hr1',
776
+ name: 'hr1',
777
+ description: 'Informationen aus Hessen',
778
+ streamUrl: 'https://hr-hr1-live.cast.addradio.de/hr/hr1/live/mp3/128/stream.mp3',
779
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Hr1_logo.svg/300px-Hr1_logo.svg.png',
780
+ category: 'regional',
781
+ genre: 'news'
782
+ },
783
+ {
784
+ id: 'hr3',
785
+ name: 'hr3',
786
+ description: 'Das Pop- und Eventradio für Hessen',
787
+ streamUrl: 'https://hr-hr3-live.cast.addradio.de/hr/hr3/live/mp3/128/stream.mp3',
788
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Hr3_logo.svg/300px-Hr3_logo.svg.png',
789
+ category: 'regional',
790
+ genre: 'music'
791
+ },
792
+ {
793
+ id: 'mdrsachsen',
794
+ name: 'MDR Sachsen',
795
+ description: 'Das Radio für Sachsen',
796
+ streamUrl: 'https://mdr-mdrsachsen-live.cast.addradio.de/mdr/mdrsachsen/live/mp3/128/stream.mp3',
797
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/MDR_Sachsen_Logo.svg/300px-MDR_Sachsen_Logo.svg.png',
798
+ category: 'regional',
799
+ genre: 'music'
800
+ },
801
+ {
802
+ id: 'mdrjump',
803
+ name: 'MDR JUMP',
804
+ description: 'Der beste Mix für Sachsen, Sachsen-Anhalt, Thüringen',
805
+ streamUrl: 'https://mdr-mdrjump-live.cast.addradio.de/mdr/mdrjump/live/mp3/128/stream.mp3',
806
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9b/MDR_Jump_Logo.svg/300px-MDR_Jump_Logo.svg.png',
807
+ category: 'regional',
808
+ genre: 'music'
809
+ },
810
+ {
811
+ id: 'rbb',
812
+ name: 'rbbKultur',
813
+ description: 'Kultur- und Wortradio für Berlin und Brandenburg',
814
+ streamUrl: 'https://rbb-rbbkultur-live.sslcast.addradio.de/rbb/rbbkultur/live/mp3/128/stream.mp3',
815
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Rbb_Kultur_logo.svg/300px-Rbb_Kultur_logo.svg.png',
816
+ category: 'regional',
817
+ genre: 'kultur'
818
+ },
819
+ {
820
+ id: 'radioeins',
821
+ name: 'radioeins',
822
+ description: 'Berlins alternativer Sender',
823
+ streamUrl: 'https://rbb-radioeins-live.sslcast.addradio.de/rbb/radioeins/live/mp3/128/stream.mp3',
824
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/Radioeins_Logo_2017.svg/300px-Radioeins_Logo_2017.svg.png',
825
+ category: 'regional',
826
+ genre: 'music'
827
+ },
828
+ {
829
+ id: 'sr1',
830
+ name: 'SR 1 Europawelle',
831
+ description: 'Das Radio für das Saarland',
832
+ streamUrl: 'https://sr-sr1-live.cast.addradio.de/sr/sr1/live/mp3/128/stream.mp3',
833
+ logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/SR_1_Europawelle_logo.svg/300px-SR_1_Europawelle_logo.svg.png',
834
+ category: 'regional',
835
+ genre: 'music'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
836
  }
837
+ ];
838
+
839
+ // App State
840
+ let currentStation = null;
841
+ let isPlaying = false;
842
+ let currentVolume = 70;
843
+ let favorites = JSON.parse(localStorage.getItem('radioFavorites')) || [];
844
+ let currentFilter = 'all';
845
+ let searchTerm = '';
846
 
847
+ // DOM Elements
 
848
  const audioPlayer = document.getElementById('audioPlayer');
849
+ const stationsGrid = document.getElementById('stationsGrid');
850
+ const searchInput = document.getElementById('searchInput');
851
+ const filterButtons = document.querySelectorAll('.filter-btn');
852
+ const playerBar = document.getElementById('playerBar');
853
+ const nowPlayingLogo = document.getElementById('nowPlayingLogo');
854
+ const nowPlayingName = document.getElementById('nowPlayingName');
855
+ const nowPlayingGenre = document.getElementById('nowPlayingGenre');
856
+ const playPauseBtn = document.getElementById('playPauseBtn');
857
+ const playPauseIcon = document.getElementById('playPauseIcon');
858
+ const prevBtn = document.getElementById('prevBtn');
859
+ const nextBtn = document.getElementById('nextBtn');
860
+ const muteBtn = document.getElementById('muteBtn');
861
+ const volumeIcon = document.getElementById('volumeIcon');
862
+ const volumeSlider = document.getElementById('volumeSlider');
863
+ const loadingSpinner = document.getElementById('loadingSpinner');
864
+ const toast = document.getElementById('toast');
865
+ const emptyState = document.getElementById('emptyState');
866
+
867
+ // Initialize
868
+ document.addEventListener('DOMContentLoaded', () => {
869
+ renderStations();
870
+ setupEventListeners();
871
+ audioPlayer.volume = currentVolume / 100;
872
+ volumeSlider.value = currentVolume;
873
+ });
874
+
875
+ // Render Stations
876
+ function renderStations() {
877
+ const filteredStations = getFilteredStations();
878
+
879
+ if (filteredStations.length === 0) {
880
+ stationsGrid.style.display = 'none';
881
+ emptyState.classList.add('show');
882
+ } else {
883
+ stationsGrid.style.display = 'grid';
884
+ emptyState.classList.remove('show');
885
+
886
+ stationsGrid.innerHTML = filteredStations.map(station => `
887
+ <div class="station-card ${currentStation?.id === station.id ? 'playing' : ''}"
888
+ data-id="${station.id}">
889
+ <div class="station-header">
890
+ <img src="${station.logo}" alt="${station.name}" class="station-logo" loading="lazy">
891
+ <div class="station-info">
892
+ <h3>${station.name}</h3>
893
+ <span class="station-genre">${getGenreLabel(station.genre)}</span>
894
+ </div>
895
+ </div>
896
+ <p class="station-description">${station.description}</p>
897
+ <div class="station-actions">
898
+ <div class="play-indicator">
899
+ <span>JETZT LIVE</span>
900
+ <div class="equalizer">
901
+ <div class="equalizer-bar"></div>
902
+ <div class="equalizer-bar"></div>
903
+ <div class="equalizer-bar"></div>
904
+ <div class="equalizer-bar"></div>
905
+ </div>
906
+ </div>
907
+ <button class="favorite-btn ${favorites.includes(station.id) ? 'active' : ''}"
908
+ data-id="${station.id}"
909
+ title="Favorit">
910
+ <i class="${favorites.includes(station.id) ? 'fas' : 'far'} fa-heart"></i>
911
+ </button>
912
+ </div>
913
+ </div>
914
+ `).join('');
915
  }
916
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
917
 
918
+ // Get Filtered Stations
919
+ function getFilteredStations() {
920
+ return stations.filter(station => {
921
+ const matchesFilter = currentFilter === 'all' || station.category === currentFilter || station.genre === currentFilter;
922
+ const matchesSearch = searchTerm === '' ||
923
+ station.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
924
+ station.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
925
+ getGenreLabel(station.genre).toLowerCase().includes(searchTerm.toLowerCase());
926
+ return matchesFilter && matchesSearch;
927
+ });
928
+ }
929
+
930
+ // Get Genre Label
931
+ function getGenreLabel(genre) {
932
+ const labels = {
933
+ news: 'Nachrichten',
934
+ music: 'Musik',
935
+ kultur: 'Kultur',
936
+ regional: 'Regional'
937
+ };
938
+ return labels[genre] || genre;
939
+ }
940
+
941
+ // Setup Event Listeners
942
+ function setupEventListeners() {
943
+ // Search
944
+ searchInput.addEventListener('input', (e) => {
945
+ searchTerm = e.target.value;
946
+ renderStations();
947
+ });
948
+
949
+ // Filter Buttons
950
+ filterButtons.forEach(btn => {
951
+ btn.addEventListener('click', () => {
952
+ filterButtons.forEach(b => b.classList.remove('active'));
953
+ btn.classList.add('active');
954
+ currentFilter = btn.dataset.filter;
955
+ renderStations();
956
+ });
957
+ });
958
+
959
+ // Station Cards & Favorites
960
+ stationsGrid.addEventListener('click', (e) => {
961
+ const stationCard = e.target.closest('.station-card');
962
+ const favoriteBtn = e.target.closest('.favorite-btn');
963
+
964
+ if (favoriteBtn) {
965
+ e.stopPropagation();
966
+ toggleFavorite(favoriteBtn.dataset.id);
967
+ } else if (stationCard) {
968
+ playStation(stationCard.dataset.id);
969
+ }
970
+ });
971
+
972
+ // Player Controls
973
+ playPauseBtn.addEventListener('click', togglePlayPause);
974
+ prevBtn.addEventListener('click', playPrevious);
975
+ nextBtn.addEventListener('click', playNext);
976
+ muteBtn.addEventListener('click', toggleMute);
977
+ volumeSlider.addEventListener('input', (e) => setVolume(e.target.value));
978
+
979
+ // Audio Player Events
980
+ audioPlayer.addEventListener('play', () => {
981
+ isPlaying = true;
982
+ updatePlayPauseIcon();
983
+ });
984
+
985
+ audioPlayer.addEventListener('pause', () => {
986
+ isPlaying = false;
987
+ updatePlayPauseIcon();
988
+ });
989
+
990
+ audioPlayer.addEventListener('error', (e) => {
991
+ showToast('Fehler beim Laden des Streams', 'error');
992
+ loadingSpinner.classList.remove('active');
993
+ });
994
+
995
+ audioPlayer.addEventListener('load