Feat: Now-Playing serverseitig syncen + in Topbar verschieben

Backend:
- nowPlaying Map trackt aktuell gespielten Sound pro Guild
- SSE broadcast { type: 'nowplaying' } bei play und stop
- nowplaying im SSE-Snapshot für neue Clients
- playFilePath Helper broadcastet ebenfalls (Party Mode)

Frontend:
- SSE-Handler für nowplaying Events (sync über alle Clients)
- Now-Playing als Pill-Badge in der Topbar (rechts, neben Channel)
- Bottombar komplett entfernt
- Fade-in Animation und accent-farbige Pill
- --accent-rgb CSS Variable für alle Themes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bot 2026-03-01 16:00:22 +01:00
parent 4661c366fb
commit f90401a009
3 changed files with 58 additions and 46 deletions

View file

@ -154,12 +154,20 @@ export default function App() {
const g = selectedRef.current?.split(':')[0];
if (g && typeof vols[g] === 'number') setVolume(vols[g]);
} catch { }
try {
const np = msg?.nowplaying || {};
const g = selectedRef.current?.split(':')[0];
if (g && typeof np[g] === 'string') setLastPlayed(np[g]);
} catch { }
} else if (msg?.type === 'channel') {
const g = selectedRef.current?.split(':')[0];
if (msg.guildId === g) setSelected(`${msg.guildId}:${msg.channelId}`);
} else if (msg?.type === 'volume') {
const g = selectedRef.current?.split(':')[0];
if (msg.guildId === g && typeof msg.volume === 'number') setVolume(msg.volume);
} else if (msg?.type === 'nowplaying') {
const g = selectedRef.current?.split(':')[0];
if (msg.guildId === g) setLastPlayed(msg.name || '');
}
});
return () => { try { unsub(); } catch { } };
@ -362,6 +370,15 @@ export default function App() {
</div>
<div className="topbar-right">
{lastPlayed && (
<div className="now-playing">
<div className="np-waves active">
<div className="np-wave-bar" /><div className="np-wave-bar" />
<div className="np-wave-bar" /><div className="np-wave-bar" />
</div>
<span className="np-name">{lastPlayed}</span>
</div>
)}
{selected && (
<div className="connection">
<span className="conn-dot" />
@ -594,18 +611,6 @@ export default function App() {
)}
</main>
{/* ═══ BOTTOM BAR ═══ */}
<div className="bottombar">
<div className="now-playing">
<div className={`np-waves ${lastPlayed ? 'active' : ''}`}>
<div className="np-wave-bar" /><div className="np-wave-bar" />
<div className="np-wave-bar" /><div className="np-wave-bar" />
</div>
<span className="np-label">Spielt:</span>
<span className="np-name">{lastPlayed || '—'}</span>
</div>
</div>
{/* ═══ CONTEXT MENU ═══ */}
{ctxMenu && (
<div

View file

@ -23,6 +23,7 @@
--text-faint: #6d6f78;
--accent: #5865f2;
--accent-rgb: 88, 101, 242;
--accent-hover: #4752c4;
--accent-glow: rgba(88, 101, 242, .45);
@ -53,6 +54,7 @@
--bg-secondary: #241f35;
--bg-tertiary: #2e2845;
--accent: #9b59b6;
--accent-rgb: 155, 89, 182;
--accent-hover: #8e44ad;
--accent-glow: rgba(155, 89, 182, .45);
}
@ -64,6 +66,7 @@
--bg-secondary: #1c2e22;
--bg-tertiary: #253a2c;
--accent: #2ecc71;
--accent-rgb: 46, 204, 113;
--accent-hover: #27ae60;
--accent-glow: rgba(46, 204, 113, .4);
}
@ -75,6 +78,7 @@
--bg-secondary: #2f201c;
--bg-tertiary: #3d2a24;
--accent: #e67e22;
--accent-rgb: 230, 126, 34;
--accent-hover: #d35400;
--accent-glow: rgba(230, 126, 34, .4);
}
@ -86,6 +90,7 @@
--bg-secondary: #162a42;
--bg-tertiary: #1e3652;
--accent: #3498db;
--accent-rgb: 52, 152, 219;
--accent-hover: #2980b9;
--accent-glow: rgba(52, 152, 219, .4);
}
@ -962,36 +967,25 @@ input, select {
max-width: 260px;
}
/*
Bottom Bar
*/
.bottombar {
display: flex;
align-items: center;
gap: 14px;
padding: 0 20px;
height: 48px;
background: var(--bg-secondary);
border-top: 1px solid rgba(0, 0, 0, .24);
z-index: 10;
flex-shrink: 0;
transition: background .4s ease;
}
/* ── Now Playing (Topbar) ── */
.now-playing {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
gap: 6px;
padding: 4px 12px;
border-radius: 20px;
background: rgba(var(--accent-rgb, 88, 101, 242), .12);
border: 1px solid rgba(var(--accent-rgb, 88, 101, 242), .2);
font-size: 12px;
color: var(--text-muted);
max-width: 200px;
min-width: 0;
flex: 1;
animation: np-fade-in 300ms ease;
}
.np-label {
color: var(--text-faint);
font-size: 12px;
white-space: nowrap;
@keyframes np-fade-in {
from { opacity: 0; transform: translateX(10px); }
to { opacity: 1; transform: translateX(0); }
}
.np-name {
@ -1006,7 +1000,8 @@ input, select {
display: none;
gap: 1.5px;
align-items: flex-end;
height: 14px;
height: 12px;
flex-shrink: 0;
}
.np-waves.active {
@ -1014,16 +1009,16 @@ input, select {
}
.np-wave-bar {
width: 2.5px;
width: 2px;
background: var(--accent);
border-radius: 1px;
animation: wave 500ms ease-in-out infinite alternate;
}
.np-wave-bar:nth-child(1) { height: 4px; animation-delay: 0ms; }
.np-wave-bar:nth-child(2) { height: 9px; animation-delay: 120ms; }
.np-wave-bar:nth-child(3) { height: 6px; animation-delay: 240ms; }
.np-wave-bar:nth-child(4) { height: 11px; animation-delay: 80ms; }
.np-wave-bar:nth-child(1) { height: 3px; animation-delay: 0ms; }
.np-wave-bar:nth-child(2) { height: 8px; animation-delay: 120ms; }
.np-wave-bar:nth-child(3) { height: 5px; animation-delay: 240ms; }
.np-wave-bar:nth-child(4) { height: 10px; animation-delay: 80ms; }
/* ── Volume Control (Toolbar) ── */
.volume-control {
@ -1398,10 +1393,6 @@ input, select {
font-size: 11px;
}
.bottombar {
padding: 0 12px;
}
.tb-btn span:not(.tb-icon) {
display: none;
}
@ -1416,6 +1407,10 @@ input, select {
display: none;
}
.now-playing {
max-width: 120px;
}
.toolbar .tb-btn {
padding: 6px 8px;
}