generated from nhcarrigan/template
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:
+179
-15
@@ -10,8 +10,12 @@ import type {
|
|||||||
ApotheosisRequest,
|
ApotheosisRequest,
|
||||||
ApotheosisResponse,
|
ApotheosisResponse,
|
||||||
AuthResponse,
|
AuthResponse,
|
||||||
|
AwakeningRequest,
|
||||||
|
AwakeningResponse,
|
||||||
BossChallengeRequest,
|
BossChallengeRequest,
|
||||||
BossChallengeResponse,
|
BossChallengeResponse,
|
||||||
|
BuyAwakeningUpgradeRequest,
|
||||||
|
BuyAwakeningUpgradeResponse,
|
||||||
BuyConsecrationUpgradeRequest,
|
BuyConsecrationUpgradeRequest,
|
||||||
BuyConsecrationUpgradeResponse,
|
BuyConsecrationUpgradeResponse,
|
||||||
BuyEchoUpgradeRequest,
|
BuyEchoUpgradeRequest,
|
||||||
@@ -22,6 +26,10 @@ import type {
|
|||||||
BuyGoddessUpgradeResponse,
|
BuyGoddessUpgradeResponse,
|
||||||
BuyPrestigeUpgradeRequest,
|
BuyPrestigeUpgradeRequest,
|
||||||
BuyPrestigeUpgradeResponse,
|
BuyPrestigeUpgradeResponse,
|
||||||
|
BuySiringUpgradeRequest,
|
||||||
|
BuySiringUpgradeResponse,
|
||||||
|
BuyVampireUpgradeRequest,
|
||||||
|
BuyVampireUpgradeResponse,
|
||||||
ConsecrationRequest,
|
ConsecrationRequest,
|
||||||
ConsecrationResponse,
|
ConsecrationResponse,
|
||||||
CraftRecipeRequest,
|
CraftRecipeRequest,
|
||||||
@@ -49,11 +57,22 @@ import type {
|
|||||||
PublicProfileResponse,
|
PublicProfileResponse,
|
||||||
SaveRequest,
|
SaveRequest,
|
||||||
SaveResponse,
|
SaveResponse,
|
||||||
|
SiringRequest,
|
||||||
|
SiringResponse,
|
||||||
SyncNewContentResponse,
|
SyncNewContentResponse,
|
||||||
TranscendenceRequest,
|
TranscendenceRequest,
|
||||||
TranscendenceResponse,
|
TranscendenceResponse,
|
||||||
UpdateProfileRequest,
|
UpdateProfileRequest,
|
||||||
UpdateProfileResponse,
|
UpdateProfileResponse,
|
||||||
|
VampireBossChallengeRequest,
|
||||||
|
VampireBossChallengeResponse,
|
||||||
|
VampireCraftRequest,
|
||||||
|
VampireCraftResponse,
|
||||||
|
VampireExploreClaimableResponse,
|
||||||
|
VampireExploreCollectRequest,
|
||||||
|
VampireExploreCollectResponse,
|
||||||
|
VampireExploreStartRequest,
|
||||||
|
VampireExploreStartResponse,
|
||||||
} from "@elysium/types";
|
} from "@elysium/types";
|
||||||
|
|
||||||
const baseUrl = "/api";
|
const baseUrl = "/api";
|
||||||
@@ -356,10 +375,10 @@ const debugHardReset = async(): Promise<LoadResponse> => {
|
|||||||
const challengeGoddessBoss = async(
|
const challengeGoddessBoss = async(
|
||||||
body: GoddessBossChallengeRequest,
|
body: GoddessBossChallengeRequest,
|
||||||
): Promise<GoddessBossChallengeResponse> => {
|
): Promise<GoddessBossChallengeResponse> => {
|
||||||
return await fetchJson<GoddessBossChallengeResponse>("/goddess/boss", {
|
return await fetchJson<GoddessBossChallengeResponse>(
|
||||||
body: JSON.stringify(body),
|
"/goddess-boss/challenge",
|
||||||
method: "POST",
|
{ body: JSON.stringify(body), method: "POST" },
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -426,7 +445,7 @@ const buyEnlightenmentUpgrade = async(
|
|||||||
const buyGoddessUpgrade = async(
|
const buyGoddessUpgrade = async(
|
||||||
body: BuyGoddessUpgradeRequest,
|
body: BuyGoddessUpgradeRequest,
|
||||||
): Promise<BuyGoddessUpgradeResponse> => {
|
): Promise<BuyGoddessUpgradeResponse> => {
|
||||||
return await fetchJson<BuyGoddessUpgradeResponse>("/goddess/upgrade", {
|
return await fetchJson<BuyGoddessUpgradeResponse>("/goddess-upgrade/buy", {
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
@@ -440,7 +459,7 @@ const buyGoddessUpgrade = async(
|
|||||||
const craftGoddessRecipe = async(
|
const craftGoddessRecipe = async(
|
||||||
body: GoddessCraftRequest,
|
body: GoddessCraftRequest,
|
||||||
): Promise<GoddessCraftResponse> => {
|
): Promise<GoddessCraftResponse> => {
|
||||||
return await fetchJson<GoddessCraftResponse>("/goddess/craft", {
|
return await fetchJson<GoddessCraftResponse>("/goddess-craft", {
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
@@ -454,10 +473,10 @@ const craftGoddessRecipe = async(
|
|||||||
const startGoddessExploration = async(
|
const startGoddessExploration = async(
|
||||||
body: GoddessExploreStartRequest,
|
body: GoddessExploreStartRequest,
|
||||||
): Promise<GoddessExploreStartResponse> => {
|
): Promise<GoddessExploreStartResponse> => {
|
||||||
return await fetchJson<GoddessExploreStartResponse>("/goddess/explore", {
|
return await fetchJson<GoddessExploreStartResponse>(
|
||||||
body: JSON.stringify(body),
|
"/goddess-explore/start",
|
||||||
method: "POST",
|
{ body: JSON.stringify(body), method: "POST" },
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -468,10 +487,10 @@ const startGoddessExploration = async(
|
|||||||
const collectGoddessExploration = async(
|
const collectGoddessExploration = async(
|
||||||
body: GoddessExploreCollectRequest,
|
body: GoddessExploreCollectRequest,
|
||||||
): Promise<GoddessExploreCollectResponse> => {
|
): Promise<GoddessExploreCollectResponse> => {
|
||||||
return await fetchJson<GoddessExploreCollectResponse>("/goddess/explore", {
|
return await fetchJson<GoddessExploreCollectResponse>(
|
||||||
body: JSON.stringify(body),
|
"/goddess-explore/collect",
|
||||||
method: "PUT",
|
{ body: JSON.stringify(body), method: "PUT" },
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -483,7 +502,142 @@ const checkGoddessExplorationClaimable = async(
|
|||||||
areaId: string,
|
areaId: string,
|
||||||
): Promise<GoddessExploreClaimableResponse> => {
|
): Promise<GoddessExploreClaimableResponse> => {
|
||||||
return await fetchJson<GoddessExploreClaimableResponse>(
|
return await fetchJson<GoddessExploreClaimableResponse>(
|
||||||
`/goddess/explore/claimable?areaId=${encodeURIComponent(areaId)}`,
|
`/goddess-explore/claimable?areaId=${encodeURIComponent(areaId)}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Challenges a vampire boss.
|
||||||
|
* @param body - The vampire boss challenge request payload.
|
||||||
|
* @returns The vampire boss challenge response data.
|
||||||
|
*/
|
||||||
|
const challengeVampireBoss = async(
|
||||||
|
body: VampireBossChallengeRequest,
|
||||||
|
): Promise<VampireBossChallengeResponse> => {
|
||||||
|
return await fetchJson<VampireBossChallengeResponse>(
|
||||||
|
"/vampire-boss/challenge",
|
||||||
|
{ body: JSON.stringify(body), method: "POST" },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a siring reset on the server.
|
||||||
|
* @param body - The siring request payload.
|
||||||
|
* @returns The siring response data.
|
||||||
|
*/
|
||||||
|
const sire = async(body: SiringRequest): Promise<SiringResponse> => {
|
||||||
|
return await fetchJson<SiringResponse>("/siring", {
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purchases a siring upgrade on the server.
|
||||||
|
* @param body - The buy siring upgrade request payload.
|
||||||
|
* @returns The buy siring upgrade response data.
|
||||||
|
*/
|
||||||
|
const buySiringUpgrade = async(
|
||||||
|
body: BuySiringUpgradeRequest,
|
||||||
|
): Promise<BuySiringUpgradeResponse> => {
|
||||||
|
return await fetchJson<BuySiringUpgradeResponse>("/siring/buy-upgrade", {
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a vampire awakening reset on the server.
|
||||||
|
* @param body - The awakening request payload.
|
||||||
|
* @returns The awakening response data.
|
||||||
|
*/
|
||||||
|
const awaken = async(body: AwakeningRequest): Promise<AwakeningResponse> => {
|
||||||
|
return await fetchJson<AwakeningResponse>("/vampire-awakening", {
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purchases a vampire awakening upgrade on the server.
|
||||||
|
* @param body - The buy awakening upgrade request payload.
|
||||||
|
* @returns The buy awakening upgrade response data.
|
||||||
|
*/
|
||||||
|
const buyAwakeningUpgrade = async(
|
||||||
|
body: BuyAwakeningUpgradeRequest,
|
||||||
|
): Promise<BuyAwakeningUpgradeResponse> => {
|
||||||
|
return await fetchJson<BuyAwakeningUpgradeResponse>(
|
||||||
|
"/vampire-awakening/buy-upgrade",
|
||||||
|
{ body: JSON.stringify(body), method: "POST" },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purchases a vampire upgrade on the server.
|
||||||
|
* @param body - The buy vampire upgrade request payload.
|
||||||
|
* @returns The buy vampire upgrade response data.
|
||||||
|
*/
|
||||||
|
const buyVampireUpgrade = async(
|
||||||
|
body: BuyVampireUpgradeRequest,
|
||||||
|
): Promise<BuyVampireUpgradeResponse> => {
|
||||||
|
return await fetchJson<BuyVampireUpgradeResponse>("/vampire-upgrade/buy", {
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crafts a vampire recipe on the server.
|
||||||
|
* @param body - The vampire craft request payload.
|
||||||
|
* @returns The vampire craft response data.
|
||||||
|
*/
|
||||||
|
const craftVampireRecipe = async(
|
||||||
|
body: VampireCraftRequest,
|
||||||
|
): Promise<VampireCraftResponse> => {
|
||||||
|
return await fetchJson<VampireCraftResponse>("/vampire-craft", {
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a vampire exploration in a given area.
|
||||||
|
* @param body - The vampire exploration start request payload.
|
||||||
|
* @returns The vampire exploration start response data.
|
||||||
|
*/
|
||||||
|
const startVampireExploration = async(
|
||||||
|
body: VampireExploreStartRequest,
|
||||||
|
): Promise<VampireExploreStartResponse> => {
|
||||||
|
return await fetchJson<VampireExploreStartResponse>(
|
||||||
|
"/vampire-explore/start",
|
||||||
|
{ body: JSON.stringify(body), method: "POST" },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects the rewards from a completed vampire exploration.
|
||||||
|
* @param body - The vampire exploration collect request payload.
|
||||||
|
* @returns The vampire exploration collect response data.
|
||||||
|
*/
|
||||||
|
const collectVampireExploration = async(
|
||||||
|
body: VampireExploreCollectRequest,
|
||||||
|
): Promise<VampireExploreCollectResponse> => {
|
||||||
|
return await fetchJson<VampireExploreCollectResponse>(
|
||||||
|
"/vampire-explore/collect",
|
||||||
|
{ body: JSON.stringify(body), method: "PUT" },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a given vampire exploration area is ready to claim on the server.
|
||||||
|
* @param areaId - The area ID to check.
|
||||||
|
* @returns Whether the vampire exploration is claimable.
|
||||||
|
*/
|
||||||
|
const checkVampireExplorationClaimable = async(
|
||||||
|
areaId: string,
|
||||||
|
): Promise<VampireExploreClaimableResponse> => {
|
||||||
|
return await fetchJson<VampireExploreClaimableResponse>(
|
||||||
|
`/vampire-explore/claimable?areaId=${encodeURIComponent(areaId)}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -515,20 +669,28 @@ const updateProfile = async(
|
|||||||
export {
|
export {
|
||||||
ValidationError,
|
ValidationError,
|
||||||
achieveApotheosis,
|
achieveApotheosis,
|
||||||
|
awaken,
|
||||||
|
buyAwakeningUpgrade,
|
||||||
buyConsecrationUpgrade,
|
buyConsecrationUpgrade,
|
||||||
buyEchoUpgrade,
|
buyEchoUpgrade,
|
||||||
buyEnlightenmentUpgrade,
|
buyEnlightenmentUpgrade,
|
||||||
buyGoddessUpgrade,
|
buyGoddessUpgrade,
|
||||||
buyPrestigeUpgrade,
|
buyPrestigeUpgrade,
|
||||||
|
buySiringUpgrade,
|
||||||
|
buyVampireUpgrade,
|
||||||
challengeBoss,
|
challengeBoss,
|
||||||
challengeGoddessBoss,
|
challengeGoddessBoss,
|
||||||
|
challengeVampireBoss,
|
||||||
checkExplorationClaimable,
|
checkExplorationClaimable,
|
||||||
checkGoddessExplorationClaimable,
|
checkGoddessExplorationClaimable,
|
||||||
|
checkVampireExplorationClaimable,
|
||||||
collectExploration,
|
collectExploration,
|
||||||
collectGoddessExploration,
|
collectGoddessExploration,
|
||||||
|
collectVampireExploration,
|
||||||
consecrate,
|
consecrate,
|
||||||
craftGoddessRecipe,
|
craftGoddessRecipe,
|
||||||
craftRecipe,
|
craftRecipe,
|
||||||
|
craftVampireRecipe,
|
||||||
debugHardReset,
|
debugHardReset,
|
||||||
enlighten,
|
enlighten,
|
||||||
forceUnlocks,
|
forceUnlocks,
|
||||||
@@ -541,8 +703,10 @@ export {
|
|||||||
prestige,
|
prestige,
|
||||||
resetProgress,
|
resetProgress,
|
||||||
saveGame,
|
saveGame,
|
||||||
|
sire,
|
||||||
startExploration,
|
startExploration,
|
||||||
startGoddessExploration,
|
startGoddessExploration,
|
||||||
|
startVampireExploration,
|
||||||
transcend,
|
transcend,
|
||||||
updateProfile,
|
updateProfile,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
/* eslint-disable max-lines -- Complex layout with many conditional renders */
|
/* eslint-disable max-lines -- Complex layout with many conditional renders */
|
||||||
/* eslint-disable max-lines-per-function -- 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 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 { type JSX, useEffect, useState } from "react";
|
||||||
import { useGame } from "../../context/gameContext.js";
|
import { useGame } from "../../context/gameContext.js";
|
||||||
import { ResourceBar } from "../ui/resourceBar.js";
|
import { ResourceBar } from "../ui/resourceBar.js";
|
||||||
@@ -89,6 +90,19 @@ type GoddessTab =
|
|||||||
| "goddess-exploration"
|
| "goddess-exploration"
|
||||||
| "goddess-achievements";
|
| "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 }> = [
|
const baseTabs: Array<{ id: Tab; label: string }> = [
|
||||||
{ id: "adventurers", label: "⚔️ Adventurers" },
|
{ id: "adventurers", label: "⚔️ Adventurers" },
|
||||||
{ id: "upgrades", label: "🔧 Upgrades" },
|
{ id: "upgrades", label: "🔧 Upgrades" },
|
||||||
@@ -111,6 +125,20 @@ const baseTabs: Array<{ id: Tab; label: string }> = [
|
|||||||
{ id: "debug", label: "🔧 Debug" },
|
{ 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 }> = [
|
const goddessTabs: Array<{ id: GoddessTab; label: string }> = [
|
||||||
{ id: "goddess-zones", label: "🌟 Zones" },
|
{ id: "goddess-zones", label: "🌟 Zones" },
|
||||||
{ id: "goddess-bosses", label: "👁️ Bosses" },
|
{ id: "goddess-bosses", label: "👁️ Bosses" },
|
||||||
@@ -169,12 +197,15 @@ const GameLayout = (): JSX.Element => {
|
|||||||
const [ activeTab, setActiveTab ] = useState<Tab>("adventurers");
|
const [ activeTab, setActiveTab ] = useState<Tab>("adventurers");
|
||||||
const [ activeGoddessTab, setActiveGoddessTab ]
|
const [ activeGoddessTab, setActiveGoddessTab ]
|
||||||
= useState<GoddessTab>("goddess-zones");
|
= useState<GoddessTab>("goddess-zones");
|
||||||
|
const [ activeVampireTab, setActiveVampireTab ]
|
||||||
|
= useState<VampireTab>("vampire-zones");
|
||||||
const [ editingProfile, setEditingProfile ] = useState(false);
|
const [ editingProfile, setEditingProfile ] = useState(false);
|
||||||
const [ dismissedOutdatedWarning, setDismissedOutdatedWarning ]
|
const [ dismissedOutdatedWarning, setDismissedOutdatedWarning ]
|
||||||
= useState(false);
|
= useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.classList.toggle("goddess-mode", activeMode === "goddess");
|
document.body.classList.toggle("goddess-mode", activeMode === "goddess");
|
||||||
|
document.body.classList.toggle("vampire-mode", activeMode === "vampire");
|
||||||
}, [ activeMode ]);
|
}, [ activeMode ]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -273,8 +304,13 @@ const GameLayout = (): JSX.Element => {
|
|||||||
<nav className="mode-bar">
|
<nav className="mode-bar">
|
||||||
{modes.map((mode) => {
|
{modes.map((mode) => {
|
||||||
const apotheosisCount = state.apotheosis?.count ?? 0;
|
const apotheosisCount = state.apotheosis?.count ?? 0;
|
||||||
const goddessLocked = mode === "goddess" && apotheosisCount === 0;
|
const eternalSovereigntyCount
|
||||||
const isLocked = goddessLocked || mode === "vampire";
|
= state.vampire?.eternalSovereignty.count ?? 0;
|
||||||
|
const vampireLocked
|
||||||
|
= mode === "vampire" && apotheosisCount === 0;
|
||||||
|
const goddessLocked
|
||||||
|
= mode === "goddess" && eternalSovereigntyCount === 0;
|
||||||
|
const isLocked = vampireLocked || goddessLocked;
|
||||||
function handleModeClick(): void {
|
function handleModeClick(): void {
|
||||||
if (!isLocked) {
|
if (!isLocked) {
|
||||||
handleSetMode(mode);
|
handleSetMode(mode);
|
||||||
@@ -304,6 +340,7 @@ const GameLayout = (): JSX.Element => {
|
|||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{/* eslint-disable-next-line no-nested-ternary -- Three-way mode switch for tab bar */}
|
||||||
{activeMode === "mortal"
|
{activeMode === "mortal"
|
||||||
? <nav className="tab-bar">
|
? <nav className="tab-bar">
|
||||||
{baseTabs.map((tab) => {
|
{baseTabs.map((tab) => {
|
||||||
@@ -331,7 +368,8 @@ const GameLayout = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
: <nav className="tab-bar goddess-tab-bar">
|
: activeMode === "goddess"
|
||||||
|
? <nav className="tab-bar goddess-tab-bar">
|
||||||
{goddessTabs.map((tab) => {
|
{goddessTabs.map((tab) => {
|
||||||
const { id: tabId, label } = tab;
|
const { id: tabId, label } = tab;
|
||||||
function handleGoddessTabClick(): void {
|
function handleGoddessTabClick(): void {
|
||||||
@@ -351,6 +389,26 @@ const GameLayout = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</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">
|
<div className="tab-content">
|
||||||
@@ -420,8 +478,69 @@ const GameLayout = (): JSX.Element => {
|
|||||||
&& activeGoddessTab === "goddess-achievements"
|
&& activeGoddessTab === "goddess-achievements"
|
||||||
&& <GoddessAchievementsPanel />}
|
&& <GoddessAchievementsPanel />}
|
||||||
{activeMode === "vampire"
|
{activeMode === "vampire"
|
||||||
&& <div className="goddess-placeholder">
|
&& activeVampireTab === "vampire-zones"
|
||||||
<p>{"🧛 Vampire panels coming soon..."}</p>
|
&& <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>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ const ResourceBar = ({
|
|||||||
|
|
||||||
const { gold, essence, crystals, prayers, divinity, stardust } = resources;
|
const { gold, essence, crystals, prayers, divinity, stardust } = resources;
|
||||||
const hasApotheosis = apotheosisCount > 0;
|
const hasApotheosis = apotheosisCount > 0;
|
||||||
|
const blood = resources.blood ?? 0;
|
||||||
|
const ichor = state?.vampire?.siring.ichor ?? 0;
|
||||||
|
const soulShards = state?.vampire?.awakening.soulShards ?? 0;
|
||||||
let partyCombatPower = 0;
|
let partyCombatPower = 0;
|
||||||
let goldPerSecond = 0;
|
let goldPerSecond = 0;
|
||||||
let essencePerSecond = 0;
|
let essencePerSecond = 0;
|
||||||
@@ -286,6 +289,40 @@ const ResourceBar = ({
|
|||||||
</span>
|
</span>
|
||||||
<span className="resource-label">{"Stardust"}</span>
|
<span className="resource-label">{"Stardust"}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<hr className="resources-divider" />
|
||||||
|
<div className={`resource${hasApotheosis
|
||||||
|
? ""
|
||||||
|
: " resource-locked"}`}>
|
||||||
|
<span className="resource-icon">{"🩸"}</span>
|
||||||
|
<span className="resource-value">
|
||||||
|
{hasApotheosis
|
||||||
|
? formatNumber(blood)
|
||||||
|
: "🔒"}
|
||||||
|
</span>
|
||||||
|
<span className="resource-label">{"Blood"}</span>
|
||||||
|
</div>
|
||||||
|
<div className={`resource${hasApotheosis
|
||||||
|
? ""
|
||||||
|
: " resource-locked"}`}>
|
||||||
|
<span className="resource-icon">{"💧"}</span>
|
||||||
|
<span className="resource-value">
|
||||||
|
{hasApotheosis
|
||||||
|
? formatNumber(ichor)
|
||||||
|
: "🔒"}
|
||||||
|
</span>
|
||||||
|
<span className="resource-label">{"Ichor"}</span>
|
||||||
|
</div>
|
||||||
|
<div className={`resource${hasApotheosis
|
||||||
|
? ""
|
||||||
|
: " resource-locked"}`}>
|
||||||
|
<span className="resource-icon">{"💠"}</span>
|
||||||
|
<span className="resource-value">
|
||||||
|
{hasApotheosis
|
||||||
|
? formatNumber(soulShards)
|
||||||
|
: "🔒"}
|
||||||
|
</span>
|
||||||
|
<span className="resource-label">{"Soul Shards"}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5382,3 +5382,36 @@ body,
|
|||||||
color: var(--colour-text-muted);
|
color: var(--colour-text-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===================== VAMPIRE THEME ===================== */
|
||||||
|
body.vampire-mode {
|
||||||
|
--colour-bg: #1a0a0a;
|
||||||
|
--colour-surface: #2d1515;
|
||||||
|
--colour-surface-2: #3d2020;
|
||||||
|
--colour-border: #5c3d3d;
|
||||||
|
--colour-accent: #c41e3a;
|
||||||
|
--colour-accent-light: #e84c3d;
|
||||||
|
--colour-gold: #d4a574;
|
||||||
|
--colour-text: #f5e6e6;
|
||||||
|
--colour-text-muted: #b8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== VAMPIRE TAB BAR ===================== */
|
||||||
|
.vampire-tab-bar .tab-button.active {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--colour-accent),
|
||||||
|
var(--colour-accent-light)
|
||||||
|
);
|
||||||
|
border-color: var(--colour-accent-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== VAMPIRE PLACEHOLDER ===================== */
|
||||||
|
.vampire-placeholder {
|
||||||
|
align-items: center;
|
||||||
|
color: var(--colour-text-muted);
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user