Multimedix commited on
Commit
5c5e17f
·
verified ·
1 Parent(s): 72047d5

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +757 -710
index.html CHANGED
@@ -1,510 +1,551 @@
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>DE Radio Stream - Player</title>
7
- <meta name="description" content="Der beste deutsche Online Radio Player mit Senderliste.">
8
-
9
- <!-- Google Fonts Import -->
10
- <link rel="preconnect" href="https://fonts.googleapis.com">
11
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
13
-
14
- <style>
15
- :root {
16
- --bg-color: #0f172a;
17
- --surface-color: rgba(30, 41, 59, 0.7);
18
- --surface-border: rgba(255, 255, 255, 0.1);
19
- --primary-color: #6366f1;
20
- --primary-hover: #4f46e5;
21
- --accent-color: #ec4899;
22
- --text-main: #f8fafc;
23
- --text-muted: #94a3b8;
24
- --glass-blur: blur(12px);
25
- --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
26
- --radius-lg: 16px;
27
- --radius-md: 8px;
28
- --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
29
- }
30
-
31
- * {
32
- box-sizing: border-box;
33
- margin: 0;
34
- padding: 0;
35
- outline: none;
36
- }
37
-
38
- body {
39
- font-family: 'Inter', sans-serif;
40
- background-color: var(--bg-color);
41
- background-image:
42
- radial-gradient(at 0% 0%, hsla(253,16%,7%,1) 0, transparent 50%),
43
- radial-gradient(at 50% 0%, hsla(225,39%,30%,1) 0, transparent 50%),
44
- radial-gradient(at 100% 0%, hsla(339,49%,30%,1) 0, transparent 50%);
45
- color: var(--text-main);
46
- min-height: 100vh;
47
- display: flex;
48
- flex-direction: column;
49
- overflow-x: hidden;
50
- }
51
-
52
- /* Custom Scrollbar */
53
- ::-webkit-scrollbar {
54
- width: 8px;
55
- }
56
- ::-webkit-scrollbar-track {
57
- background: rgba(0,0,0,0.2);
58
- }
59
- ::-webkit-scrollbar-thumb {
60
- background: var(--surface-border);
61
- border-radius: 4px;
62
- }
63
- ::-webkit-scrollbar-thumb:hover {
64
- background: var(--primary-color);
65
- }
66
-
67
- /* --- Header --- */
68
- header {
69
- display: flex;
70
- justify-content: space-between;
71
- align-items: center;
72
- padding: 1.5rem 2rem;
73
- background: rgba(15, 23, 42, 0.8);
74
- backdrop-filter: var(--glass-blur);
75
- border-bottom: 1px solid var(--surface-border);
76
- position: sticky;
77
- top: 0;
78
- z-index: 100;
79
- }
80
-
81
- .logo {
82
- font-size: 1.5rem;
83
- font-weight: 800;
84
- background: linear-gradient(to right, var(--primary-color), var(--accent-color));
85
- -webkit-background-clip: text;
86
- -webkit-text-fill-color: transparent;
87
- letter-spacing: -0.5px;
88
- display: flex;
89
- align-items: center;
90
- gap: 10px;
91
- }
92
-
93
- .anycoder-link {
94
- font-size: 0.85rem;
95
- color: var(--text-muted);
96
- text-decoration: none;
97
- padding: 0.5rem 1rem;
98
- border: 1px solid var(--surface-border);
99
- border-radius: 20px;
100
- transition: var(--transition);
101
- }
102
-
103
- .anycoder-link:hover {
104
- color: var(--text-main);
105
- border-color: var(--primary-color);
106
- background: rgba(99, 102, 241, 0.1);
107
- }
108
-
109
- /* --- Main Layout --- */
110
- main {
111
- flex: 1;
112
- display: grid;
113
- grid-template-columns: 1fr 380px;
114
- gap: 2rem;
115
- padding: 2rem;
116
- max-width: 1400px;
117
- margin: 0 auto;
118
- width: 100%;
119
- }
120
-
121
- @media (max-width: 900px) {
122
- main {
123
- grid-template-columns: 1fr;
124
- padding: 1rem;
125
- }
126
- }
127
-
128
- /* --- Station List (Left Side) --- */
129
- .station-list-container {
130
- display: flex;
131
- flex-direction: column;
132
- gap: 1.5rem;
133
- }
134
-
135
- .search-bar {
136
- position: relative;
137
- }
138
-
139
- .search-bar input {
140
- width: 100%;
141
- padding: 1rem 1rem 1rem 3rem;
142
- background: var(--surface-color);
143
- border: 1px solid var(--surface-border);
144
- border-radius: var(--radius-lg);
145
- color: var(--text-main);
146
- font-size: 1rem;
147
- transition: var(--transition);
148
- }
149
-
150
- .search-bar input:focus {
151
- border-color: var(--primary-color);
152
- box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
153
- }
154
-
155
- .search-icon {
156
- position: absolute;
157
- left: 1rem;
158
- top: 50%;
159
- transform: translateY(-50%);
160
- color: var(--text-muted);
161
- width: 20px;
162
- height: 20px;
163
- }
164
-
165
- .stations-grid {
166
- display: grid;
167
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
168
- gap: 1rem;
169
- }
170
-
171
- .station-card {
172
- background: var(--surface-color);
173
- border: 1px solid var(--surface-border);
174
- border-radius: var(--radius-md);
175
- padding: 1rem;
176
- cursor: pointer;
177
- transition: var(--transition);
178
- display: flex;
179
- align-items: center;
180
- gap: 1rem;
181
- position: relative;
182
- overflow: hidden;
183
- }
184
-
185
- .station-card:hover {
186
- transform: translateY(-2px);
187
- background: rgba(255, 255, 255, 0.05);
188
- border-color: rgba(255, 255, 255, 0.2);
189
- }
190
-
191
- .station-card.active {
192
- background: rgba(99, 102, 241, 0.15);
193
- border-color: var(--primary-color);
194
- }
195
-
196
- .station-card.active::before {
197
- content: '';
198
- position: absolute;
199
- left: 0;
200
- top: 0;
201
- bottom: 0;
202
- width: 4px;
203
- background: var(--primary-color);
204
- }
205
-
206
- .station-logo {
207
- width: 48px;
208
- height: 48px;
209
- border-radius: 50%;
210
- background: #333;
211
- display: flex;
212
- align-items: center;
213
- justify-content: center;
214
- font-weight: bold;
215
- font-size: 0.8rem;
216
- color: #fff;
217
- flex-shrink: 0;
218
- object-fit: cover;
219
- }
220
-
221
- .station-info h3 {
222
- font-size: 1rem;
223
- font-weight: 600;
224
- margin-bottom: 0.25rem;
225
- }
226
-
227
- .station-info p {
228
- font-size: 0.85rem;
229
- color: var(--text-muted);
230
- }
231
-
232
- .play-indicator {
233
- margin-left: auto;
234
- opacity: 0;
235
- color: var(--primary-color);
236
- transition: var(--transition);
237
- }
238
-
239
- .station-card:hover .play-indicator,
240
- .station-card.active .play-indicator {
241
- opacity: 1;
242
- }
243
-
244
- /* --- Player (Right Side / Sticky) --- */
245
- .player-container {
246
- background: var(--surface-color);
247
- backdrop-filter: var(--glass-blur);
248
- border: 1px solid var(--surface-border);
249
- border-radius: var(--radius-lg);
250
- padding: 2rem;
251
- height: fit-content;
252
- position: sticky;
253
- top: 100px;
254
- display: flex;
255
- flex-direction: column;
256
- align-items: center;
257
- box-shadow: var(--shadow-lg);
258
- text-align: center;
259
- }
260
-
261
- @media (max-width: 900px) {
262
- .player-container {
263
- position: relative;
264
- top: 0;
265
- order: -1; /* Player on top on mobile */
266
- }
267
- }
268
-
269
- .player-visualizer {
270
- display: flex;
271
- align-items: flex-end;
272
- justify-content: center;
273
- gap: 4px;
274
- height: 60px;
275
- margin-bottom: 1.5rem;
276
- width: 100%;
277
- }
278
-
279
- .bar {
280
- width: 6px;
281
- background: var(--primary-color);
282
- border-radius: 4px;
283
- height: 10%;
284
- transition: height 0.1s ease;
285
- }
286
-
287
- .bar:nth-child(even) {
288
- background: var(--accent-color);
289
- }
290
-
291
- .player-visualizer.playing .bar {
292
- animation: bounce 1s infinite ease-in-out;
293
- }
294
-
295
- @keyframes bounce {
296
- 0%, 100% { height: 10%; }
297
- 50% { height: 100%; }
298
- }
299
-
300
- /* Stagger animations */
301
- .bar:nth-child(1) { animation-duration: 0.8s; }
302
- .bar:nth-child(2) { animation-duration: 1.1s; }
303
- .bar:nth-child(3) { animation-duration: 0.9s; }
304
- .bar:nth-child(4) { animation-duration: 1.2s; }
305
- .bar:nth-child(5) { animation-duration: 0.7s; }
306
- .bar:nth-child(6) { animation-duration: 1.0s; }
307
- .bar:nth-child(7) { animation-duration: 0.8s; }
308
- .bar:nth-child(8) { animation-duration: 1.3s; }
309
-
310
- .current-station-art {
311
- width: 150px;
312
- height: 150px;
313
- border-radius: 20px;
314
- background: linear-gradient(135deg, #333, #111);
315
- margin-bottom: 1.5rem;
316
- box-shadow: 0 10px 30px rgba(0,0,0,0.3);
317
- display: flex;
318
- align-items: center;
319
- justify-content: center;
320
- font-size: 3rem;
321
- color: var(--text-muted);
322
- position: relative;
323
- overflow: hidden;
324
- }
325
-
326
- .current-station-art img {
327
- width: 100%;
328
- height: 100%;
329
- object-fit: cover;
330
- }
331
 
332
- .current-station-name {
333
- font-size: 1.5rem;
334
- font-weight: 700;
335
- margin-bottom: 0.5rem;
336
- line-height: 1.2;
337
- }
338
-
339
- .current-station-genre {
340
- display: inline-block;
341
- background: rgba(255,255,255,0.1);
342
- padding: 4px 12px;
343
- border-radius: 12px;
344
- font-size: 0.8rem;
345
- color: var(--text-muted);
346
- margin-bottom: 2rem;
347
- }
348
-
349
- .controls {
350
- display: flex;
351
- align-items: center;
352
- gap: 1.5rem;
353
- width: 100%;
354
- justify-content: center;
355
- margin-bottom: 2rem;
356
- }
357
-
358
- .btn-control {
359
- background: none;
360
- border: none;
361
- cursor: pointer;
362
- color: var(--text-main);
363
- transition: var(--transition);
364
- display: flex;
365
- align-items: center;
366
- justify-content: center;
367
- }
368
-
369
- .btn-control:hover {
370
- color: var(--primary-color);
371
- transform: scale(1.1);
372
- }
373
-
374
- .btn-play {
375
- width: 64px;
376
- height: 64px;
377
- border-radius: 50%;
378
- background: var(--primary-color);
379
- box-shadow: 0 0 20px rgba(99, 102, 241, 0.4);
380
- }
381
-
382
- .btn-play:hover {
383
- background: var(--primary-hover);
384
- color: white;
385
- box-shadow: 0 0 30px rgba(99, 102, 241, 0.6);
386
- }
387
-
388
- .volume-container {
389
- display: flex;
390
- align-items: center;
391
- gap: 10px;
392
- width: 100%;
393
- max-width: 200px;
394
- }
395
-
396
- input[type="range"] {
397
- -webkit-appearance: none;
398
- width: 100%;
399
- height: 6px;
400
- background: rgba(255,255,255,0.1);
401
- border-radius: 3px;
402
- cursor: pointer;
403
- }
404
-
405
- input[type="range"]::-webkit-slider-thumb {
406
- -webkit-appearance: none;
407
- width: 16px;
408
- height: 16px;
409
- background: var(--text-main);
410
- border-radius: 50%;
411
- transition: var(--transition);
412
- }
413
-
414
- input[type="range"]::-webkit-slider-thumb:hover {
415
- background: var(--primary-color);
416
- transform: scale(1.2);
417
- }
418
-
419
- /* --- Footer --- */
420
- footer {
421
- text-align: center;
422
- padding: 2rem;
423
- color: var(--text-muted);
424
- font-size: 0.9rem;
425
- border-top: 1px solid var(--surface-border);
426
- margin-top: auto;
427
- }
428
-
429
- /* --- Loading State --- */
430
- .loading-spinner {
431
- width: 20px;
432
- height: 20px;
433
- border: 2px solid rgba(255,255,255,0.3);
434
- border-top-color: var(--primary-color);
435
- border-radius: 50%;
436
- animation: spin 1s linear infinite;
437
- display: none;
438
- }
439
-
440
- @keyframes spin {
441
- to { transform: rotate(360deg); }
442
- }
443
-
444
- .status-message {
445
- margin-top: 1rem;
446
- font-size: 0.85rem;
447
- min-height: 1.2em;
448
- color: var(--accent-color);
449
- }
450
-
451
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  </head>
 
453
  <body>
454
 
455
- <header>
456
- <div class="logo">
457
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
458
- <path d="M12 2c-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-.5 5-1.3M15 5a5 5 0 0 1 5 5v1M15 9a3 3 0 0 1 3 3v1"/>
459
- <circle cx="12" cy="12" r="2"/>
460
- </svg>
461
- DE Radio
462
- </div>
463
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
464
- </header>
465
-
466
- <main>
467
- <!-- Senderliste -->
468
- <section class="station-list-container">
469
- <div class="search-bar">
470
- <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
471
- <circle cx="11" cy="11" r="8"></circle>
472
- <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
473
- </svg>
474
- <input type="text" id="searchInput" placeholder="Sender suchen...">
475
- </div>
476
-
477
- <div class="stations-grid" id="stationsGrid">
478
- <!-- Stationen werden hier per JS eingefügt -->
479
- </div>
480
- </section>
481
-
482
- <!-- Player -->
483
- <section class="player-container">
484
- <div class="player-visualizer" id="visualizer">
485
- <div class="bar"></div>
486
- <div class="bar"></div>
487
- <div class="bar"></div>
488
- <div class="bar"></div>
489
- <div class="bar"></div>
490
- <div class="bar"></div>
491
- <div class="bar"></div>
492
- <div class="bar"></div>
493
- </div>
494
-
495
- <div class="current-station-art" id="playerArt">
496
- <svg width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
497
- <path d="M9 18V5l12-2v13"></path>
498
- <circle cx="6" cy="18" r="3"></circle>
499
- <circle cx="18" cy="16" r="3"></circle>
500
- </svg>
501
- </div>
502
-
503
- <h2 class="current-station-name" id="playerTitle">Wähle einen Sender</h2>
504
- <span class="current-station-genre" id="playerGenre">Bereit</span>
505
-
506
- <div class="controls">
507
- <button class="btn-control btn-play" id="playPauseBtn" aria-label="Play/Pause">
 
 
 
 
508
  <svg id="iconPlay" width="32" height="32" viewBox="0 0 24 24" fill="currentColor">
509
  <polygon points="5 3 19 12 5 21 5 3"></polygon>
510
  </svg>
@@ -514,226 +555,232 @@
514
  </svg>
515
  <div class="loading-spinner" id="loadingSpinner"></div>
516
  </button>
517
- </div>
518
-
519
- <div class="volume-container">
520
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
521
- <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
522
- </svg>
523
- <input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="0.8">
524
- </div>
525
- <div class="status-message" id="statusMessage"></div>
526
- </section>
527
- </main>
528
-
529
- <footer>
530
- <p>&copy; 2023 DE Radio Stream Player. Hören Sie Ihre Lieblingssender.</p>
531
- </footer>
532
-
533
- <script>
534
- // Senderdaten (Name, Genre, Stream URL, Farbe für das Logo-Platzhalter)
535
- const stations = [
536
- { name: "Deutschlandfunk", genre: "Nachrichten & Info", url: "http://st01.dlf.de/dlf/01/128/mp3/stream.mp3", color: "#e11d48" },
537
- { name: "Deutschlandradio Kultur", genre: "Kultur", url: "http://st02.dlf.de/dlf/02/128/mp3/stream.mp3", color: "#d97706" },
538
- { name: "DRadio Wissen", genre: "Wissen", url: "http://st03.dlf.de/dlf/03/128/mp3/stream.mp3", color: "#059669" },
539
- { name: "BAYERN 3", genre: "Pop & Rock", url: "http://br-mp3-f-shz-cybr.sharp-stream.com/br3/live/mp3/128/stream.mp3", color: "#2563eb" },
540
- { name: "Antenne Bayern", genre: "Pop", url: "https://stream.antenne.de/antenne-bayern", color: "#ea580c" },
541
- { name: "1LIVE", genre: "Pop & Electro", url: "http://wdr-1live-live.icecast.wdr.de/wdr/1live/live/mp3/128/stream.mp3", color: "#7c3aed" },
542
- { name: "WDR 2", genre: "Pop & Information", url: "http://wdr-wdr2-rheinland.icecast.wdr.de/wdr/wdr2/rheinland/mp3/128/stream.mp3", color: "#0891b2" },
543
- { name: "SWR3", genre: "Pop", url: "https://swr-swr3-live.cast.addradio.de/swr/swr3/live/mp3/128/stream.mp3", color: "#db2777" },
544
- { name: "NDR 2", genre: "Pop", url: "https://ndr-ndr2-niedersachsen.cast.addradio.de/ndr/ndr2/niedersachsen/mp3/128/stream.mp3", color: "#4338ca" },
545
- { name: "NDR Info", genre: "Nachrichten", url: "https://ndr-ndrinfo-niedersachsen.cast.addradio.de/ndr/ndrinfo/niedersachsen/mp3/128/stream.mp3", color: "#334155" },
546
- { name: "RBB 88.8", genre: "Berlin", url: "https://rbb-berlin-live.cast.addradio.de/rbb/berlin/live/mp3/128/stream.mp3", color: "#dc2626" },
547
- { name: "RBB Radio Eins", genre: "Popkultur", url: "https://rbb-radioeins-live.cast.addradio.de/rbb/radioeins/live/mp3/128/stream.mp3", color: "#be185d" },
548
- { name: "HR 3", genre: "Pop", url: "https://hr-hr3-live.cast.addradio.de/hr/hr3/live/mp3/128/stream.mp3", color: "#ca8a04" },
549
- { name: "MDR JUMP", genre: "Dance & Pop", url: "https://mdr-jump-live.cast.addradio.de/mdr/jump/live/mp3/128/stream.mp3", color: "#65a30d" },
550
- { name: "MDR KULTUR", genre: "Kultur", url: "https://mdr-kultur-live.cast.addradio.de/mdr/kultur/live/mp3/128/stream.mp3", color: "#475569" },
551
- { name: "SR 1 Europawelle", genre: "Pop", url: "http://mp3sr1.sr-online.de/sr1_2.m3u", color: "#0369a1" }, // Fallback handling usually needed for m3u, using direct stream if possible
552
- { name: "KISS FM", genre: "Dance & R&B", url: "https://stream.kissfm.de/kissfm/mp3-128", color: "#c026d3" },
553
- { name: "JAM FM", genre: "Black & Pop", url: "https://stream.jam.fm/jamfm-live/mp3-128", color: "#f59e0b" },
554
- { name: "Radio Bremen 4", genre: "Information", url: "https://rb-bremenvier-live.cast.addradio.de/rb/bremenvier/live/mp3/128/stream.mp3", color: "#083344" },
555
- { name: "N-JOY", genre: "Pop", url: "https://ndr-njoy-live.cast.addradio.de/ndr/njoy/live/mp3/128/stream.mp3", color: "#16a34a" }
556
- ];
557
-
558
- // DOM Elemente
559
- const stationsGrid = document.getElementById('stationsGrid');
560
- const searchInput = document.getElementById('searchInput');
561
- const audio = new Audio();
562
-
563
- const playerTitle = document.getElementById('playerTitle');
564
- const playerGenre = document.getElementById('playerGenre');
565
- const playerArt = document.getElementById('playerArt');
566
- const playPauseBtn = document.getElementById('playPauseBtn');
567
- const iconPlay = document.getElementById('iconPlay');
568
- const iconPause = document.getElementById('iconPause');
569
- const loadingSpinner = document.getElementById('loadingSpinner');
570
- const volumeSlider = document.getElementById('volumeSlider');
571
- const visualizer = document.getElementById('visualizer');
572
- const statusMessage = document.getElementById('statusMessage');
573
-
574
- let currentStationIndex = -1;
575
- let isPlaying = false;
576
-
577
- // Initialisierung
578
- function init() {
579
- renderStations(stations);
580
- audio.volume = volumeSlider.value;
581
- }
582
-
583
- // Stationen rendern
584
- function renderStations(list) {
585
- stationsGrid.innerHTML = '';
586
- list.forEach((station, index) => {
587
- const card = document.createElement('div');
588
- card.className = 'station-card';
589
- card.dataset.index = index; // Speicher den ursprünglichen Index
590
- card.onclick = () => selectStation(station);
591
-
592
- // Initialen für das Logo
593
- const initials = station.name.substring(0, 2).toUpperCase();
594
-
595
- card.innerHTML = `
596
- <div class="station-logo" style="background-color: ${station.color}">
597
- ${initials}
598
- </div>
599
- <div class="station-info">
600
- <h3>${station.name}</h3>
601
- <p>${station.genre}</p>
602
- </div>
603
- <div class="play-indicator">
604
- <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
605
- <polygon points="5 3 19 12 5 21 5 3"></polygon>
606
- </svg>
607
- </div>
608
- `;
609
- stationsGrid.appendChild(card);
610
- });
611
- }
612
-
613
- // Suche
614
- searchInput.addEventListener('input', (e) => {
615
- const term = e.target.value.toLowerCase();
616
- const filtered = stations.filter(s =>
617
- s.name.toLowerCase().includes(term) ||
618
- s.genre.toLowerCase().includes(term)
619
- );
620
- renderStations(filtered);
621
  });
622
-
623
- // Station auswählen
624
- function selectStation(station) {
625
- // Wenn gleiche Station geklickt wird, nur Play/Pause toggeln
626
- if (playerTitle.innerText === station.name) {
627
- togglePlay();
628
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
629
  }
 
630
 
631
- // UI Update
632
- playerTitle.innerText = station.name;
633
- playerGenre.innerText = station.genre;
634
- playerArt.style.backgroundColor = station.color;
635
- playerArt.innerHTML = `<span style="font-size: 3rem; font-weight: 800; color: rgba(255,255,255,0.8);">${station.name.substring(0,1).toUpperCase()}</span>`;
636
-
637
- // Highlight in Liste
638
- document.querySelectorAll('.station-card').forEach(card => {
639
- card.classList.remove('active');
640
- if (card.querySelector('h3').innerText === station.name) {
641
- card.classList.add('active');
642
- }
643
- });
644
 
645
- // Audio laden
646
- statusMessage.innerText = "Verbinde...";
647
- statusMessage.style.color = "var(--text-muted)";
648
- audio.src = station.url;
649
- audio.load();
650
-
651
  playAudio();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
652
  }
 
653
 
654
- // Play/Pause Logik
655
- function togglePlay() {
656
- if (!audio.src) return;
657
- if (audio.paused) {
658
- playAudio();
659
- } else {
660
- pauseAudio();
661
- }
662
- }
663
-
664
- function playAudio() {
665
- const playPromise = audio.play();
666
-
667
- if (playPromise !== undefined) {
668
- playPromise.then(_ => {
669
- isPlaying = true;
670
- updatePlayButton();
671
- visualizer.classList.add('playing');
672
- statusMessage.innerText = "";
673
- })
674
- .catch(error => {
675
- console.error("Playback error:", error);
676
- isPlaying = false;
677
- updatePlayButton();
678
- visualizer.classList.remove('playing');
679
- statusMessage.innerText = "Fehler beim Laden des Streams.";
680
- statusMessage.style.color = "#ef4444";
681
- });
682
- }
683
- }
684
-
685
- function pauseAudio() {
686
- audio.pause();
687
- isPlaying = false;
688
- updatePlayButton();
689
- visualizer.classList.remove('playing');
690
- }
691
-
692
- function updatePlayButton() {
693
- if (isPlaying) {
694
- iconPlay.style.display = 'none';
695
- iconPause.style.display = 'block';
696
- loadingSpinner.style.display = 'none';
697
- } else {
698
- iconPlay.style.display = 'block';
699
- iconPause.style.display = 'none';
700
- loadingSpinner.style.display = 'none';
701
- }
702
- }
703
-
704
- playPauseBtn.addEventListener('click', togglePlay);
705
 
706
- // Lautstärke
707
- volumeSlider.addEventListener('input', (e) => {
708
- audio.volume = e.target.value;
709
- });
710
-
711
- // Audio Events
712
- audio.addEventListener('waiting', () => {
713
- loadingSpinner.style.display = 'block';
714
  iconPlay.style.display = 'none';
715
- iconPause.style.display = 'none';
716
- statusMessage.innerText = "Puffer...";
717
- });
718
-
719
- audio.addEventListener('playing', () => {
720
- loadingSpinner.style.display = 'none';
721
- updatePlayButton();
722
- });
723
-
724
- audio.addEventListener('error', (e) => {
725
- console.error("Stream Error", e);
726
  loadingSpinner.style.display = 'none';
 
727
  iconPlay.style.display = 'block';
728
  iconPause.style.display = 'none';
729
- visualizer.classList.remove('playing');
730
- statusMessage.innerText = "Stream nicht verfügbar.";
731
- statusMessage.style.color = "#ef4444";
732
- });
733
-
734
- // App starten
735
- init();
736
-
737
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
  </body>
 
739
  </html>
 
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>DE Radio Stream - Player (Updated)</title>
8
+ <meta name="description" content="Der beste deutsche Online Radio Player mit funktionierenden Senderlisten.">
9
+
10
+ <!-- Google Fonts Import -->
11
+ <link rel="preconnect" href="https://fonts.googleapis.com">
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
14
+
15
+ <style>
16
+ :root {
17
+ --bg-color: #0f172a;
18
+ --surface-color: rgba(30, 41, 59, 0.7);
19
+ --surface-border: rgba(255, 255, 255, 0.1);
20
+ --primary-color: #6366f1;
21
+ --primary-hover: #4f46e5;
22
+ --accent-color: #ec4899;
23
+ --text-main: #f8fafc;
24
+ --text-muted: #94a3b8;
25
+ --glass-blur: blur(12px);
26
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
27
+ --radius-lg: 16px;
28
+ --radius-md: 8px;
29
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
30
+ }
31
+
32
+ * {
33
+ box-sizing: border-box;
34
+ margin: 0;
35
+ padding: 0;
36
+ outline: none;
37
+ }
38
+
39
+ body {
40
+ font-family: 'Inter', sans-serif;
41
+ background-color: var(--bg-color);
42
+ background-image:
43
+ radial-gradient(at 0% 0%, hsla(253, 16%, 7%, 1) 0, transparent 50%),
44
+ radial-gradient(at 50% 0%, hsla(225, 39%, 30%, 1) 0, transparent 50%),
45
+ radial-gradient(at 100% 0%, hsla(339, 49%, 30%, 1) 0, transparent 50%);
46
+ color: var(--text-main);
47
+ min-height: 100vh;
48
+ display: flex;
49
+ flex-direction: column;
50
+ overflow-x: hidden;
51
+ }
52
+
53
+ /* Custom Scrollbar */
54
+ ::-webkit-scrollbar {
55
+ width: 8px;
56
+ }
57
+
58
+ ::-webkit-scrollbar-track {
59
+ background: rgba(0, 0, 0, 0.2);
60
+ }
61
+
62
+ ::-webkit-scrollbar-thumb {
63
+ background: var(--surface-border);
64
+ border-radius: 4px;
65
+ }
66
+
67
+ ::-webkit-scrollbar-thumb:hover {
68
+ background: var(--primary-color);
69
+ }
70
+
71
+ /* --- Header --- */
72
+ header {
73
+ display: flex;
74
+ justify-content: space-between;
75
+ align-items: center;
76
+ padding: 1.5rem 2rem;
77
+ background: rgba(15, 23, 42, 0.8);
78
+ backdrop-filter: var(--glass-blur);
79
+ border-bottom: 1px solid var(--surface-border);
80
+ position: sticky;
81
+ top: 0;
82
+ z-index: 100;
83
+ }
84
+
85
+ .logo {
86
+ font-size: 1.5rem;
87
+ font-weight: 800;
88
+ background: linear-gradient(to right, var(--primary-color), var(--accent-color));
89
+ -webkit-background-clip: text;
90
+ -webkit-text-fill-color: transparent;
91
+ letter-spacing: -0.5px;
92
+ display: flex;
93
+ align-items: center;
94
+ gap: 10px;
95
+ }
96
+
97
+ .anycoder-link {
98
+ font-size: 0.85rem;
99
+ color: var(--text-muted);
100
+ text-decoration: none;
101
+ padding: 0.5rem 1rem;
102
+ border: 1px solid var(--surface-border);
103
+ border-radius: 20px;
104
+ transition: var(--transition);
105
+ }
106
+
107
+ .anycoder-link:hover {
108
+ color: var(--text-main);
109
+ border-color: var(--primary-color);
110
+ background: rgba(99, 102, 241, 0.1);
111
+ }
112
+
113
+ /* --- Main Layout --- */
114
+ main {
115
+ flex: 1;
116
+ display: grid;
117
+ grid-template-columns: 1fr 380px;
118
+ gap: 2rem;
119
+ padding: 2rem;
120
+ max-width: 1400px;
121
+ margin: 0 auto;
122
+ width: 100%;
123
+ }
124
+
125
+ @media (max-width: 900px) {
126
+ main {
127
+ grid-template-columns: 1fr;
128
+ padding: 1rem;
129
+ }
130
+ }
131
+
132
+ /* --- Station List (Left Side) --- */
133
+ .station-list-container {
134
+ display: flex;
135
+ flex-direction: column;
136
+ gap: 1.5rem;
137
+ }
138
+
139
+ .search-bar {
140
+ position: relative;
141
+ }
142
+
143
+ .search-bar input {
144
+ width: 100%;
145
+ padding: 1rem 1rem 1rem 3rem;
146
+ background: var(--surface-color);
147
+ border: 1px solid var(--surface-border);
148
+ border-radius: var(--radius-lg);
149
+ color: var(--text-main);
150
+ font-size: 1rem;
151
+ transition: var(--transition);
152
+ }
153
+
154
+ .search-bar input:focus {
155
+ border-color: var(--primary-color);
156
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
157
+ }
158
+
159
+ .search-icon {
160
+ position: absolute;
161
+ left: 1rem;
162
+ top: 50%;
163
+ transform: translateY(-50%);
164
+ color: var(--text-muted);
165
+ width: 20px;
166
+ height: 20px;
167
+ }
168
+
169
+ .stations-grid {
170
+ display: grid;
171
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
172
+ gap: 1rem;
173
+ }
174
+
175
+ .station-card {
176
+ background: var(--surface-color);
177
+ border: 1px solid var(--surface-border);
178
+ border-radius: var(--radius-md);
179
+ padding: 1rem;
180
+ cursor: pointer;
181
+ transition: var(--transition);
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 1rem;
185
+ position: relative;
186
+ overflow: hidden;
187
+ }
188
+
189
+ .station-card:hover {
190
+ transform: translateY(-2px);
191
+ background: rgba(255, 255, 255, 0.05);
192
+ border-color: rgba(255, 255, 255, 0.2);
193
+ }
194
+
195
+ .station-card.active {
196
+ background: rgba(99, 102, 241, 0.15);
197
+ border-color: var(--primary-color);
198
+ }
199
+
200
+ .station-card.active::before {
201
+ content: '';
202
+ position: absolute;
203
+ left: 0;
204
+ top: 0;
205
+ bottom: 0;
206
+ width: 4px;
207
+ background: var(--primary-color);
208
+ }
209
+
210
+ .station-logo {
211
+ width: 48px;
212
+ height: 48px;
213
+ border-radius: 50%;
214
+ background: #333;
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: center;
218
+ font-weight: bold;
219
+ font-size: 0.8rem;
220
+ color: #fff;
221
+ flex-shrink: 0;
222
+ object-fit: cover;
223
+ }
224
+
225
+ .station-info h3 {
226
+ font-size: 1rem;
227
+ font-weight: 600;
228
+ margin-bottom: 0.25rem;
229
+ }
230
+
231
+ .station-info p {
232
+ font-size: 0.85rem;
233
+ color: var(--text-muted);
234
+ }
235
+
236
+ .play-indicator {
237
+ margin-left: auto;
238
+ opacity: 0;
239
+ color: var(--primary-color);
240
+ transition: var(--transition);
241
+ }
242
+
243
+ .station-card:hover .play-indicator,
244
+ .station-card.active .play-indicator {
245
+ opacity: 1;
246
+ }
247
+
248
+ /* --- Player (Right Side / Sticky) --- */
249
+ .player-container {
250
+ background: var(--surface-color);
251
+ backdrop-filter: var(--glass-blur);
252
+ border: 1px solid var(--surface-border);
253
+ border-radius: var(--radius-lg);
254
+ padding: 2rem;
255
+ height: fit-content;
256
+ position: sticky;
257
+ top: 100px;
258
+ display: flex;
259
+ flex-direction: column;
260
+ align-items: center;
261
+ box-shadow: var(--shadow-lg);
262
+ text-align: center;
263
+ }
264
+
265
+ @media (max-width: 900px) {
266
+ .player-container {
267
+ position: relative;
268
+ top: 0;
269
+ order: -1;
270
+ /* Player on top on mobile */
271
+ }
272
+ }
273
+
274
+ .player-visualizer {
275
+ display: flex;
276
+ align-items: flex-end;
277
+ justify-content: center;
278
+ gap: 4px;
279
+ height: 60px;
280
+ margin-bottom: 1.5rem;
281
+ width: 100%;
282
+ }
283
+
284
+ .bar {
285
+ width: 6px;
286
+ background: var(--primary-color);
287
+ border-radius: 4px;
288
+ height: 10%;
289
+ transition: height 0.1s ease;
290
+ }
291
+
292
+ .bar:nth-child(even) {
293
+ background: var(--accent-color);
294
+ }
295
+
296
+ .player-visualizer.playing .bar {
297
+ animation: bounce 1s infinite ease-in-out;
298
+ }
299
+
300
+ @keyframes bounce {
301
+
302
+ 0%,
303
+ 100% {
304
+ height: 10%;
305
+ }
306
+
307
+ 50% {
308
+ height: 100%;
309
+ }
310
+ }
311
+
312
+ /* Stagger animations */
313
+ .bar:nth-child(1) {
314
+ animation-duration: 0.8s;
315
+ }
316
+
317
+ .bar:nth-child(2) {
318
+ animation-duration: 1.1s;
319
+ }
320
+
321
+ .bar:nth-child(3) {
322
+ animation-duration: 0.9s;
323
+ }
324
+
325
+ .bar:nth-child(4) {
326
+ animation-duration: 1.2s;
327
+ }
328
+
329
+ .bar:nth-child(5) {
330
+ animation-duration: 0.7s;
331
+ }
332
+
333
+ .bar:nth-child(6) {
334
+ animation-duration: 1.0s;
335
+ }
336
+
337
+ .bar:nth-child(7) {
338
+ animation-duration: 0.8s;
339
+ }
340
+
341
+ .bar:nth-child(8) {
342
+ animation-duration: 1.3s;
343
+ }
344
+
345
+ .current-station-art {
346
+ width: 150px;
347
+ height: 150px;
348
+ border-radius: 20px;
349
+ background: linear-gradient(135deg, #333, #111);
350
+ margin-bottom: 1.5rem;
351
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
352
+ display: flex;
353
+ align-items: center;
354
+ justify-content: center;
355
+ font-size: 3rem;
356
+ color: var(--text-muted);
357
+ position: relative;
358
+ overflow: hidden;
359
+ }
360
+
361
+ .current-station-art img {
362
+ width: 100%;
363
+ height: 100%;
364
+ object-fit: cover;
365
+ }
366
+
367
+ .current-station-name {
368
+ font-size: 1.5rem;
369
+ font-weight: 700;
370
+ margin-bottom: 0.5rem;
371
+ line-height: 1.2;
372
+ }
373
+
374
+ .current-station-genre {
375
+ display: inline-block;
376
+ background: rgba(255, 255, 255, 0.1);
377
+ padding: 4px 12px;
378
+ border-radius: 12px;
379
+ font-size: 0.8rem;
380
+ color: var(--text-muted);
381
+ margin-bottom: 2rem;
382
+ }
383
+
384
+ .controls {
385
+ display: flex;
386
+ align-items: center;
387
+ gap: 1.5rem;
388
+ width: 100%;
389
+ justify-content: center;
390
+ margin-bottom: 2rem;
391
+ }
392
+
393
+ .btn-control {
394
+ background: none;
395
+ border: none;
396
+ cursor: pointer;
397
+ color: var(--text-main);
398
+ transition: var(--transition);
399
+ display: flex;
400
+ align-items: center;
401
+ justify-content: center;
402
+ }
403
+
404
+ .btn-control:hover {
405
+ color: var(--primary-color);
406
+ transform: scale(1.1);
407
+ }
408
+
409
+ .btn-play {
410
+ width: 64px;
411
+ height: 64px;
412
+ border-radius: 50%;
413
+ background: var(--primary-color);
414
+ box-shadow: 0 0 20px rgba(99, 102, 241, 0.4);
415
+ }
416
+
417
+ .btn-play:hover {
418
+ background: var(--primary-hover);
419
+ color: white;
420
+ box-shadow: 0 0 30px rgba(99, 102, 241, 0.6);
421
+ }
422
+
423
+ .volume-container {
424
+ display: flex;
425
+ align-items: center;
426
+ gap: 10px;
427
+ width: 100%;
428
+ max-width: 200px;
429
+ }
430
+
431
+ input[type="range"] {
432
+ -webkit-appearance: none;
433
+ width: 100%;
434
+ height: 6px;
435
+ background: rgba(255, 255, 255, 0.1);
436
+ border-radius: 3px;
437
+ cursor: pointer;
438
+ }
439
+
440
+ input[type="range"]::-webkit-slider-thumb {
441
+ -webkit-appearance: none;
442
+ width: 16px;
443
+ height: 16px;
444
+ background: var(--text-main);
445
+ border-radius: 50%;
446
+ transition: var(--transition);
447
+ }
448
+
449
+ input[type="range"]::-webkit-slider-thumb:hover {
450
+ background: var(--primary-color);
451
+ transform: scale(1.2);
452
+ }
453
+
454
+ /* --- Footer --- */
455
+ footer {
456
+ text-align: center;
457
+ padding: 2rem;
458
+ color: var(--text-muted);
459
+ font-size: 0.9rem;
460
+ border-top: 1px solid var(--surface-border);
461
+ margin-top: auto;
462
+ }
463
+
464
+ /* --- Loading State --- */
465
+ .loading-spinner {
466
+ width: 20px;
467
+ height: 20px;
468
+ border: 2px solid rgba(255, 255, 255, 0.3);
469
+ border-top-color: var(--primary-color);
470
+ border-radius: 50%;
471
+ animation: spin 1s linear infinite;
472
+ display: none;
473
+ }
474
+
475
+ @keyframes spin {
476
+ to {
477
+ transform: rotate(360deg);
478
+ }
479
+ }
480
+
481
+ .status-message {
482
+ margin-top: 1rem;
483
+ font-size: 0.85rem;
484
+ min-height: 1.2em;
485
+ color: var(--accent-color);
486
+ }
487
+ </style>
488
  </head>
489
+
490
  <body>
491
 
492
+ <header>
493
+ <div class="logo">
494
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
495
+ stroke-linecap="round" stroke-linejoin="round">
496
+ <path d="M12 2c-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-.5 5-1.3M15 5a5 5 0 0 1 5 5v1M15 9a3 3 0 0 1 3 3v1" />
497
+ <circle cx="12" cy="12" r="2" />
498
+ </svg>
499
+ DE Radio
500
+ </div>
501
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with
502
+ anycoder</a>
503
+ </header>
504
+
505
+ <main>
506
+ <!-- Senderliste -->
507
+ <section class="station-list-container">
508
+ <div class="search-bar">
509
+ <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
510
+ stroke-linecap="round" stroke-linejoin="round">
511
+ <circle cx="11" cy="11" r="8"></circle>
512
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
513
+ </svg>
514
+ <input type="text" id="searchInput" placeholder="Sender suchen...">
515
+ </div>
516
+
517
+ <div class="stations-grid" id="stationsGrid">
518
+ <!-- Stationen werden hier per JS eingefügt -->
519
+ </div>
520
+ </section>
521
+
522
+ <!-- Player -->
523
+ <section class="player-container">
524
+ <div class="player-visualizer" id="visualizer">
525
+ <div class="bar"></div>
526
+ <div class="bar"></div>
527
+ <div class="bar"></div>
528
+ <div class="bar"></div>
529
+ <div class="bar"></div>
530
+ <div class="bar"></div>
531
+ <div class="bar"></div>
532
+ <div class="bar"></div>
533
+ </div>
534
+
535
+ <div class="current-station-art" id="playerArt">
536
+ <svg width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
537
+ stroke-linecap="round" stroke-linejoin="round">
538
+ <path d="M9 18V5l12-2v13"></path>
539
+ <circle cx="6" cy="18" r="3"></circle>
540
+ <circle cx="18" cy="16" r="3"></circle>
541
+ </svg>
542
+ </div>
543
+
544
+ <h2 class="current-station-name" id="playerTitle">Wähle einen Sender</h2>
545
+ <span class="current-station-genre" id="playerGenre">Bereit</span>
546
+
547
+ <div class="controls">
548
+ <button class="btn-control btn-play" id="playPauseBtn" aria-label="Play/Pause">
549
  <svg id="iconPlay" width="32" height="32" viewBox="0 0 24 24" fill="currentColor">
550
  <polygon points="5 3 19 12 5 21 5 3"></polygon>
551
  </svg>
 
555
  </svg>
556
  <div class="loading-spinner" id="loadingSpinner"></div>
557
  </button>
558
+ </div>
559
+
560
+ <div class="volume-container">
561
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
562
+ stroke-linecap="round" stroke-linejoin="round">
563
+ <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
564
+ </svg>
565
+ <input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="0.8">
566
+ </div>
567
+ <div class="status-message" id="statusMessage"></div>
568
+ </section>
569
+ </main>
570
+
571
+ <footer>
572
+ <p>&copy; 2023 DE Radio Stream Player. Hören Sie Ihre Lieblingssender.</p>
573
+ </footer>
574
+
575
+ <script>
576
+ // Aktualisierte Senderdaten mit HTTPS-Links (CORS-freundliche URLs wo möglich)
577
+ const stations = [
578
+ { name: "Antenne Bayern", genre: "Pop", url: "https://stream.antenne.de/antenne-bayern", color: "#ea580c" },
579
+ { name: "BAYERN 3", genre: "Pop & Rock", url: "https://br-mp3-f-shz-cybr.sharp-stream.com/br3/live/mp3/128/stream.mp3", color: "#2563eb" },
580
+ { name: "1LIVE", genre: "Pop & Electro", url: "https://wdr-1live-live.icecast.wdr.de/wdr/1live/live/mp3/128/stream.mp3", color: "#7c3aed" },
581
+ { name: "WDR 2", genre: "Pop & Info", url: "https://wdr-wdr2-rheinland.icecast.wdr.de/wdr/wdr2/rheinland/mp3/128/stream.mp3", color: "#0891b2" },
582
+ { name: "SWR3", genre: "Pop", url: "https://swr-swr3-live.cast.addradio.de/swr/swr3/live/mp3/128/stream.mp3", color: "#db2777" },
583
+ { name: "NDR 2", genre: "Pop", url: "https://ndr-ndr2-niedersachsen.cast.addradio.de/ndr/ndr2/niedersachsen/mp3/128/stream.mp3", color: "#4338ca" },
584
+ { name: "NDR Info", genre: "Nachrichten", url: "https://ndr-ndrinfo-niedersachsen.cast.addradio.de/ndr/ndrinfo/niedersachsen/mp3/128/stream.mp3", color: "#334155" },
585
+ { name: "N-JOY", genre: "Pop", url: "https://ndr-njoy-live.cast.addradio.de/ndr/njoy/live/mp3/128/stream.mp3", color: "#16a34a" },
586
+ { name: "RBB 88.8", genre: "Berlin", url: "https://rbb-berlin-live.cast.addradio.de/rbb/berlin/live/mp3/128/stream.mp3", color: "#dc2626" },
587
+ { name: "RBB Radio Eins", genre: "Popkultur", url: "https://rbb-radioeins-live.cast.addradio.de/rbb/radioeins/live/mp3/128/stream.mp3", color: "#be185d" },
588
+ { name: "HR 3", genre: "Pop", url: "https://hr-hr3-live.cast.addradio.de/hr/hr3/live/mp3/128/stream.mp3", color: "#ca8a04" },
589
+ { name: "MDR JUMP", genre: "Dance & Pop", url: "https://mdr-jump-live.cast.addradio.de/mdr/jump/live/mp3/128/stream.mp3", color: "#65a30d" },
590
+ { name: "Deutschlandfunk", genre: "Nachrichten & Info", url: "https://st01.dlf.de/dlf/01/128/mp3/stream.mp3", color: "#e11d48" },
591
+ { name: "DRadio Wissen", genre: "Wissen", url: "https://st03.dlf.de/dlf/03/128/mp3/stream.mp3", color: "#059669" },
592
+ { name: "KISS FM", genre: "Dance & R&B", url: "https://stream.kissfm.de/kissfm/mp3-128", color: "#c026d3" },
593
+ { name: "JAM FM", genre: "Black & Pop", url: "https://stream.jam.fm/jamfm-live/mp3-128", color: "#f59e0b" },
594
+ { name: "Radio BOB", genre: "Rock", url: "https://stream.radiobob.de/bob-national/mp3-192", color: "#7f1d1d" },
595
+ { name: "105'5 Spreeradio", genre: "Berlin Hits", url: "https://stream.spreeradio.de/spreeradio-live-mp3-128", color: "#0ea5e9" },
596
+ { name: "Radio Paloma", genre: "Schlager", url: "https://stream.radio-paloma.de/live/mp3-192", color: "#f472b6" },
597
+ { name: "FFN", genre: "Pop & Rock", url: "https://stream.ffn.de/ffn/mp3-192", color: "#14b8a6" }
598
+ ];
599
+
600
+ // DOM Elemente
601
+ const stationsGrid = document.getElementById('stationsGrid');
602
+ const searchInput = document.getElementById('searchInput');
603
+ const audio = new Audio();
604
+ audio.crossOrigin = "anonymous"; // Wichtig für CORS
605
+
606
+ const playerTitle = document.getElementById('playerTitle');
607
+ const playerGenre = document.getElementById('playerGenre');
608
+ const playerArt = document.getElementById('playerArt');
609
+ const playPauseBtn = document.getElementById('playPauseBtn');
610
+ const iconPlay = document.getElementById('iconPlay');
611
+ const iconPause = document.getElementById('iconPause');
612
+ const loadingSpinner = document.getElementById('loadingSpinner');
613
+ const volumeSlider = document.getElementById('volumeSlider');
614
+ const visualizer = document.getElementById('visualizer');
615
+ const statusMessage = document.getElementById('statusMessage');
616
+
617
+ let currentStationIndex = -1;
618
+ let isPlaying = false;
619
+
620
+ // Initialisierung
621
+ function init() {
622
+ renderStations(stations);
623
+ audio.volume = volumeSlider.value;
624
+ }
625
+
626
+ // Stationen rendern
627
+ function renderStations(list) {
628
+ stationsGrid.innerHTML = '';
629
+ list.forEach((station, index) => {
630
+ const card = document.createElement('div');
631
+ card.className = 'station-card';
632
+ // Wir speichern den Namen im Dataset, um das richtige Element wiederzufinden
633
+ card.dataset.name = station.name;
634
+ card.onclick = () => selectStation(station);
635
+
636
+ // Initialen für das Logo
637
+ const initials = station.name.substring(0, 2).toUpperCase();
638
+
639
+ card.innerHTML = `
640
+ <div class="station-logo" style="background-color: ${station.color}">
641
+ ${initials}
642
+ </div>
643
+ <div class="station-info">
644
+ <h3>${station.name}</h3>
645
+ <p>${station.genre}</p>
646
+ </div>
647
+ <div class="play-indicator">
648
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
649
+ <polygon points="5 3 19 12 5 21 5 3"></polygon>
650
+ </svg>
651
+ </div>
652
+ `;
653
+ stationsGrid.appendChild(card);
 
 
 
 
 
 
 
 
654
  });
655
+ }
656
+
657
+ // Suche
658
+ searchInput.addEventListener('input', (e) => {
659
+ const term = e.target.value.toLowerCase();
660
+ const filtered = stations.filter(s =>
661
+ s.name.toLowerCase().includes(term) ||
662
+ s.genre.toLowerCase().includes(term)
663
+ );
664
+ renderStations(filtered);
665
+ });
666
+
667
+ // Station auswählen
668
+ function selectStation(station) {
669
+ // Wenn gleiche Station geklickt wird, nur Play/Pause toggeln
670
+ if (playerTitle.innerText === station.name) {
671
+ togglePlay();
672
+ return;
673
+ }
674
+
675
+ // UI Update
676
+ playerTitle.innerText = station.name;
677
+ playerGenre.innerText = station.genre;
678
+ playerArt.style.backgroundColor = station.color;
679
+ playerArt.innerHTML = `<span style="font-size: 3rem; font-weight: 800; color: rgba(255,255,255,0.8);">${station.name.substring(0,1).toUpperCase()}</span>`;
680
+
681
+ // Highlight in Liste
682
+ document.querySelectorAll('.station-card').forEach(card => {
683
+ card.classList.remove('active');
684
+ if (card.dataset.name === station.name) {
685
+ card.classList.add('active');
686
  }
687
+ });
688
 
689
+ // Audio laden
690
+ statusMessage.innerText = "Verbinde...";
691
+ statusMessage.style.color = "var(--text-muted)";
692
+
693
+ // Reset Audio Source
694
+ audio.src = station.url;
695
+ audio.load();
696
+
697
+ playAudio();
698
+ }
 
 
 
699
 
700
+ // Play/Pause Logik
701
+ function togglePlay() {
702
+ if (!audio.src) return;
703
+ if (audio.paused) {
 
 
704
  playAudio();
705
+ } else {
706
+ pauseAudio();
707
+ }
708
+ }
709
+
710
+ function playAudio() {
711
+ const playPromise = audio.play();
712
+
713
+ if (playPromise !== undefined) {
714
+ playPromise.then(_ => {
715
+ isPlaying = true;
716
+ updatePlayButton();
717
+ visualizer.classList.add('playing');
718
+ statusMessage.innerText = "";
719
+ })
720
+ .catch(error => {
721
+ console.error("Playback error:", error);
722
+ isPlaying = false;
723
+ updatePlayButton();
724
+ visualizer.classList.remove('playing');
725
+ statusMessage.innerText = "Fehler: Stream blockiert oder offline.";
726
+ statusMessage.style.color = "#ef4444";
727
+ });
728
  }
729
+ }
730
 
731
+ function pauseAudio() {
732
+ audio.pause();
733
+ isPlaying = false;
734
+ updatePlayButton();
735
+ visualizer.classList.remove('playing');
736
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
 
738
+ function updatePlayButton() {
739
+ if (isPlaying) {
 
 
 
 
 
 
740
  iconPlay.style.display = 'none';
741
+ iconPause.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
742
  loadingSpinner.style.display = 'none';
743
+ } else {
744
  iconPlay.style.display = 'block';
745
  iconPause.style.display = 'none';
746
+ loadingSpinner.style.display = 'none';
747
+ }
748
+ }
749
+
750
+ playPauseBtn.addEventListener('click', togglePlay);
751
+
752
+ // Lautstärke
753
+ volumeSlider.addEventListener('input', (e) => {
754
+ audio.volume = e.target.value;
755
+ });
756
+
757
+ // Audio Events
758
+ audio.addEventListener('waiting', () => {
759
+ loadingSpinner.style.display = 'block';
760
+ iconPlay.style.display = 'none';
761
+ iconPause.style.display = 'none';
762
+ statusMessage.innerText = "Puffer...";
763
+ });
764
+
765
+ audio.addEventListener('playing', () => {
766
+ loadingSpinner.style.display = 'none';
767
+ updatePlayButton();
768
+ });
769
+
770
+ audio.addEventListener('error', (e) => {
771
+ console.error("Stream Error", e);
772
+ loadingSpinner.style.display = 'none';
773
+ iconPlay.style.display = 'block';
774
+ iconPause.style.display = 'none';
775
+ visualizer.classList.remove('playing');
776
+ statusMessage.innerText = "Stream nicht verfügbar (CORS/Offline).";
777
+ statusMessage.style.color = "#ef4444";
778
+ });
779
+
780
+ // App starten
781
+ init();
782
+
783
+ </script>
784
  </body>
785
+
786
  </html>