diff --git a/src/games/hordeseer/assets/leaderboard-modal.module.css b/src/games/hordeseer/assets/leaderboard-modal.module.css index a35c831..b5100e7 100644 --- a/src/games/hordeseer/assets/leaderboard-modal.module.css +++ b/src/games/hordeseer/assets/leaderboard-modal.module.css @@ -1,5 +1,4 @@ .modal { - max-width: 480px; height: fit-content; } @@ -61,17 +60,16 @@ color: var(--accent-alt) !important; } -.nextPlace { +.timeCol { + color: var(--yellow) !important; + white-space: nowrap; +} + +.footer { font-size: 13px; color: var(--text-muted); strong { color: var(--yellow); } -} - -.nextPlaceMeta { - font-size: 12px; - color: var(--text-muted); - opacity: 0.7; } \ No newline at end of file diff --git a/src/games/hordeseer/assets/stats-panel.module.css b/src/games/hordeseer/assets/stats-panel.module.css index 1a2bae1..b255e6e 100644 --- a/src/games/hordeseer/assets/stats-panel.module.css +++ b/src/games/hordeseer/assets/stats-panel.module.css @@ -1,6 +1,7 @@ .panel { - width: 420px; + width: 520px; min-width: 320px; + max-width: 50dvw; flex-shrink: 0; display: flex; flex-direction: column; @@ -15,6 +16,7 @@ @media (max-width: 768px) { .panel { width: 100%; + max-width: unset; border-right: none; border-bottom: 1px solid var(--border); } diff --git a/src/games/hordeseer/assets/workers-panel.module.css b/src/games/hordeseer/assets/workers-panel.module.css index 78e78d6..7e096ee 100644 --- a/src/games/hordeseer/assets/workers-panel.module.css +++ b/src/games/hordeseer/assets/workers-panel.module.css @@ -5,6 +5,15 @@ overflow: hidden; } +@media (max-width: 768px) { + .panel { + width: 100%; + max-width: unset; + height: fit-content; + overflow: unset; + } +} + .header { display: flex; flex-direction: row; diff --git a/src/games/hordeseer/components/modals/leaderboard-modal.tsx b/src/games/hordeseer/components/modals/leaderboard-modal.tsx index 3cf3c61..06f3a53 100644 --- a/src/games/hordeseer/components/modals/leaderboard-modal.tsx +++ b/src/games/hordeseer/components/modals/leaderboard-modal.tsx @@ -3,6 +3,7 @@ import { useBool } from "@common/hooks/useBool"; import { useHordeState } from "../../contexts/state"; import { fetchLeaderboard, type LeaderboardEntry } from "../../utils/api"; import { formatNumber, formatTime } from "@common/utils"; +import { calculateTotalKudosPerHour } from "../../utils/calculations"; import { Modal } from "@common/components/Modal"; import styles from "../../assets/leaderboard-modal.module.css"; @@ -19,9 +20,7 @@ export const LeaderboardModal = ({ open, onClose }: Props) => { const ownWorkerIds = new Set(user?.worker_ids ?? []); const ownWorkers = workers.filter(w => ownWorkerIds.has(w.id)); - const totalUptime = ownWorkers.reduce((s, w) => s + w.uptime, 0); - const totalGenerated = ownWorkers.reduce((s, w) => s + (w.kudos_details?.generated ?? 0), 0); - const kudosPerHour = totalUptime > 0 ? (totalGenerated / totalUptime) * 3600 : 0; + const kudosPerHour = calculateTotalKudosPerHour(ownWorkers); const [rows, setRows] = useState<(LeaderboardEntry & { rank: number; diff?: number })[]>([]); const loading = useBool(false); @@ -93,17 +92,8 @@ export const LeaderboardModal = ({ open, onClose }: Props) => { return () => { cancelled = true; }; }, [open, user?.username]); - const nextRow = rows.length >= 2 ? rows[rows.length - 2] : null; - const footer = user && nextRow?.diff && nextRow.diff > 0 && kudosPerHour > 0 - ? (() => { - const secs = Math.ceil((nextRow.diff / kudosPerHour) * 3600); - return ( - - Time to #{nextRow.rank}: {formatTime(secs)} - ({formatNumber(Math.round(kudosPerHour))} kudos/h) - - ); - })() + const footer = user && kudosPerHour > 0 + ? Kudos gathering speed: {formatNumber(Math.round(kudosPerHour))} kudos/h : undefined; return ( @@ -118,21 +108,36 @@ export const LeaderboardModal = ({ open, onClose }: Props) => { Username Kudos {user && Diff} + {user && kudosPerHour > 0 && Time} - {rows.map(r => ( - - {r.rank} - {r.username} - {formatNumber(r.kudos)} - {user && ( - 0 ? styles.above : styles.below) : undefined}> - {r.diff !== undefined ? `+${formatNumber(r.diff)}` : '—'} - - )} - - ))} + {rows.map(r => { + const remainingSecs = r.diff !== undefined && r.diff > 0 && kudosPerHour > 0 + ? Math.ceil((r.diff / kudosPerHour) * 3600) + : undefined; + return ( + + {r.rank} + {r.username} + {formatNumber(r.kudos)} + {user && ( + 0 ? styles.above : styles.below) : undefined}> + {r.diff !== undefined ? `+${formatNumber(r.diff)}` : '—'} + + )} + {user && kudosPerHour > 0 && ( + + {r.username === user?.username + ? '—' + : remainingSecs !== undefined + ? formatTime(remainingSecs) + : '—'} + + )} + + ); + })} )} diff --git a/src/games/hordeseer/components/stats-panel.tsx b/src/games/hordeseer/components/stats-panel.tsx index a2c2697..817ba59 100644 --- a/src/games/hordeseer/components/stats-panel.tsx +++ b/src/games/hordeseer/components/stats-panel.tsx @@ -1,6 +1,7 @@ import { useState } from "preact/hooks"; import { useHordeState } from "../contexts/state"; import { formatNumber, formatTime } from "@common/utils"; +import { calculateTotalKudosPerHour } from "../utils/calculations"; import styles from "../assets/stats-panel.module.css"; type ModelSortKey = 'name' | 'count' | 'queued' | 'jobs' | 'performance'; @@ -16,10 +17,9 @@ export const StatsPanel = () => { const ownWorkerIds = new Set(user?.worker_ids ?? []); const ownWorkers = workers.filter(w => ownWorkerIds.has(w.id)); - // Kudos/hour: sum generated kudos across own workers / total uptime hours + // Kudos/hour: sum of per-worker kudos/hour rates + const kudosPerHour = calculateTotalKudosPerHour(ownWorkers); const totalUptime = ownWorkers.reduce((s, w) => s + w.uptime, 0); - const totalGenerated = ownWorkers.reduce((s, w) => s + (w.kudos_details?.generated ?? 0), 0); - const kudosPerHour = totalUptime > 0 ? (totalGenerated / totalUptime) * 3600 : 0; // Own model names set const ownModelNames = new Set(ownWorkers.flatMap(w => w.models)); @@ -27,11 +27,12 @@ export const StatsPanel = () => { // Sort models const sortedModels = [...models].sort((a, b) => { let cmp = 0; - if (sortKey === 'name') cmp = a.name.localeCompare(b.name); - else if (sortKey === 'count') cmp = a.count - b.count; + if (sortKey === 'name') cmp = a.name.localeCompare(b.name); + else if (sortKey === 'count') cmp = a.count - b.count; else if (sortKey === 'queued') cmp = a.queued - b.queued; - else if (sortKey === 'jobs') cmp = a.jobs - b.jobs; - else cmp = a.performance - b.performance; + else if (sortKey === 'jobs') cmp = a.jobs - b.jobs; + else cmp = a.performance - b.performance; + return sortDir === 'asc' ? cmp : -cmp; }); @@ -45,14 +46,13 @@ export const StatsPanel = () => { return (