diff --git a/web/src/App.tsx b/web/src/App.tsx index 17bc640..7daedf3 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -27,6 +27,10 @@ export default function App() { const [clock, setClock] = useState(() => new Intl.DateTimeFormat('de-DE', { hour: '2-digit', minute: '2-digit', hour12: false, timeZone: 'Europe/Berlin' }).format(new Date())); const [totalPlays, setTotalPlays] = useState(0); const [mediaUrl, setMediaUrl] = useState(''); + const [chaosMode, setChaosMode] = useState(false); + const chaosTimeoutRef = useRef(null); + const chaosModeRef = useRef(false); + useEffect(() => { chaosModeRef.current = chaosMode; }, [chaosMode]); useEffect(() => { (async () => { @@ -89,6 +93,11 @@ export default function App() { // Theme anwenden/persistieren useEffect(() => { document.body.setAttribute('data-theme', theme); + if (import.meta.env.VITE_BUILD_CHANNEL === 'nightly') { + document.body.setAttribute('data-build', 'nightly'); + } else { + document.body.removeAttribute('data-build'); + } localStorage.setItem('theme', theme); }, [theme]); @@ -160,6 +169,72 @@ export default function App() { } } + // CHAOS Mode Funktionen (zufällige Wiedergabe alle 1-3 Minuten) + const startChaosMode = async () => { + if (!selected || !sounds.length) return; + + const playRandomSound = async () => { + const pool = sounds; + if (!pool.length || !selected) return; + const randomSound = pool[Math.floor(Math.random() * pool.length)]; + const [guildId, channelId] = selected.split(':'); + try { + await playSound(randomSound.name, guildId, channelId, volume, randomSound.relativePath); + } catch (e: any) { + console.error('Chaos sound play failed:', e); + } + }; + + const scheduleNextPlay = async () => { + if (!chaosModeRef.current) return; + await playRandomSound(); + const delay = 60_000 + Math.floor(Math.random() * 60_000); // 60-120 Sekunden + chaosTimeoutRef.current = window.setTimeout(scheduleNextPlay, delay); + }; + + // Sofort ersten Sound abspielen + await playRandomSound(); + // Nächsten zufällig in 1-3 Minuten planen + const firstDelay = 60_000 + Math.floor(Math.random() * 60_000); + chaosTimeoutRef.current = window.setTimeout(scheduleNextPlay, firstDelay); + }; + + const stopChaosMode = async () => { + if (chaosTimeoutRef.current) { + clearTimeout(chaosTimeoutRef.current); + chaosTimeoutRef.current = null; + } + + // Alle Sounds stoppen (wie Panic Button) + if (selected) { + const [guildId] = selected.split(':'); + try { + await fetch(`/api/stop?guildId=${encodeURIComponent(guildId)}`, { method: 'POST' }); + } catch (e: any) { + console.error('Chaos stop failed:', e); + } + } + }; + + const toggleChaosMode = async () => { + if (chaosMode) { + setChaosMode(false); + await stopChaosMode(); + } else { + setChaosMode(true); + await startChaosMode(); + } + }; + + // Cleanup bei Komponenten-Unmount + useEffect(() => { + return () => { + if (chaosTimeoutRef.current) { + clearTimeout(chaosTimeoutRef.current); + } + }; + }, []); + return (
@@ -199,7 +274,17 @@ export default function App() { - + +
@@ -272,7 +357,24 @@ export default function App() { {!isAdmin ? ( <>
- setAdminPwd(e.target.value)} /> + setAdminPwd(e.target.value)} + onKeyDown={async (e)=>{ + if(e.key === 'Enter') { + const ok = await adminLogin(adminPwd); + if(ok) { + setIsAdmin(true); + setAdminPwd(''); + } else { + alert('Login fehlgeschlagen'); + } + } + }} + /> lock
@@ -316,6 +418,9 @@ export default function App() { + {error &&
{error}
} + {info &&
{info}
} +
@@ -338,9 +443,6 @@ export default function App() {
- {error &&
{error}
} - {info &&
{info}
} -
{(activeFolder === '__favs__' ? filtered.filter((s) => !!favs[s.relativePath ?? s.fileName]) : filtered).map((s) => { const key = `${s.relativePath ?? s.fileName}`; diff --git a/web/src/styles.css b/web/src/styles.css index 928b377..940fe0a 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -303,6 +303,25 @@ body { .container { width: 90vw; max-width: 1800px; margin: 0 auto; padding: 28px; } +/* Nightly Build: volle Breite (mind. 90% der Anzeige), kein max-width-Limit */ +[data-build="nightly"] .container { + width: 90vw; + max-width: none; +} + +/* CHAOS Button Regenbogen-Animation */ +.chaos-rainbow { + background: linear-gradient(45deg, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080); + background-size: 400% 400%; + animation: chaos-rainbow-animation 2s ease-in-out infinite; +} + +@keyframes chaos-rainbow-animation { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + /* Neuer Header-Style basierend auf Google Stitch Design */ header { display: flex; @@ -556,6 +575,16 @@ header p { padding: 12px 16px; /* gleichmäßiges Padding links/rechts */ justify-content: center; /* Text zentrieren */ } +/* Soundbutton-Text minimal kräftiger als 500 */ +.sounds-flow .sound-btn > span { font-weight: 501 !important; } + +/* URL Input mit Download Button - Text soll nicht über Button laufen */ +.input-field.pl-10.with-left-icon { + padding-right: 100px !important; /* Platz für Download Button */ + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} .sound-wrap { position: relative; display: block; } .sound-wrap.row .sound { width: 100%; } .row-check { width: 18px; height: 18px; accent-color: #60a5fa; }