feat: Schocken - erstes spielbares Grundgerüst (admin only)

This commit is contained in:
2026-07-05 00:08:26 +02:00
parent 772cbc62ba
commit 19619cb57c
5 changed files with 1204 additions and 8 deletions

View File

@@ -0,0 +1,595 @@
import { useState, useEffect, useCallback } from 'react';
import { api, S } from '../lib.js';
const MY_COLOR = '#4ecdc4';
function getMyId() {
try { return JSON.parse(atob(localStorage.getItem('sk_token').split('.')[1])).id; } catch { return null; }
}
function getMyRole() {
try { return JSON.parse(atob(localStorage.getItem('sk_token').split('.')[1])).role; } catch { return null; }
}
// ── Würfel-Visualisierung ─────────────────────────────────────────────────────
const DOTS = {
1: [[50,50]],
2: [[25,25],[75,75]],
3: [[25,25],[50,50],[75,75]],
4: [[25,25],[75,25],[25,75],[75,75]],
5: [[25,25],[75,25],[50,50],[25,75],[75,75]],
6: [[25,25],[75,25],[25,50],[75,50],[25,75],[75,75]],
};
function Die({ value, kept, selected, onClick, dark, size=56 }) {
const dots = value ? DOTS[value] : [];
const bg = dark ? 'rgba(40,40,60,0.9)' : kept ? 'rgba(78,205,196,0.15)' : 'rgba(255,255,255,0.08)';
const border = selected ? '2px solid #4ecdc4' : kept ? '2px solid rgba(78,205,196,0.4)' : '1px solid rgba(255,255,255,0.15)';
return (
<div onClick={onClick} style={{
width:size, height:size, borderRadius:10, background:bg, border,
cursor:onClick?'pointer':'default', position:'relative', flexShrink:0,
transition:'all 0.15s', boxShadow: selected ? '0 0 12px rgba(78,205,196,0.4)' : 'none',
}}>
{dark && <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',color:'rgba(255,255,255,0.2)',fontSize:22}}>🎲</div>}
{!dark && dots.map(([x,y],i) => (
<div key={i} style={{
position:'absolute',
left:`${x}%`, top:`${y}%`,
transform:'translate(-50%,-50%)',
width:size>40?10:7, height:size>40?10:7,
borderRadius:'50%', background:'#fff',
}}/>
))}
{!value && !dark && <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',color:'rgba(255,255,255,0.15)',fontSize:10,fontFamily:'monospace'}}>?</div>}
</div>
);
}
// ── Spieler-Zeile ─────────────────────────────────────────────────────────────
function PlayerRow({ player, chips, has16th, isMe, isCurrent, result, drinkLosses, game }) {
const isLoserH1 = game.has_16th === player.id;
return (
<div style={{
...S.card, padding:'10px 14px', marginBottom:8,
border: isCurrent ? '1px solid rgba(78,205,196,0.4)' : '1px solid rgba(255,255,255,0.07)',
background: isMe ? 'rgba(78,205,196,0.04)' : 'rgba(255,255,255,0.02)',
}}>
<div style={{display:'flex',alignItems:'center',gap:10}}>
<div style={{flex:1}}>
<div style={{color:'#fff',fontFamily:'monospace',fontSize:13,display:'flex',alignItems:'center',gap:6}}>
{isCurrent && <span style={{color:MY_COLOR}}></span>}
<strong>{player.username}</strong>
{isMe && <span style={{color:'rgba(255,255,255,0.3)',fontSize:10}}>(du)</span>}
{isLoserH1 && <span title="Verlierer 1. Hälfte" style={{fontSize:14}}>🪶</span>}
</div>
</div>
<div style={{display:'flex',alignItems:'center',gap:12}}>
{drinkLosses > 0 && (
<span style={{color:'#f59e0b',fontFamily:'monospace',fontSize:11}}>🍺×{drinkLosses}</span>
)}
<div style={{textAlign:'center'}}>
<div style={{color:chips>0?'#ff6b9d':MY_COLOR,fontFamily:'Space Mono,monospace',fontSize:18,fontWeight:700,lineHeight:1}}>{chips}</div>
<div style={{color:'rgba(255,255,255,0.25)',fontSize:9,fontFamily:'monospace'}}>Scheiben</div>
</div>
</div>
</div>
{result && (
<div style={{marginTop:6,padding:'4px 8px',background:'rgba(255,255,255,0.04)',borderRadius:6,
color:MY_COLOR,fontFamily:'monospace',fontSize:11}}>
{result.label}
</div>
)}
</div>
);
}
// ── Würfelbereich ─────────────────────────────────────────────────────────────
function DiceArea({ game, myId, onRoll, onReady, onEvaluate }) {
const [selectedKeep, setSelectedKeep] = useState([]);
const [goDark, setGoDark] = useState(false);
const [flipSixes, setFlipSixes] = useState(false);
const [rolling, setRolling] = useState(false);
const round = game.current_round;
const players = game.players;
const activePlayers = getActivePlayers(game);
const myState = round?.playerStates?.[myId];
const currentPlayer = activePlayers[game.current_player_idx % activePlayers.length];
const isMyTurn = currentPlayer?.id === myId;
const allReady = activePlayers.every(p => round?.ready?.[p.id]);
const allDone = round?.phase === 'reveal';
const isFirstRound = !!game.first_round;
const myDice = myState?.dice || [null, null, null];
const myRolls = myState?.roll_count || 0;
const maxRolls = round?.max_rolls || 3;
const canFlipSixes = myDice.filter(d => d===6).length >= 2 && myRolls < maxRolls - 1;
// Becher umdrehen Phase — warte bis alle ready
if (!round || !allReady) {
const iMReady = round?.ready?.[myId];
const readyCount = Object.keys(round?.ready || {}).length;
const activeCount = activePlayers.length;
return (
<div>
<div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:12,textAlign:'center',marginBottom:12}}>
{readyCount}/{activeCount} haben den Becher umgedreht
</div>
{!iMReady && (
<button onClick={onReady} style={{...S.btn('#4ecdc4'),width:'100%',fontSize:14,padding:'12px'}}>
🎲 Becher umdrehen
</button>
)}
{iMReady && (
<div style={{color:MY_COLOR,fontFamily:'monospace',fontSize:12,textAlign:'center',padding:'12px 0'}}>
Becher umgedreht warte auf andere
</div>
)}
{isFirstRound && iMReady && readyCount === activeCount && (
<div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,textAlign:'center',marginTop:8}}>
Alle bereit Becher werden gleichzeitig gehoben!
</div>
)}
</div>
);
}
// Auswertungsphase
if (allDone) {
const isFirstPlayer = activePlayers[0]?.id === myId || players[0]?.id === myId;
return (
<div>
<div style={{color:MY_COLOR,fontFamily:'monospace',fontSize:12,textAlign:'center',marginBottom:12}}>
Alle haben gewürfelt Ergebnis auswerten!
</div>
{isFirstPlayer && (
<button onClick={onEvaluate} style={{...S.btn('#ffe66d'),width:'100%'}}>
Runde auswerten &amp; Scheiben verteilen
</button>
)}
</div>
);
}
// Mein Würfeln
return (
<div>
{/* Status */}
<div style={{color:isMyTurn?MY_COLOR:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:11,textAlign:'center',marginBottom:10}}>
{isMyTurn ? `▶ Dein Wurf (${myRolls}/${maxRolls||'?'})` : `${currentPlayer?.username} würfelt…`}
</div>
{/* Würfel */}
<div style={{display:'flex',gap:10,justifyContent:'center',marginBottom:14}}>
{myDice.map((d, i) => (
<Die
key={i}
value={d}
dark={myState?.dark}
kept={selectedKeep.includes(i)}
selected={selectedKeep.includes(i)}
onClick={isMyTurn && myRolls > 0 && !myState?.done ? () => {
setSelectedKeep(prev =>
prev.includes(i) ? prev.filter(x=>x!==i) : [...prev,i]
);
} : undefined}
size={64}
/>
))}
</div>
{/* Optionen */}
{isMyTurn && !myState?.done && (
<div style={{display:'flex',flexDirection:'column',gap:8}}>
{myRolls > 0 && (
<div style={{display:'flex',gap:8,fontSize:11,fontFamily:'monospace',color:'rgba(255,255,255,0.5)'}}>
{selectedKeep.length > 0 && <span>📌 {selectedKeep.length} Würfel stehen lassen</span>}
</div>
)}
{canFlipSixes && (
<label style={{display:'flex',alignItems:'center',gap:8,cursor:'pointer',fontFamily:'monospace',fontSize:11,color:'rgba(255,255,255,0.6)'}}>
<input type="checkbox" checked={flipSixes} onChange={e=>setFlipSixes(e.target.checked)}/>
Zwei Sechsen zu Einer Eins umdrehen
</label>
)}
{myRolls > 0 && myRolls < (maxRolls||3) && (
<label style={{display:'flex',alignItems:'center',gap:8,cursor:'pointer',fontFamily:'monospace',fontSize:11,color:'rgba(255,255,255,0.6)'}}>
<input type="checkbox" checked={goDark} onChange={e=>setGoDark(e.target.checked)}/>
Dunkel legen (Becher nicht heben)
</label>
)}
<button
disabled={rolling}
onClick={async () => {
setRolling(true);
try { await onRoll(selectedKeep, flipSixes, goDark); setSelectedKeep([]); setFlipSixes(false); setGoDark(false); }
finally { setRolling(false); }
}}
style={{...S.btn(MY_COLOR), padding:'10px', fontSize:13}}
>
{rolling ? '🎲 Würfle…' : myRolls === 0 ? '🎲 Becher heben' : goDark ? '🎲 Dunkel würfeln' : '🎲 Nochmal würfeln'}
</button>
</div>
)}
{myState?.done && !allDone && (
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:11,textAlign:'center',padding:'8px 0'}}>
{myState.dark ? '🎲 Du bist dunkel — warte auf andere…' : `✓ Du stehst im ${myRolls}. — warte auf andere…`}
</div>
)}
{/* Andere Spieler Status */}
<div style={{marginTop:12,display:'flex',flexWrap:'wrap',gap:6}}>
{activePlayers.filter(p => p.id !== myId).map(p => {
const ps = round.playerStates?.[p.id];
return (
<div key={p.id} style={{
padding:'4px 10px', borderRadius:20,
background: ps?.done ? 'rgba(78,205,196,0.1)' : 'rgba(255,255,255,0.04)',
border: ps?.done ? '1px solid rgba(78,205,196,0.3)' : '1px solid rgba(255,255,255,0.08)',
color: ps?.done ? MY_COLOR : 'rgba(255,255,255,0.4)',
fontFamily:'monospace', fontSize:10,
}}>
{ps?.done ? '✓' : '⏳'} {p.username} {ps?.dark && '🌑'}
</div>
);
})}
</div>
</div>
);
}
// ── Schockstock ───────────────────────────────────────────────────────────────
function Schockstock({ total=15, stock, players, chips }) {
const used = total - stock;
return (
<div style={{...S.card,padding:'10px 14px',marginBottom:12}}>
<div style={{display:'flex',alignItems:'center',gap:8,marginBottom:8}}>
<span style={{color:'rgba(255,255,255,0.55)',fontFamily:'monospace',fontSize:11,letterSpacing:1}}>SCHOCKSTOCK</span>
<span style={{color:MY_COLOR,fontFamily:'monospace',fontSize:11,marginLeft:'auto'}}>{stock} verbleibend</span>
</div>
<div style={{display:'flex',gap:3,flexWrap:'wrap'}}>
{Array.from({length:16}).map((_,i) => {
const is16th = i === 15;
const isUsed = !is16th && i >= stock;
return (
<div key={i} style={{
width:14, height:14, borderRadius:'50%',
background: is16th
? 'rgba(245,158,11,0.6)'
: isUsed
? 'rgba(255,255,255,0.1)'
: '#4ecdc4',
border: is16th ? '1px solid #f59e0b' : '1px solid rgba(255,255,255,0.1)',
title: is16th ? '16. Scheibe' : '',
}}/>
);
})}
</div>
</div>
);
}
// ── Neue Runde Button ─────────────────────────────────────────────────────────
function NewRoundButton({ game, myId, onReady }) {
// Der Beginner der neuen Runde startet mit Becher umdrehen
const isStarter = game.beginner_id === myId;
return (
<div style={{...S.card,padding:'14px',textAlign:'center',marginBottom:12,border:'1px solid rgba(78,205,196,0.2)'}}>
<div style={{color:'rgba(255,255,255,0.6)',fontFamily:'monospace',fontSize:12,marginBottom:10}}>
Neue Runde {isStarter ? 'du fängst an!' : `${game.players.find(p=>p.id===game.beginner_id)?.username} fängt an`}
</div>
<button onClick={onReady} style={{...S.btn('#4ecdc4'),width:'100%',fontSize:14,padding:'12px'}}>
🎲 Becher umdrehen
</button>
</div>
);
}
// ── Spielende ─────────────────────────────────────────────────────────────────
function GameOver({ game }) {
const dl = game.drink_losses;
const loser = game.players.find(p => dl[p.id] > 0);
const isDoppelfeige = game.loser_h1 === game.loser_h2;
return (
<div style={{...S.card,padding:'20px',textAlign:'center',border:'1px solid rgba(245,158,11,0.3)'}}>
<div style={{fontSize:48,marginBottom:12}}>{isDoppelfeige ? '🪶🪶' : '🍺'}</div>
{isDoppelfeige ? (
<div style={{color:'#f59e0b',fontFamily:'Space Mono,monospace',fontSize:16,fontWeight:700,marginBottom:8}}>
DOPPELFEIGE!
</div>
) : (
<div style={{color:'#f59e0b',fontFamily:'Space Mono,monospace',fontSize:16,fontWeight:700,marginBottom:8}}>
RUNDE VORBEI
</div>
)}
{loser && (
<div style={{color:'rgba(255,255,255,0.7)',fontFamily:'monospace',fontSize:13,marginBottom:16}}>
{loser.username} zahlt die nächste Runde! 🍺
</div>
)}
<div style={{marginTop:12}}>
<div style={{...S.head,marginBottom:8}}>GETRÄNKE-STATISTIK</div>
{game.players.map(p => (
<div key={p.id} style={{display:'flex',justifyContent:'space-between',padding:'4px 0',
borderBottom:'1px solid rgba(255,255,255,0.05)',fontFamily:'monospace',fontSize:12}}>
<span style={{color:'rgba(255,255,255,0.7)'}}>{p.username}</span>
<span style={{color:dl[p.id]>0?'#f59e0b':MY_COLOR}}>🍺 {dl[p.id]||0}</span>
</div>
))}
</div>
</div>
);
}
// ── Aktive Spieler berechnen (Frontend) ───────────────────────────────────────
function getActivePlayers(game) {
const { players, player_chips, stock, status, loser_h1, loser_h2 } = game;
if (status === 'endkampf') {
return players.filter(p => p.id === loser_h1 || p.id === loser_h2);
}
if (stock > 0) return players;
return players.filter(p => player_chips[p.id] > 0);
}
// ── Neues Spiel Modal ─────────────────────────────────────────────────────────
function NewGameModal({ onClose, onCreate, toast }) {
const [users, setUsers] = useState([]);
const [picked, setPicked] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => { api('/tools/schocken/users').then(setUsers).catch(()=>{}); }, []);
const toggle = id => setPicked(p => p.includes(id) ? p.filter(x=>x!==id) : [...p,id]);
const create = async () => {
if (!picked.length) return;
setLoading(true);
try {
const r = await api('/tools/schocken', { body: { player_ids: picked } });
toast('Spiel erstellt! 🎲');
onCreate(r.id);
} catch(e) { toast(e.message||'Fehler','error'); }
finally { setLoading(false); }
};
return (
<div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.85)',display:'flex',alignItems:'center',justifyContent:'center',zIndex:1000,padding:20}}>
<div style={{...S.card,maxWidth:380,width:'100%'}}>
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:18}}>
<span style={{color:'#fff',fontFamily:'Space Mono,monospace',fontSize:13,fontWeight:700,letterSpacing:1}}>NEUES SPIEL</span>
<button onClick={onClose} style={S.btn('#666666',true)}></button>
</div>
<div style={{...S.head,marginBottom:8}}>MITSPIELER WÄHLEN</div>
<div style={{maxHeight:240,overflowY:'auto',marginBottom:16}}>
{users.map(u => (
<div key={u.id} onClick={()=>toggle(u.id)} style={{
display:'flex',alignItems:'center',gap:10,padding:'8px 10px',
cursor:'pointer',borderRadius:8,marginBottom:4,
background: picked.includes(u.id) ? 'rgba(78,205,196,0.1)' : 'rgba(255,255,255,0.03)',
border: picked.includes(u.id) ? '1px solid rgba(78,205,196,0.3)' : '1px solid rgba(255,255,255,0.06)',
}}>
<span style={{color:picked.includes(u.id)?MY_COLOR:'rgba(255,255,255,0.5)',fontSize:14}}>
{picked.includes(u.id)?'☑':'☐'}
</span>
<span style={{color:'#fff',fontFamily:'monospace',fontSize:13}}>{u.username}</span>
</div>
))}
</div>
<div style={{display:'flex',gap:8}}>
<button onClick={onClose} style={{...S.btn('#666666'),flex:1}}>Abbrechen</button>
<button onClick={create} disabled={!picked.length||loading} style={{...S.btn('#4ecdc4'),flex:1}}>
{loading ? 'Erstelle…' : `Starten (${picked.length+1} Spieler)`}
</button>
</div>
</div>
</div>
);
}
// ── Spielliste ────────────────────────────────────────────────────────────────
function GameList({ games, myId, onSelect, onNew }) {
const active = games.filter(g => g.status !== 'finished');
const finished = games.filter(g => g.status === 'finished');
const statusLabel = s => ({
lobby:'Lobby', half1:'1. Hälfte', half2:'2. Hälfte', endkampf:'Endkampf', finished:'Beendet'
}[s]||s);
const GameRow = ({ g }) => {
const others = g.players.filter(p=>p.id!==myId).map(p=>p.username).join(', ');
return (
<button onClick={()=>onSelect(g.id)} style={{
...S.card,width:'100%',textAlign:'left',cursor:'pointer',
padding:'12px 14px',marginBottom:8,display:'flex',alignItems:'center',gap:10,
border:'1px solid rgba(255,255,255,0.07)',
}}>
<div style={{flex:1}}>
<div style={{color:'#fff',fontFamily:'monospace',fontSize:13,marginBottom:3}}>
🎲 mit <strong>{others}</strong>
</div>
<div style={{color:'rgba(255,255,255,0.35)',fontSize:11,fontFamily:'monospace'}}>
{g.players.length} Spieler
</div>
</div>
<span style={{color:g.status==='finished'?'rgba(255,255,255,0.3)':MY_COLOR,fontSize:11,fontFamily:'monospace',flexShrink:0}}>
{statusLabel(g.status)}
</span>
</button>
);
};
return (
<div>
<button onClick={onNew} style={{...S.btn('#4ecdc4'),width:'100%',marginBottom:20,padding:'10px 14px',fontSize:13}}>
+ Neues Spiel starten
</button>
{active.length > 0 && <>
<div style={{...S.head,marginBottom:8}}>LAUFEND ({active.length})</div>
{active.map(g=><GameRow key={g.id} g={g}/>)}
</>}
{finished.length > 0 && <>
<div style={{...S.head,marginTop:16,marginBottom:8}}>BEENDET</div>
{finished.map(g=><GameRow key={g.id} g={g}/>)}
</>}
{games.length===0 && (
<div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:13,textAlign:'center',paddingTop:40}}>
Noch keine Spiele starte eines!
</div>
)}
</div>
);
}
// ── Spielansicht ──────────────────────────────────────────────────────────────
function GameView({ game, myId, onRefresh, toast }) {
const players = game.players;
const chips = game.player_chips;
const dl = game.drink_losses;
const round = game.current_round;
const activePlayers = getActivePlayers(game);
const currentPlayer = activePlayers[game.current_player_idx % activePlayers.length];
const handleRoll = async (keepDice, flipSixes, goDark) => {
await api(`/tools/schocken/${game.id}/roll`, {
body: { keep_dice: keepDice, flip_sixes: flipSixes, go_dark: goDark }
});
onRefresh();
};
const handleReady = async () => {
await api(`/tools/schocken/${game.id}/ready`, { body: {} });
onRefresh();
};
const handleEvaluate = async () => {
const result = await api(`/tools/schocken/${game.id}/evaluate`, { body: {} });
if (result.round_result?.eventMsg) toast(result.round_result.eventMsg);
onRefresh();
};
const handleStart = async () => {
await api(`/tools/schocken/${game.id}/start`, { body: {} });
onRefresh();
};
const statusLabel = { lobby:'Lobby', half1:'1. Hälfte', half2:'2. Hälfte', endkampf:'Endkampf', finished:'Beendet' };
return (
<div>
{/* Phase-Header */}
<div style={{...S.card,padding:'8px 14px',marginBottom:12,display:'flex',alignItems:'center',gap:10}}>
<span style={{color:MY_COLOR,fontFamily:'monospace',fontSize:11,letterSpacing:1}}>{statusLabel[game.status]}</span>
<span style={{flex:1}}/>
<span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>Runde {game.round_number}</span>
</div>
{/* Schockstock */}
<Schockstock stock={game.stock} players={players} chips={chips}/>
{/* Spieler */}
<div style={{marginBottom:12}}>
{players.map(p => (
<PlayerRow
key={p.id}
player={p}
chips={chips[p.id]||0}
has16th={game.has_16th}
isMe={p.id===myId}
isCurrent={currentPlayer?.id===p.id}
result={round?.playerStates?.[p.id]?.result}
drinkLosses={dl[p.id]||0}
game={game}
/>
))}
</div>
{/* Lobby */}
{game.status==='lobby' && (
<div style={{textAlign:'center'}}>
{players[0]?.id===myId ? (
<button onClick={handleStart} style={{...S.btn('#4ecdc4'),width:'100%',fontSize:14,padding:'12px'}}>
🎲 Spiel starten
</button>
) : (
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:12,padding:'20px 0'}}>
Warte auf {players[0]?.username} um das Spiel zu starten
</div>
)}
</div>
)}
{/* Spielfeld */}
{game.status!=='lobby' && game.status!=='finished' && (
<div style={{...S.card,padding:'14px',border:'1px solid rgba(78,205,196,0.15)'}}>
<DiceArea
game={game}
myId={myId}
onRoll={handleRoll}
onReady={handleReady}
onEvaluate={handleEvaluate}
/>
</div>
)}
{/* Spielende */}
{game.status==='finished' && <GameOver game={game}/>}
</div>
);
}
// ── Hauptkomponente ───────────────────────────────────────────────────────────
export default function Schocken({ toast }) {
const [games, setGames] = useState([]);
const [activeId, setActiveId] = useState(null);
const [game, setGame] = useState(null);
const [showNew, setShowNew] = useState(false);
const [loading, setLoading] = useState(true);
const myId = getMyId();
const loadGames = useCallback(async () => {
try { setGames(await api('/tools/schocken')); }
catch { toast?.('Fehler beim Laden','error'); }
finally { setLoading(false); }
}, []);
const loadGame = useCallback(async () => {
if (!activeId) { setGame(null); return; }
try { setGame(await api(`/tools/schocken/${activeId}`)); }
catch {}
}, [activeId]);
useEffect(() => { loadGames(); }, [loadGames]);
useEffect(() => { loadGame(); }, [loadGame]);
// Polling wenn Spiel aktiv
useEffect(() => {
if (!activeId) return;
const iv = setInterval(loadGame, 5000);
return () => clearInterval(iv);
}, [activeId, loadGame]);
if (loading) return <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',padding:40,textAlign:'center'}}>Lade</div>;
return (
<div style={{maxWidth:560,margin:'0 auto'}}>
<div style={{display:'flex',alignItems:'center',gap:10,marginBottom:24,flexWrap:'wrap'}}>
{activeId && (
<button onClick={()=>{setActiveId(null);setGame(null);loadGames();}} style={S.btn('#666666',true)}> Zurück</button>
)}
<h2 style={{margin:0,fontSize:15,fontFamily:'monospace',color:'rgba(255,255,255,0.55)',letterSpacing:2,fontWeight:400}}>
🎲 SCHOCKEN
</h2>
</div>
{!activeId
? <GameList games={games} myId={myId} onSelect={setActiveId} onNew={()=>setShowNew(true)}/>
: game && myId
? <GameView game={game} myId={myId} onRefresh={loadGame} toast={toast}/>
: <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',textAlign:'center',paddingTop:40}}>Lade</div>
}
{showNew && <NewGameModal onClose={()=>setShowNew(false)} toast={toast} onCreate={id=>{setShowNew(false);setActiveId(id);loadGames();}}/>}
</div>
);
}