// Leaderboard, Admin, Datamodel screens function LeaderboardScreen() { const [expanded, setExpanded] = React.useState(null); const SEED = window.SEED; const { totals } = computeScores(); const finishedRaces = SEED.calendar.filter(r => getState().raceState[r.id] === "finished"); const ranked = SEED.players.map(p => ({ p, score: totals[p.id].total, })).sort((a, b) => b.score - a.score); ranked.forEach((row, i) => { row.rank = i === 0 || row.score < ranked[i - 1].score ? i + 1 : ranked[i - 1].rank; }); const podiumColors = { 1: "#FFD700", 2: "#C0C0C0", 3: "#CD7F32" }; return (
{/* Podium */} {ranked.length >= 3 && (
Podium
{ranked[0]?.rank === ranked[1]?.rank && ( GLEICHSTAND )}
{[ranked[1], ranked[0], ranked[2]].map((row, idx) => { const place = row.rank; const isFirst = place === 1; return (
{row.p.avatar || row.p.name?.[0]}
{row.p.name}
P{place}
{row.score} PKT
); })}
)} {/* Standings-Tabelle */}
{ranked.map((row, i) => { const isMe = row.p.id === useStore.getState?.()?.user?.id || row.p.you; const isExpanded = expanded === row.p.id; return (
setExpanded(isExpanded ? null : row.p.id)}> {/* Rang */}
{row.rank}
{/* Name + Avatar */}
{row.p.avatar || row.p.name?.[0]}
{row.p.name} {isMe && DU}
{finishedRaces.length} Rennen gewertet
{/* Punkte */}
{row.score}
PKT
{row.rank !== 1 && (
{ranked[0].score - row.score} PKT auf P1
)}
{/* Expand-Button */}
{isExpanded ? "▲" : "▼"}
{/* Aufschlüsselung */} {isExpanded && (
Punkte pro Rennen
{finishedRaces.length === 0 ? (
Noch keine abgeschlossenen Rennen.
) : (
{finishedRaces.map(r => { const pts = totals[row.p.id]?.byRace[r.id] || 0; const bk = totals[row.p.id]?.byRaceBreakdown?.[r.id] || {}; const hasPick = !!(getState().racePicks[r.id]?.[row.p.id]); const scored = hasPick ? [ bk.pole ? "POLE" : null, bk.raceWinner ? "WIN" : null, bk.sprintPole ? "SPR-P" : null, bk.sprintWinner ? "SPRINT" : null, bk.finishers ? "FIN" : null, bk.finishersClose ? "FIN\xb11" : null, ].filter(Boolean) : []; return (
0 ? "var(--good)" : hasPick ? "rgba(255,255,255,0.07)" : "rgba(255,180,0,0.25)"}`, padding: "8px 12px", minWidth: 72, textAlign: "center", opacity: hasPick ? 1 : 0.55, }}>
{r.name.replace(" Grand Prix","").replace(" GP","")}
0 ? "var(--good)" : "var(--ink-3)", marginTop: 4, }}>{hasPick ? "+" + pts : "—"}
{!hasPick && (
KEIN TIPP
)} {scored.length > 0 && (
{scored.map(label => ( {label} ))}
)}
); })}
)} {totals[row.p.id]?.season > 0 && (
Saison-Bonus
{Object.entries(totals[row.p.id].seasonBreakdown || {}).map(([key, pts]) => { const label = ({ driverChampion: "WDC", constructor: "WCC", destructor: "DESTRUCTOR", driverKicked: "FAHRER RAUS", teamBossKicked: "BOSS RAUS", surprise: "SURPRISE" })[key] || key; return (
{label}
+{pts}
); })}
)}
)}
); })}
); } // -- Announcement admin section ------------------------------------------ function AnnouncementAdminSection({ scoring }) { const SEED = window.SEED; const current = scoring?.announcement || null; const [draft, setDraft] = React.useState(current?.text || ""); const save = () => { if (!draft.trim()) return; setState(s => ({ ...s, scoring: { ...(s.scoring || SEED.scoring), announcement: { id: Date.now().toString(), text: draft.trim() }, }, })); showToast("✓ Nachricht veröffentlicht"); }; const clear = () => { setDraft(""); setState(s => ({ ...s, scoring: { ...(s.scoring || SEED.scoring), announcement: null }, })); showToast("Nachricht entfernt"); }; return (
INFO-BANNER · WIRD ALLEN SPIELERN ANGEZEIGT
Ankündigung
{current && (
● AKTIV: „{current.text}"
)}