diff --git a/IDEAS.md b/IDEAS.md index 11e1065..2f89fbc 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -6,7 +6,7 @@ A running list of planned features and content additions. Strike through items a ## 🌟 New Systems -- [ ] **Offline earnings** — When returning to the game, earn a percentage of what you'd have earned offline (cap at ~8–12 hours). Upgradeable via the prestige shop to increase the % and the time cap. Essential for an idle game! +- [x] **Offline earnings** — When returning to the game, earn a percentage of what you'd have earned offline (cap at ~8–12 hours). Upgradeable via the prestige shop to increase the % and the time cap. Essential for an idle game! - [ ] **Second prestige layer (Transcendence)** — Unlocked after ~10 prestiges. Sacrifice all runestones for a new currency ("Echoes"?). Echoes are permanent account-wide currency that persist across prestiges. Has its own upgrade tree with truly game-changing bonuses. Gives endgame players a long-term goal. @@ -38,7 +38,7 @@ A running list of planned features and content additions. Strike through items a ## 💜 Priority Order (Suggested) -1. Offline earnings (core idle game feature) +1. ~~Offline earnings~~ ✅ 2. Statistics panel (low effort, high satisfaction) 3. Daily challenges (retention driver) 4. Boss first-kill bounties (easy content win) diff --git a/apps/web/src/components/game/GameLayout.tsx b/apps/web/src/components/game/GameLayout.tsx index 1110ebc..3d8f973 100644 --- a/apps/web/src/components/game/GameLayout.tsx +++ b/apps/web/src/components/game/GameLayout.tsx @@ -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" && } {activeTab === "achievements" && } {activeTab === "prestige" && } + {activeTab === "statistics" && } diff --git a/apps/web/src/components/game/StatisticsPanel.tsx b/apps/web/src/components/game/StatisticsPanel.tsx new file mode 100644 index 0000000..ab5711a --- /dev/null +++ b/apps/web/src/components/game/StatisticsPanel.tsx @@ -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 => ( +
+ {icon} + {value} + {label} + {sub !== undefined && {sub}} +
+); + +export const StatisticsPanel = (): React.JSX.Element => { + const { state, formatNumber } = useGame(); + + if (!state) return

Loading...

; + + 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 ( +
+

📊 Statistics

+ +

All-Time

+
+ + + + + + +
+ +

Current Run

+
+ + + + +
+ +

Progress

+
+ + + + + + + + +
+
+ ); +}; diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 8b19a66..b151df0 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -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);