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,
|
||||
ApotheosisResponse,
|
||||
AuthResponse,
|
||||
AwakeningRequest,
|
||||
AwakeningResponse,
|
||||
BossChallengeRequest,
|
||||
BossChallengeResponse,
|
||||
BuyAwakeningUpgradeRequest,
|
||||
BuyAwakeningUpgradeResponse,
|
||||
BuyConsecrationUpgradeRequest,
|
||||
BuyConsecrationUpgradeResponse,
|
||||
BuyEchoUpgradeRequest,
|
||||
@@ -22,6 +26,10 @@ import type {
|
||||
BuyGoddessUpgradeResponse,
|
||||
BuyPrestigeUpgradeRequest,
|
||||
BuyPrestigeUpgradeResponse,
|
||||
BuySiringUpgradeRequest,
|
||||
BuySiringUpgradeResponse,
|
||||
BuyVampireUpgradeRequest,
|
||||
BuyVampireUpgradeResponse,
|
||||
ConsecrationRequest,
|
||||
ConsecrationResponse,
|
||||
CraftRecipeRequest,
|
||||
@@ -49,11 +57,22 @@ import type {
|
||||
PublicProfileResponse,
|
||||
SaveRequest,
|
||||
SaveResponse,
|
||||
SiringRequest,
|
||||
SiringResponse,
|
||||
SyncNewContentResponse,
|
||||
TranscendenceRequest,
|
||||
TranscendenceResponse,
|
||||
UpdateProfileRequest,
|
||||
UpdateProfileResponse,
|
||||
VampireBossChallengeRequest,
|
||||
VampireBossChallengeResponse,
|
||||
VampireCraftRequest,
|
||||
VampireCraftResponse,
|
||||
VampireExploreClaimableResponse,
|
||||
VampireExploreCollectRequest,
|
||||
VampireExploreCollectResponse,
|
||||
VampireExploreStartRequest,
|
||||
VampireExploreStartResponse,
|
||||
} from "@elysium/types";
|
||||
|
||||
const baseUrl = "/api";
|
||||
@@ -356,10 +375,10 @@ const debugHardReset = async(): Promise<LoadResponse> => {
|
||||
const challengeGoddessBoss = async(
|
||||
body: GoddessBossChallengeRequest,
|
||||
): Promise<GoddessBossChallengeResponse> => {
|
||||
return await fetchJson<GoddessBossChallengeResponse>("/goddess/boss", {
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
});
|
||||
return await fetchJson<GoddessBossChallengeResponse>(
|
||||
"/goddess-boss/challenge",
|
||||
{ body: JSON.stringify(body), method: "POST" },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -426,7 +445,7 @@ const buyEnlightenmentUpgrade = async(
|
||||
const buyGoddessUpgrade = async(
|
||||
body: BuyGoddessUpgradeRequest,
|
||||
): Promise<BuyGoddessUpgradeResponse> => {
|
||||
return await fetchJson<BuyGoddessUpgradeResponse>("/goddess/upgrade", {
|
||||
return await fetchJson<BuyGoddessUpgradeResponse>("/goddess-upgrade/buy", {
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
});
|
||||
@@ -440,7 +459,7 @@ const buyGoddessUpgrade = async(
|
||||
const craftGoddessRecipe = async(
|
||||
body: GoddessCraftRequest,
|
||||
): Promise<GoddessCraftResponse> => {
|
||||
return await fetchJson<GoddessCraftResponse>("/goddess/craft", {
|
||||
return await fetchJson<GoddessCraftResponse>("/goddess-craft", {
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
});
|
||||
@@ -454,10 +473,10 @@ const craftGoddessRecipe = async(
|
||||
const startGoddessExploration = async(
|
||||
body: GoddessExploreStartRequest,
|
||||
): Promise<GoddessExploreStartResponse> => {
|
||||
return await fetchJson<GoddessExploreStartResponse>("/goddess/explore", {
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
});
|
||||
return await fetchJson<GoddessExploreStartResponse>(
|
||||
"/goddess-explore/start",
|
||||
{ body: JSON.stringify(body), method: "POST" },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -468,10 +487,10 @@ const startGoddessExploration = async(
|
||||
const collectGoddessExploration = async(
|
||||
body: GoddessExploreCollectRequest,
|
||||
): Promise<GoddessExploreCollectResponse> => {
|
||||
return await fetchJson<GoddessExploreCollectResponse>("/goddess/explore", {
|
||||
body: JSON.stringify(body),
|
||||
method: "PUT",
|
||||
});
|
||||
return await fetchJson<GoddessExploreCollectResponse>(
|
||||
"/goddess-explore/collect",
|
||||
{ body: JSON.stringify(body), method: "PUT" },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -483,7 +502,142 @@ const checkGoddessExplorationClaimable = async(
|
||||
areaId: string,
|
||||
): Promise<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 {
|
||||
ValidationError,
|
||||
achieveApotheosis,
|
||||
awaken,
|
||||
buyAwakeningUpgrade,
|
||||
buyConsecrationUpgrade,
|
||||
buyEchoUpgrade,
|
||||
buyEnlightenmentUpgrade,
|
||||
buyGoddessUpgrade,
|
||||
buyPrestigeUpgrade,
|
||||
buySiringUpgrade,
|
||||
buyVampireUpgrade,
|
||||
challengeBoss,
|
||||
challengeGoddessBoss,
|
||||
challengeVampireBoss,
|
||||
checkExplorationClaimable,
|
||||
checkGoddessExplorationClaimable,
|
||||
checkVampireExplorationClaimable,
|
||||
collectExploration,
|
||||
collectGoddessExploration,
|
||||
collectVampireExploration,
|
||||
consecrate,
|
||||
craftGoddessRecipe,
|
||||
craftRecipe,
|
||||
craftVampireRecipe,
|
||||
debugHardReset,
|
||||
enlighten,
|
||||
forceUnlocks,
|
||||
@@ -541,8 +703,10 @@ export {
|
||||
prestige,
|
||||
resetProgress,
|
||||
saveGame,
|
||||
sire,
|
||||
startExploration,
|
||||
startGoddessExploration,
|
||||
startVampireExploration,
|
||||
transcend,
|
||||
updateProfile,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -88,6 +88,9 @@ const ResourceBar = ({
|
||||
|
||||
const { gold, essence, crystals, prayers, divinity, stardust } = resources;
|
||||
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 goldPerSecond = 0;
|
||||
let essencePerSecond = 0;
|
||||
@@ -286,6 +289,40 @@ const ResourceBar = ({
|
||||
</span>
|
||||
<span className="resource-label">{"Stardust"}</span>
|
||||
</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>
|
||||
: null}
|
||||
</div>
|
||||
|
||||
@@ -5382,3 +5382,36 @@ body,
|
||||
color: var(--colour-text-muted);
|
||||
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