feat: initial elysium idle game prototype

Sets up the full monorepo with pnpm workspaces. Includes shared types
package, Hono API with Discord OAuth/JWT auth, Prisma v6 + MongoDB
Atlas, and React + Vite frontend with game loop, five tabs, and
Discord-linked save/load.
This commit is contained in:
2026-03-06 11:26:19 -08:00
committed by Naomi Carrigan
parent c69e155de3
commit a3daed1683
64 changed files with 9011 additions and 0 deletions
@@ -0,0 +1,82 @@
import { useState } from "react";
import { useGame } from "../../context/GameContext.js";
import { ResourceBar } from "../ui/ResourceBar.js";
import { AdventurerPanel } from "./AdventurerPanel.js";
import { BossPanel } from "./BossPanel.js";
import { ClickArea } from "./ClickArea.js";
import { OfflineModal } from "./OfflineModal.js";
import { PrestigePanel } from "./PrestigePanel.js";
import { QuestPanel } from "./QuestPanel.js";
import { UpgradePanel } from "./UpgradePanel.js";
type Tab = "adventurers" | "upgrades" | "quests" | "bosses" | "prestige";
const TABS: { id: Tab; label: string }[] = [
{ id: "adventurers", label: "⚔️ Adventurers" },
{ id: "upgrades", label: "🔧 Upgrades" },
{ id: "quests", label: "📜 Quests" },
{ id: "bosses", label: "👹 Bosses" },
{ id: "prestige", label: "⭐ Prestige" },
];
export const GameLayout = (): React.JSX.Element => {
const { state, isLoading, error } = useGame();
const [activeTab, setActiveTab] = useState<Tab>("adventurers");
if (isLoading) {
return (
<div className="loading-screen">
<p>Loading your adventure...</p>
</div>
);
}
if (error) {
return (
<div className="error-screen">
<p>Error: {error}</p>
</div>
);
}
if (!state) return <div className="loading-screen"><p>Loading...</p></div>;
return (
<div className="game-layout">
<ResourceBar
resources={state.resources}
prestigeCount={state.prestige.count}
/>
<OfflineModal />
<div className="game-main">
<aside className="game-sidebar">
<ClickArea />
</aside>
<main className="game-content">
<nav className="tab-bar">
{TABS.map((tab) => (
<button
key={tab.id}
className={`tab-button ${activeTab === tab.id ? "active" : ""}`}
onClick={() => { setActiveTab(tab.id); }}
type="button"
>
{tab.label}
</button>
))}
</nav>
<div className="tab-content">
{activeTab === "adventurers" && <AdventurerPanel />}
{activeTab === "upgrades" && <UpgradePanel />}
{activeTab === "quests" && <QuestPanel />}
{activeTab === "bosses" && <BossPanel />}
{activeTab === "prestige" && <PrestigePanel />}
</div>
</main>
</div>
</div>
);
};