diff --git a/web/src/plugins/radio/RadioTab.tsx b/web/src/plugins/radio/RadioTab.tsx index 3a4d7dd..54a4e0b 100644 --- a/web/src/plugins/radio/RadioTab.tsx +++ b/web/src/plugins/radio/RadioTab.tsx @@ -431,151 +431,12 @@ export default function RadioTab({ data }: { data: any }) { return (
- {/* ── Globe ── */} -
+ {/* ═══ TOPBAR ═══ */} +
+
+ {'\u{1F30D}'} + World Radio - {/* ── Theme Selector ── */} -
- {THEMES.map(t => ( -
setTheme(t.id)} - /> - ))} -
- - {/* ── Search ── */} -
-
- {'\u{1F50D}'} - handleSearch(e.target.value)} - onFocus={() => { if (searchResults.length) setSearchOpen(true); }} - /> - {searchQuery && ( - - )} -
- {searchOpen && searchResults.length > 0 && ( -
- {searchResults.slice(0, 12).map(hit => ( - - ))} -
- )} -
- - {/* ── Favorites toggle ── */} - - - {/* ── Side Panel: Favorites ── */} - {showFavorites && ( -
-
-

{'\u2B50'} Favoriten

- -
-
- {favorites.length === 0 ? ( -
Noch keine Favoriten
- ) : ( - favorites.map(fav => ( -
-
- {fav.stationName} - {fav.placeName}, {fav.country} -
-
- - -
-
- )) - )} -
-
- )} - - {/* ── Side Panel: Stations at place ── */} - {selectedPlace && !showFavorites && ( -
-
-
-

{selectedPlace.title}

- {selectedPlace.country} -
- -
-
- {stationsLoading ? ( -
-
- Sender werden geladen... -
- ) : stations.length === 0 ? ( -
Keine Sender gefunden
- ) : ( - stations.map(s => ( -
-
- {s.title} - {currentPlaying?.stationId === s.id && ( - - - Live - - )} -
-
- {currentPlaying?.stationId === s.id ? ( - - ) : ( - - )} - -
-
- )) - )} -
-
- )} - - {/* ── Bottom Bar ── */} -
-
{guilds.length > 1 && ( handleVolume(Number(e.target.value))} - /> - {Math.round(volume * 100)}% -
- {'\u{1F50A}'} {currentPlaying.channelName} -
setShowConnModal(true)} title="Verbindungsdetails"> - - Verbunden - {voiceStats?.voicePing != null && ( - {voiceStats.voicePing}ms - )} -
-
)} -
- {/* ── Places counter ── */} -
- {'\u{1F4FB}'} {places.length.toLocaleString('de-DE')} Sender weltweit +
+ {currentPlaying && ( + <> +
+ {volume === 0 ? '\u{1F507}' : volume < 0.4 ? '\u{1F509}' : '\u{1F50A}'} + handleVolume(Number(e.target.value))} + /> + {Math.round(volume * 100)}% +
+
setShowConnModal(true)} title="Verbindungsdetails"> + + Verbunden + {voiceStats?.voicePing != null && ( + {voiceStats.voicePing}ms + )} +
+ + + )} +
+ {THEMES.map(t => ( +
setTheme(t.id)} + /> + ))} +
+
+
+ + {/* ═══ GLOBE AREA ═══ */} +
+
+ + {/* ── Search ── */} +
+
+ {'\u{1F50D}'} + handleSearch(e.target.value)} + onFocus={() => { if (searchResults.length) setSearchOpen(true); }} + /> + {searchQuery && ( + + )} +
+ {searchOpen && searchResults.length > 0 && ( +
+ {searchResults.slice(0, 12).map(hit => ( + + ))} +
+ )} +
+ + {/* ── Favorites toggle ── */} + + + {/* ── Side Panel: Favorites ── */} + {showFavorites && ( +
+
+

{'\u2B50'} Favoriten

+ +
+
+ {favorites.length === 0 ? ( +
Noch keine Favoriten
+ ) : ( + favorites.map(fav => ( +
+
+ {fav.stationName} + {fav.placeName}, {fav.country} +
+
+ + +
+
+ )) + )} +
+
+ )} + + {/* ── Side Panel: Stations at place ── */} + {selectedPlace && !showFavorites && ( +
+
+
+

{selectedPlace.title}

+ {selectedPlace.country} +
+ +
+
+ {stationsLoading ? ( +
+
+ Sender werden geladen... +
+ ) : stations.length === 0 ? ( +
Keine Sender gefunden
+ ) : ( + stations.map(s => ( +
+
+ {s.title} + {currentPlaying?.stationId === s.id && ( + + + Live + + )} +
+
+ {currentPlaying?.stationId === s.id ? ( + + ) : ( + + )} + +
+
+ )) + )} +
+
+ )} + + {/* ── Places counter ── */} +
+ {'\u{1F4FB}'} {places.length.toLocaleString('de-DE')} Sender weltweit +
{/* ── Connection Details Modal ── */} - {showConnModal && voiceStats && (() => { - const uptimeSec = voiceStats.connectedSince + {showConnModal && (() => { + const uptimeSec = voiceStats?.connectedSince ? Math.floor((Date.now() - new Date(voiceStats.connectedSince).getTime()) / 1000) : 0; const h = Math.floor(uptimeSec / 3600); @@ -657,30 +666,30 @@ export default function RadioTab({ data }: { data: any }) {
Voice Ping - - {voiceStats.voicePing != null ? `${voiceStats.voicePing} ms` : '---'} + + {voiceStats?.voicePing != null ? `${voiceStats.voicePing} ms` : '---'}
Gateway Ping - - {voiceStats.gatewayPing >= 0 ? `${voiceStats.gatewayPing} ms` : '---'} + + {voiceStats && voiceStats.gatewayPing >= 0 ? `${voiceStats.gatewayPing} ms` : '---'}
Status - - {voiceStats.status === 'ready' ? 'Verbunden' : voiceStats.status} + + {voiceStats?.status === 'ready' ? 'Verbunden' : voiceStats?.status ?? 'Warte auf Verbindung'}
Kanal - {voiceStats.channelName || '---'} + {voiceStats?.channelName || '---'}
Verbunden seit - {uptimeStr} + {uptimeStr || '---'}
diff --git a/web/src/plugins/soundboard/SoundboardTab.tsx b/web/src/plugins/soundboard/SoundboardTab.tsx index 504da5b..5ab4f24 100644 --- a/web/src/plugins/soundboard/SoundboardTab.tsx +++ b/web/src/plugins/soundboard/SoundboardTab.tsx @@ -1238,8 +1238,8 @@ export default function SoundboardTab({ data }: SoundboardTabProps) { )} {/* ═══ CONNECTION MODAL ═══ */} - {showConnModal && voiceStats && (() => { - const uptimeSec = voiceStats.connectedSince + {showConnModal && (() => { + const uptimeSec = voiceStats?.connectedSince ? Math.floor((Date.now() - new Date(voiceStats.connectedSince).getTime()) / 1000) : 0; const h = Math.floor(uptimeSec / 3600); @@ -1266,30 +1266,30 @@ export default function SoundboardTab({ data }: SoundboardTabProps) {
Voice Ping - - {voiceStats.voicePing != null ? `${voiceStats.voicePing} ms` : '---'} + + {voiceStats?.voicePing != null ? `${voiceStats.voicePing} ms` : '---'}
Gateway Ping - - {voiceStats.gatewayPing >= 0 ? `${voiceStats.gatewayPing} ms` : '---'} + + {voiceStats && voiceStats.gatewayPing >= 0 ? `${voiceStats.gatewayPing} ms` : '---'}
Status - - {voiceStats.status === 'ready' ? 'Verbunden' : voiceStats.status} + + {voiceStats?.status === 'ready' ? 'Verbunden' : voiceStats?.status ?? 'Warte auf Verbindung'}
Kanal - {voiceStats.channelName || '---'} + {voiceStats?.channelName || '---'}
Verbunden seit - {uptimeStr} + {uptimeStr || '---'}
diff --git a/web/src/styles.css b/web/src/styles.css index 163e95e..98d2fb5 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -354,7 +354,8 @@ html, body { ══════════════════════════════════════════════ */ .radio-container { - position: relative; + display: flex; + flex-direction: column; width: 100%; height: 100%; overflow: hidden; @@ -416,6 +417,88 @@ html, body { } /* ── Globe ── */ +/* ── Radio Topbar ── */ +.radio-topbar { + display: flex; + align-items: center; + padding: 0 16px; + height: 52px; + background: var(--bg-secondary, #2b2d31); + border-bottom: 1px solid rgba(0, 0, 0, .24); + z-index: 10; + flex-shrink: 0; + gap: 16px; +} + +.radio-topbar-left { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; +} + +.radio-topbar-logo { + font-size: 20px; +} + +.radio-topbar-title { + font-size: 16px; + font-weight: 700; + color: var(--text-normal); + letter-spacing: -.02em; +} + +.radio-topbar-np { + flex: 1; + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + justify-content: center; +} + +.radio-topbar-right { + display: flex; + align-items: center; + gap: 6px; + flex-shrink: 0; +} + +.radio-topbar-stop { + display: flex; + align-items: center; + gap: 4px; + background: var(--danger); + color: #fff; + border: none; + border-radius: var(--radius); + padding: 6px 14px; + font-size: 13px; + font-family: var(--font); + font-weight: 600; + cursor: pointer; + transition: all var(--transition); + flex-shrink: 0; +} + +.radio-topbar-stop:hover { + background: #c63639; +} + +.radio-theme-inline { + display: flex; + align-items: center; + gap: 4px; + margin-left: 4px; +} + +/* ── Globe Wrapper ── */ +.radio-globe-wrap { + position: relative; + flex: 1; + overflow: hidden; +} + .radio-globe { width: 100%; height: 100%; @@ -819,29 +902,6 @@ html, body { 50% { transform: scaleY(1); } } -/* ── Bottom Bar ── */ -.radio-bar { - position: absolute; - bottom: 0; - left: 0; - right: 0; - z-index: 20; - background: rgba(30, 31, 34, 0.95); - backdrop-filter: blur(16px); - border-top: 1px solid var(--border); - padding: 10px 16px; - display: flex; - align-items: center; - gap: 16px; - box-shadow: 0 -4px 24px rgba(0,0,0,0.3); -} - -.radio-bar-channel { - display: flex; - gap: 8px; - flex-shrink: 0; -} - .radio-sel { background: var(--bg-secondary); border: 1px solid var(--border); @@ -859,15 +919,6 @@ html, body { border-color: var(--accent); } -/* ── Now Playing ── */ -.radio-np { - display: flex; - align-items: center; - gap: 12px; - flex: 1; - min-width: 0; -} - .radio-eq-np { flex-shrink: 0; } @@ -897,21 +948,6 @@ html, body { text-overflow: ellipsis; } -.radio-np-ch { - font-size: 12px; - color: var(--text-faint); - white-space: nowrap; - flex-shrink: 0; -} - -.radio-bar .radio-btn-stop { - width: auto; - border-radius: var(--radius); - padding: 6px 14px; - font-size: 13px; - gap: 4px; - flex-shrink: 0; -} /* ── Volume Slider ── */ .radio-volume { @@ -968,22 +1004,6 @@ html, body { text-align: right; } -/* ── Theme Selector ── */ -.radio-theme { - position: absolute; - top: 16px; - right: 72px; - z-index: 25; - display: flex; - align-items: center; - gap: 5px; - padding: 5px 10px; - border-radius: 20px; - background: rgba(30, 31, 34, 0.85); - backdrop-filter: blur(12px); - border: 1px solid var(--border); -} - .radio-theme-dot { width: 16px; height: 16px; @@ -1005,7 +1025,7 @@ html, body { /* ── Station count ── */ .radio-counter { position: absolute; - bottom: 70px; + bottom: 16px; left: 16px; z-index: 10; font-size: 12px; @@ -1049,30 +1069,28 @@ html, body { left: calc(50% - 24px); } - .radio-bar { - flex-wrap: wrap; - padding: 8px 12px; + .radio-topbar { + padding: 0 12px; gap: 8px; } + .radio-topbar-title { + display: none; + } + .radio-sel { max-width: 140px; font-size: 12px; } - - .radio-counter { - bottom: 62px; - left: 12px; - } } @media (max-width: 480px) { - .radio-np-ch { + .radio-topbar-np { display: none; } - .radio-bar-channel { - flex-wrap: wrap; + .radio-volume { + display: none; } .radio-sel {