feat: vampire UI infrastructure - mode bar, tab row, and blood-red theme

- Added vampire API client functions (boss challenge, siring, awakening, upgrades, craft, explore)
- Fixed goddess API client URL bugs (/goddess/* → /goddess-boss/challenge, /goddess-upgrade/buy, /goddess-craft, /goddess-explore/*)
- Added VampireTab type and vampireTabs array (11 tabs) to GameLayout
- Fixed mode unlock chain: vampire after apotheosis, goddess after eternalSovereignty
- Added body.vampire-mode CSS class toggle alongside existing goddess-mode toggle
- Added vampire tab bar nav with blood-red theme
- Replaced single vampire placeholder with per-tab placeholders
- Added body.vampire-mode CSS variables (blood-red palette) and .vampire-tab-bar styles
- Added Blood/Ichor/Soul Shards resource display to ResourceBar (gated on apotheosis)
This commit is contained in:
2026-04-16 10:03:14 -07:00
committed by Naomi Carrigan
parent 8fa5d12f05
commit d9d1228172
4 changed files with 392 additions and 39 deletions
+143 -24
View File
@@ -7,6 +7,7 @@
/* eslint-disable max-lines -- Complex layout with many conditional renders */
/* eslint-disable max-lines-per-function -- Complex layout with many conditional renders */
/* eslint-disable complexity -- Many tab render paths */
/* eslint-disable max-statements -- Many state variables for multi-mode tab routing */
import { type JSX, useEffect, useState } from "react";
import { useGame } from "../../context/gameContext.js";
import { ResourceBar } from "../ui/resourceBar.js";
@@ -89,6 +90,19 @@ type GoddessTab =
| "goddess-exploration"
| "goddess-achievements";
type VampireTab =
| "vampire-zones"
| "vampire-bosses"
| "vampire-quests"
| "thralls"
| "vampire-equipment"
| "vampire-upgrades"
| "siring"
| "vampire-awakening"
| "vampire-crafting"
| "vampire-exploration"
| "vampire-achievements";
const baseTabs: Array<{ id: Tab; label: string }> = [
{ id: "adventurers", label: "⚔️ Adventurers" },
{ id: "upgrades", label: "🔧 Upgrades" },
@@ -111,6 +125,20 @@ const baseTabs: Array<{ id: Tab; label: string }> = [
{ id: "debug", label: "🔧 Debug" },
];
const vampireTabs: Array<{ id: VampireTab; label: string }> = [
{ id: "vampire-zones", label: "🗺️ Zones" },
{ id: "vampire-bosses", label: "🩸 Bosses" },
{ id: "vampire-quests", label: "📜 Quests" },
{ id: "thralls", label: "🧟 Thralls" },
{ id: "vampire-equipment", label: "🦇 Equipment" },
{ id: "vampire-upgrades", label: "⚔️ Upgrades" },
{ id: "siring", label: "🩸 Siring" },
{ id: "vampire-awakening", label: "💀 Awakening" },
{ id: "vampire-crafting", label: "⚗️ Crafting" },
{ id: "vampire-exploration", label: "🌑 Exploration" },
{ id: "vampire-achievements", label: "🏆 Achievements" },
];
const goddessTabs: Array<{ id: GoddessTab; label: string }> = [
{ id: "goddess-zones", label: "🌟 Zones" },
{ id: "goddess-bosses", label: "👁️ Bosses" },
@@ -169,12 +197,15 @@ const GameLayout = (): JSX.Element => {
const [ activeTab, setActiveTab ] = useState<Tab>("adventurers");
const [ activeGoddessTab, setActiveGoddessTab ]
= useState<GoddessTab>("goddess-zones");
const [ activeVampireTab, setActiveVampireTab ]
= useState<VampireTab>("vampire-zones");
const [ editingProfile, setEditingProfile ] = useState(false);
const [ dismissedOutdatedWarning, setDismissedOutdatedWarning ]
= useState(false);
useEffect(() => {
document.body.classList.toggle("goddess-mode", activeMode === "goddess");
document.body.classList.toggle("vampire-mode", activeMode === "vampire");
}, [ activeMode ]);
if (isLoading) {
@@ -273,8 +304,13 @@ const GameLayout = (): JSX.Element => {
<nav className="mode-bar">
{modes.map((mode) => {
const apotheosisCount = state.apotheosis?.count ?? 0;
const goddessLocked = mode === "goddess" && apotheosisCount === 0;
const isLocked = goddessLocked || mode === "vampire";
const eternalSovereigntyCount
= state.vampire?.eternalSovereignty.count ?? 0;
const vampireLocked
= mode === "vampire" && apotheosisCount === 0;
const goddessLocked
= mode === "goddess" && eternalSovereigntyCount === 0;
const isLocked = vampireLocked || goddessLocked;
function handleModeClick(): void {
if (!isLocked) {
handleSetMode(mode);
@@ -304,6 +340,7 @@ const GameLayout = (): JSX.Element => {
})}
</nav>
{/* eslint-disable-next-line no-nested-ternary -- Three-way mode switch for tab bar */}
{activeMode === "mortal"
? <nav className="tab-bar">
{baseTabs.map((tab) => {
@@ -331,26 +368,47 @@ const GameLayout = (): JSX.Element => {
);
})}
</nav>
: <nav className="tab-bar goddess-tab-bar">
{goddessTabs.map((tab) => {
const { id: tabId, label } = tab;
function handleGoddessTabClick(): void {
setActiveGoddessTab(tabId);
}
return (
<button
className={`tab-button${activeGoddessTab === tabId
? " active"
: ""}`}
key={tabId}
onClick={handleGoddessTabClick}
type="button"
>
{label}
</button>
);
})}
</nav>
: activeMode === "goddess"
? <nav className="tab-bar goddess-tab-bar">
{goddessTabs.map((tab) => {
const { id: tabId, label } = tab;
function handleGoddessTabClick(): void {
setActiveGoddessTab(tabId);
}
return (
<button
className={`tab-button${activeGoddessTab === tabId
? " active"
: ""}`}
key={tabId}
onClick={handleGoddessTabClick}
type="button"
>
{label}
</button>
);
})}
</nav>
: <nav className="tab-bar vampire-tab-bar">
{vampireTabs.map((tab) => {
const { id: tabId, label } = tab;
function handleVampireTabClick(): void {
setActiveVampireTab(tabId);
}
return (
<button
className={`tab-button${activeVampireTab === tabId
? " active"
: ""}`}
key={tabId}
onClick={handleVampireTabClick}
type="button"
>
{label}
</button>
);
})}
</nav>
}
<div className="tab-content">
@@ -420,8 +478,69 @@ const GameLayout = (): JSX.Element => {
&& activeGoddessTab === "goddess-achievements"
&& <GoddessAchievementsPanel />}
{activeMode === "vampire"
&& <div className="goddess-placeholder">
<p>{"🧛 Vampire panels coming soon..."}</p>
&& activeVampireTab === "vampire-zones"
&& <div className="vampire-placeholder">
<p>{"🗺️ Vampire Zones coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "vampire-bosses"
&& <div className="vampire-placeholder">
<p>{"🩸 Vampire Bosses coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "vampire-quests"
&& <div className="vampire-placeholder">
<p>{"📜 Vampire Quests coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "thralls"
&& <div className="vampire-placeholder">
<p>{"🧟 Thralls coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "vampire-equipment"
&& <div className="vampire-placeholder">
<p>{"🦇 Vampire Equipment coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "vampire-upgrades"
&& <div className="vampire-placeholder">
<p>{"⚔️ Vampire Upgrades coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "siring"
&& <div className="vampire-placeholder">
<p>{"🩸 Siring coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "vampire-awakening"
&& <div className="vampire-placeholder">
<p>{"💀 Vampire Awakening coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "vampire-crafting"
&& <div className="vampire-placeholder">
<p>{"⚗️ Vampire Crafting coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "vampire-exploration"
&& <div className="vampire-placeholder">
<p>{"🌑 Vampire Exploration coming soon..."}</p>
</div>
}
{activeMode === "vampire"
&& activeVampireTab === "vampire-achievements"
&& <div className="vampire-placeholder">
<p>{"🏆 Vampire Achievements coming soon..."}</p>
</div>
}
</div>