feat: add statistics panel with all-time, current run, and progress stats

This commit is contained in:
2026-03-06 22:02:57 -08:00
committed by Naomi Carrigan
parent 5b4661b398
commit a7d4b72805
4 changed files with 176 additions and 3 deletions
+4 -1
View File
@@ -12,9 +12,10 @@ import { EquipmentPanel } from "./EquipmentPanel.js";
import { OfflineModal } from "./OfflineModal.js";
import { PrestigePanel } from "./PrestigePanel.js";
import { QuestPanel } from "./QuestPanel.js";
import { StatisticsPanel } from "./StatisticsPanel.js";
import { UpgradePanel } from "./UpgradePanel.js";
type Tab = "adventurers" | "upgrades" | "quests" | "bosses" | "equipment" | "achievements" | "prestige";
type Tab = "adventurers" | "upgrades" | "quests" | "bosses" | "equipment" | "achievements" | "prestige" | "statistics";
const TABS: { id: Tab; label: string }[] = [
{ id: "adventurers", label: "⚔️ Adventurers" },
@@ -24,6 +25,7 @@ const TABS: { id: Tab; label: string }[] = [
{ id: "equipment", label: "🗡️ Equipment" },
{ id: "achievements", label: "🏆 Achievements" },
{ id: "prestige", label: "⭐ Prestige" },
{ id: "statistics", label: "📊 Statistics" },
];
export const GameLayout = (): React.JSX.Element => {
@@ -98,6 +100,7 @@ export const GameLayout = (): React.JSX.Element => {
{activeTab === "equipment" && <EquipmentPanel />}
{activeTab === "achievements" && <AchievementPanel />}
{activeTab === "prestige" && <PrestigePanel />}
{activeTab === "statistics" && <StatisticsPanel />}
</div>
</main>
</div>
@@ -0,0 +1,153 @@
import { useGame } from "../../context/GameContext.js";
import { PRESTIGE_UPGRADES } from "../../data/prestigeUpgrades.js";
const formatDate = (timestamp: number): string =>
new Date(timestamp).toLocaleDateString("en-GB", {
day: "numeric",
month: "short",
year: "numeric",
});
interface StatCardProps {
icon: string;
label: string;
value: string;
sub?: string;
}
const StatCard = ({ icon, label, value, sub }: StatCardProps): React.JSX.Element => (
<div className="profile-stat">
<span className="profile-stat-icon">{icon}</span>
<span className="profile-stat-value">{value}</span>
<span className="profile-stat-label">{label}</span>
{sub !== undefined && <span className="profile-stat-date">{sub}</span>}
</div>
);
export const StatisticsPanel = (): React.JSX.Element => {
const { state, formatNumber } = useGame();
if (!state) return <section className="panel"><p>Loading...</p></section>;
const { player, resources, prestige, bosses, quests, zones, adventurers, upgrades, equipment, achievements } = state;
const bossesDefeated = bosses.filter((b) => b.status === "defeated").length;
const questsCompleted = quests.filter((q) => q.status === "completed").length;
const zonesUnlocked = zones.filter((z) => z.status === "unlocked").length;
const adventurersRecruited = adventurers.reduce((sum, a) => sum + a.count, 0);
const equipmentOwned = (equipment ?? []).filter((e) => e.owned).length;
const upgradesPurchased = upgrades.filter((u) => u.purchased).length;
const achievementsUnlocked = (achievements ?? []).filter((a) => a.unlockedAt !== null).length;
const prestigeUpgradesPurchased = prestige.purchasedUpgradeIds.length;
return (
<section className="panel statistics-panel">
<h2>📊 Statistics</h2>
<h3 className="stats-section-header">All-Time</h3>
<div className="profile-stats">
<StatCard
icon="🪙"
label="Total Gold Earned"
value={formatNumber(player.totalGoldEarned)}
sub="across all runs"
/>
<StatCard
icon="👆"
label="Total Clicks"
value={formatNumber(player.totalClicks)}
/>
<StatCard
icon="⭐"
label="Prestiges"
value={String(prestige.count)}
/>
<StatCard
icon="📅"
label="Guild Founded"
value={formatDate(player.createdAt)}
/>
<StatCard
icon="☁️"
label="Last Cloud Save"
value={formatDate(player.lastSavedAt)}
/>
<StatCard
icon="✖️"
label="Production Multiplier"
value={`×${prestige.productionMultiplier.toFixed(2)}`}
sub="from prestige"
/>
</div>
<h3 className="stats-section-header">Current Run</h3>
<div className="profile-stats">
<StatCard
icon="🪙"
label="Gold"
value={formatNumber(resources.gold)}
/>
<StatCard
icon="✨"
label="Essence"
value={formatNumber(resources.essence)}
/>
<StatCard
icon="💎"
label="Crystals"
value={formatNumber(resources.crystals)}
/>
<StatCard
icon="🔮"
label="Runestones"
value={formatNumber(prestige.runestones)}
sub="permanent currency"
/>
</div>
<h3 className="stats-section-header">Progress</h3>
<div className="profile-stats">
<StatCard
icon="👹"
label="Bosses Defeated"
value={`${String(bossesDefeated)} / ${String(bosses.length)}`}
/>
<StatCard
icon="📜"
label="Quests Completed"
value={`${String(questsCompleted)} / ${String(quests.length)}`}
/>
<StatCard
icon="🗺️"
label="Zones Unlocked"
value={`${String(zonesUnlocked)} / ${String(zones.length)}`}
/>
<StatCard
icon="⚔️"
label="Adventurers Recruited"
value={formatNumber(adventurersRecruited)}
/>
<StatCard
icon="🗡️"
label="Equipment Owned"
value={`${String(equipmentOwned)} / ${String((equipment ?? []).length)}`}
/>
<StatCard
icon="🔧"
label="Upgrades Purchased"
value={`${String(upgradesPurchased)} / ${String(upgrades.length)}`}
/>
<StatCard
icon="🏆"
label="Achievements"
value={`${String(achievementsUnlocked)} / ${String((achievements ?? []).length)}`}
/>
<StatCard
icon="🔮"
label="Prestige Upgrades"
value={`${String(prestigeUpgradesPurchased)} / ${String(PRESTIGE_UPGRADES.length)}`}
/>
</div>
</section>
);
};
+17
View File
@@ -1084,6 +1084,23 @@ body {
background: var(--colour-accent-light);
}
/* ===================== STATISTICS ===================== */
.stats-section-header {
border-bottom: 1px solid var(--colour-border);
color: var(--colour-text-muted);
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.08em;
margin-bottom: 0.75rem;
margin-top: 1.5rem;
padding-bottom: 0.4rem;
text-transform: uppercase;
}
.statistics-panel .stats-section-header:first-of-type {
margin-top: 0.5rem;
}
/* ===================== ACHIEVEMENTS ===================== */
.achievement-progress {
color: var(--colour-text-muted);