generated from nhcarrigan/template
feat: expansion coming-soon preview with save-safe display data
Add an expansion preview system so the Goddess and Vampire panels render their full content (zones, bosses, thralls, achievements, etc.) even when the expansion has not been unlocked, with all interactive elements visually disabled. - API /load now returns expansionPreview alongside game state, populated from initialGoddessState() and initialVampireState() — never part of the saved blob - LoadResponse type updated with expansionPreview field - gameContext exposes goddessPreview and vampirePreview, stored in separate state vars that never touch stateReference so saves are never polluted - gameLayout applies expansion-preview CSS class when viewing a locked expansion with preview data available, and shows the coming-soon banner - All 22 expansion panels updated to use state.vampire ?? vampirePreview and state.goddess ?? goddessPreview for display - CSS disables all buttons/inputs/selects inside .expansion-preview - apotheosis service patched to never auto-initialise goddess state — expansion remains locked until explicitly released
This commit is contained in:
+53
-21
@@ -20,7 +20,11 @@ import {
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { defaultBosses } from "../data/bosses.js";
|
import { defaultBosses } from "../data/bosses.js";
|
||||||
import { defaultEquipmentSets } from "../data/equipmentSets.js";
|
import { defaultEquipmentSets } from "../data/equipmentSets.js";
|
||||||
import { initialGameState } from "../data/initialState.js";
|
import {
|
||||||
|
initialGameState,
|
||||||
|
initialGoddessState,
|
||||||
|
initialVampireState,
|
||||||
|
} from "../data/initialState.js";
|
||||||
import { dailyRewards } from "../data/loginBonus.js";
|
import { dailyRewards } from "../data/loginBonus.js";
|
||||||
import { defaultQuests } from "../data/quests.js";
|
import { defaultQuests } from "../data/quests.js";
|
||||||
import { currentSchemaVersion } from "../data/schemaVersion.js";
|
import { currentSchemaVersion } from "../data/schemaVersion.js";
|
||||||
@@ -1152,17 +1156,29 @@ gameRouter.get("/load", async(context) => {
|
|||||||
const signature = secret === undefined
|
const signature = secret === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: computeHmac(JSON.stringify(freshState), secret);
|
: computeHmac(JSON.stringify(freshState), secret);
|
||||||
|
const { inGuild, loginStreak } = playerRecord;
|
||||||
|
const loginBonus = null;
|
||||||
|
const offlineEssence = 0;
|
||||||
|
const offlineGold = 0;
|
||||||
|
const offlineSeconds = 0;
|
||||||
|
const schemaOutdated = false;
|
||||||
|
const state = freshState;
|
||||||
|
const expansionPreview = {
|
||||||
|
goddess: initialGoddessState(),
|
||||||
|
vampire: initialVampireState(),
|
||||||
|
};
|
||||||
return context.json({
|
return context.json({
|
||||||
currentSchemaVersion: currentSchemaVersion,
|
currentSchemaVersion,
|
||||||
inGuild: playerRecord.inGuild,
|
expansionPreview,
|
||||||
loginBonus: null,
|
inGuild,
|
||||||
loginStreak: playerRecord.loginStreak,
|
loginBonus,
|
||||||
offlineEssence: 0,
|
loginStreak,
|
||||||
offlineGold: 0,
|
offlineEssence,
|
||||||
offlineSeconds: 0,
|
offlineGold,
|
||||||
schemaOutdated: false,
|
offlineSeconds,
|
||||||
signature: signature,
|
schemaOutdated,
|
||||||
state: freshState,
|
signature,
|
||||||
|
state,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1294,8 +1310,13 @@ gameRouter.get("/load", async(context) => {
|
|||||||
? undefined
|
? undefined
|
||||||
: computeHmac(JSON.stringify(state), secret);
|
: computeHmac(JSON.stringify(state), secret);
|
||||||
const inGuild = playerRecord?.inGuild ?? false;
|
const inGuild = playerRecord?.inGuild ?? false;
|
||||||
|
const expansionPreview = {
|
||||||
|
goddess: initialGoddessState(),
|
||||||
|
vampire: initialVampireState(),
|
||||||
|
};
|
||||||
return context.json({
|
return context.json({
|
||||||
currentSchemaVersion,
|
currentSchemaVersion,
|
||||||
|
expansionPreview,
|
||||||
inGuild,
|
inGuild,
|
||||||
loginBonus,
|
loginBonus,
|
||||||
loginStreak,
|
loginStreak,
|
||||||
@@ -1524,17 +1545,28 @@ gameRouter.post("/reset", async(context) => {
|
|||||||
const signature = secret === undefined
|
const signature = secret === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: computeHmac(JSON.stringify(freshState), secret);
|
: computeHmac(JSON.stringify(freshState), secret);
|
||||||
|
const { loginStreak } = playerRecord;
|
||||||
|
const loginBonus = null;
|
||||||
|
const offlineEssence = 0;
|
||||||
|
const offlineGold = 0;
|
||||||
|
const offlineSeconds = 0;
|
||||||
|
const schemaOutdated = false;
|
||||||
|
const state = freshState;
|
||||||
|
const expansionPreview = {
|
||||||
|
goddess: initialGoddessState(),
|
||||||
|
vampire: initialVampireState(),
|
||||||
|
};
|
||||||
return context.json({
|
return context.json({
|
||||||
currentSchemaVersion: currentSchemaVersion,
|
currentSchemaVersion,
|
||||||
loginBonus: null,
|
expansionPreview,
|
||||||
loginStreak: playerRecord.loginStreak,
|
loginBonus,
|
||||||
offlineEssence: 0,
|
loginStreak,
|
||||||
offlineGold: 0,
|
offlineEssence,
|
||||||
offlineSeconds: 0,
|
offlineGold,
|
||||||
schemaOutdated: false,
|
offlineSeconds,
|
||||||
signature: signature,
|
schemaOutdated,
|
||||||
state: freshState,
|
signature,
|
||||||
|
state,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
void logger.error(
|
void logger.error(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* @license Naomi's Public License
|
* @license Naomi's Public License
|
||||||
* @author Naomi Carrigan
|
* @author Naomi Carrigan
|
||||||
*/
|
*/
|
||||||
import { initialGameState, initialGoddessState } from "../data/initialState.js";
|
import { initialGameState } from "../data/initialState.js";
|
||||||
import {
|
import {
|
||||||
defaultTranscendenceUpgrades,
|
defaultTranscendenceUpgrades,
|
||||||
} from "../data/transcendenceUpgrades.js";
|
} from "../data/transcendenceUpgrades.js";
|
||||||
@@ -48,13 +48,11 @@ const buildPostApotheosisState = (
|
|||||||
|
|
||||||
const freshState = initialGameState(currentState.player, characterName);
|
const freshState = initialGameState(currentState.player, characterName);
|
||||||
|
|
||||||
// Goddess state: initialised on first apotheosis, preserved on subsequent resets
|
// Goddess state: preserved across resets; never auto-initialised (expansion not yet live)
|
||||||
let goddessSpread: object = {};
|
const goddessSpread: object
|
||||||
if (apotheosisCount === 1) {
|
= currentState.goddess === undefined
|
||||||
goddessSpread = { goddess: initialGoddessState() };
|
? {}
|
||||||
} else if (currentState.goddess !== undefined) {
|
: { goddess: currentState.goddess };
|
||||||
goddessSpread = { goddess: currentState.goddess };
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedState: GameState = {
|
const updatedState: GameState = {
|
||||||
...freshState,
|
...freshState,
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ const ConsecrationPanel = (): JSX.Element => {
|
|||||||
buyConsecrationUpgrade,
|
buyConsecrationUpgrade,
|
||||||
showConsecrationToast,
|
showConsecrationToast,
|
||||||
dismissConsecrationToast,
|
dismissConsecrationToast,
|
||||||
|
goddessPreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
|
|
||||||
const [ isPending, setIsPending ] = useState(false);
|
const [ isPending, setIsPending ] = useState(false);
|
||||||
@@ -327,7 +328,7 @@ const ConsecrationPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { goddess } = state;
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
|
|
||||||
if (goddess === undefined) {
|
if (goddess === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ const DiscipleCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const DisciplesPanel = (): JSX.Element => {
|
const DisciplesPanel = (): JSX.Element => {
|
||||||
const { state, formatNumber } = useGame();
|
const { state, formatNumber, goddessPreview } = useGame();
|
||||||
const [ selectedBatch, setSelectedBatch ] = useState<BatchSize>(() => {
|
const [ selectedBatch, setSelectedBatch ] = useState<BatchSize>(() => {
|
||||||
return parseBatchSize(localStorage.getItem("elysium_disciple_batch"));
|
return parseBatchSize(localStorage.getItem("elysium_disciple_batch"));
|
||||||
});
|
});
|
||||||
@@ -212,7 +212,7 @@ const DisciplesPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const goddessState = state.goddess;
|
const goddessState = state.goddess ?? goddessPreview;
|
||||||
if (goddessState === undefined) {
|
if (goddessState === undefined) {
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ const EnlightenmentPanel = (): JSX.Element => {
|
|||||||
buyEnlightenmentUpgrade,
|
buyEnlightenmentUpgrade,
|
||||||
showEnlightenmentToast,
|
showEnlightenmentToast,
|
||||||
dismissEnlightenmentToast,
|
dismissEnlightenmentToast,
|
||||||
|
goddessPreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
|
|
||||||
const [ isPending, setIsPending ] = useState(false);
|
const [ isPending, setIsPending ] = useState(false);
|
||||||
@@ -226,7 +227,7 @@ const EnlightenmentPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { goddess } = state;
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
|
|
||||||
if (goddess === undefined) {
|
if (goddess === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -231,6 +231,8 @@ const GameLayout = (): JSX.Element => {
|
|||||||
loginBonus,
|
loginBonus,
|
||||||
dismissLoginBonus,
|
dismissLoginBonus,
|
||||||
schemaOutdated,
|
schemaOutdated,
|
||||||
|
goddessPreview,
|
||||||
|
vampirePreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
const [ activeMode, setActiveMode ] = useState<Mode>(readSavedMode);
|
const [ activeMode, setActiveMode ] = useState<Mode>(readSavedMode);
|
||||||
const [ activeTab, setActiveTab ] = useState<Tab>("adventurers");
|
const [ activeTab, setActiveTab ] = useState<Tab>("adventurers");
|
||||||
@@ -276,6 +278,13 @@ const GameLayout = (): JSX.Element => {
|
|||||||
|
|
||||||
const codexBadgeCount = pendingCodexEntryIds.length;
|
const codexBadgeCount = pendingCodexEntryIds.length;
|
||||||
const storyBadgeCount = pendingStoryChapterIds.length;
|
const storyBadgeCount = pendingStoryChapterIds.length;
|
||||||
|
const isGoddessPreview = activeMode === "goddess"
|
||||||
|
&& state.goddess === undefined
|
||||||
|
&& goddessPreview !== undefined;
|
||||||
|
const isVampirePreview = activeMode === "vampire"
|
||||||
|
&& state.vampire === undefined
|
||||||
|
&& vampirePreview !== undefined;
|
||||||
|
const isExpansionPreview = isGoddessPreview || isVampirePreview;
|
||||||
|
|
||||||
function handleOpenEditProfile(): void {
|
function handleOpenEditProfile(): void {
|
||||||
setEditingProfile(true);
|
setEditingProfile(true);
|
||||||
@@ -342,38 +351,20 @@ const GameLayout = (): JSX.Element => {
|
|||||||
<main className="game-content">
|
<main className="game-content">
|
||||||
<nav className="mode-bar">
|
<nav className="mode-bar">
|
||||||
{modes.map((mode) => {
|
{modes.map((mode) => {
|
||||||
const apotheosisCount = state.apotheosis?.count ?? 0;
|
|
||||||
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 {
|
function handleModeClick(): void {
|
||||||
if (!isLocked) {
|
|
||||||
handleSetMode(mode);
|
handleSetMode(mode);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`mode-button${activeMode === mode
|
className={`mode-button${activeMode === mode
|
||||||
? " active"
|
? " active"
|
||||||
: ""}${isLocked
|
|
||||||
? " locked"
|
|
||||||
: ""}`}
|
: ""}`}
|
||||||
disabled={isLocked}
|
|
||||||
key={mode}
|
key={mode}
|
||||||
onClick={handleModeClick}
|
onClick={handleModeClick}
|
||||||
title={isLocked
|
title={modeLabels[mode]}
|
||||||
? "Not yet unlocked"
|
|
||||||
: modeLabels[mode]}
|
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{modeLabels[mode]}
|
{modeLabels[mode]}
|
||||||
{isLocked
|
|
||||||
? <span className="mode-lock">{"🔒"}</span>
|
|
||||||
: null}
|
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -450,7 +441,16 @@ const GameLayout = (): JSX.Element => {
|
|||||||
</nav>
|
</nav>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div className="tab-content">
|
<div className={`tab-content${
|
||||||
|
isExpansionPreview
|
||||||
|
? " expansion-preview"
|
||||||
|
: ""
|
||||||
|
}`}>
|
||||||
|
{activeMode !== "mortal"
|
||||||
|
&& <div className="expansion-coming-soon">
|
||||||
|
<p>{"✨ Expansion Coming Soon~"}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{activeMode === "mortal" && activeTab === "adventurers"
|
{activeMode === "mortal" && activeTab === "adventurers"
|
||||||
&& <AdventurerPanel />}
|
&& <AdventurerPanel />}
|
||||||
{activeMode === "mortal" && activeTab === "upgrades"
|
{activeMode === "mortal" && activeTab === "upgrades"
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ const GoddessAchievementCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const GoddessAchievementsPanel = (): JSX.Element => {
|
const GoddessAchievementsPanel = (): JSX.Element => {
|
||||||
const { state, formatNumber } = useGame();
|
const { state, formatNumber, goddessPreview } = useGame();
|
||||||
const [ showLocked, setShowLocked ] = useState(true);
|
const [ showLocked, setShowLocked ] = useState(true);
|
||||||
|
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
@@ -189,7 +189,7 @@ const GoddessAchievementsPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { goddess } = state;
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
|
|
||||||
if (goddess === undefined) {
|
if (goddess === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ const GoddessBossPanel = (): JSX.Element => {
|
|||||||
dismissGoddessBattle,
|
dismissGoddessBattle,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
formatInteger,
|
formatInteger,
|
||||||
|
goddessPreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
|
|
||||||
const [ challengingBossId, setChallengingBossId ] = useState<string | null>(
|
const [ challengingBossId, setChallengingBossId ] = useState<string | null>(
|
||||||
@@ -322,7 +323,7 @@ const GoddessBossPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { goddess } = state;
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
|
|
||||||
if (goddess === undefined) {
|
if (goddess === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
/* eslint-disable max-lines-per-function -- Complex component with many render paths */
|
/* eslint-disable max-lines-per-function -- Complex component with many render paths */
|
||||||
/* eslint-disable max-nested-callbacks -- Nested recipe/material maps require nesting */
|
/* eslint-disable max-nested-callbacks -- Nested recipe/material maps require nesting */
|
||||||
|
/* eslint-disable complexity -- Expansion preview fallback adds necessary branching */
|
||||||
|
|
||||||
import { type JSX, useState } from "react";
|
import { type JSX, useState } from "react";
|
||||||
import { useGame } from "../../context/gameContext.js";
|
import { useGame } from "../../context/gameContext.js";
|
||||||
@@ -25,7 +26,7 @@ const bonusLabel: Record<string, string> = {
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const GoddessCraftingPanel = (): JSX.Element => {
|
const GoddessCraftingPanel = (): JSX.Element => {
|
||||||
const { state, craftGoddessRecipe, formatNumber } = useGame();
|
const { state, craftGoddessRecipe, formatNumber, goddessPreview } = useGame();
|
||||||
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
||||||
return (
|
return (
|
||||||
sessionStorage.getItem("elysium_goddess_craft_zone")
|
sessionStorage.getItem("elysium_goddess_craft_zone")
|
||||||
@@ -42,7 +43,7 @@ const GoddessCraftingPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { goddess } = state;
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
const playerMaterials = goddess?.exploration.materials ?? [];
|
const playerMaterials = goddess?.exploration.materials ?? [];
|
||||||
const craftedIds = goddess?.exploration.craftedRecipeIds ?? [];
|
const craftedIds = goddess?.exploration.craftedRecipeIds ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ type TabFilter = "all" | GoddessEquipmentType;
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
export const GoddessEquipmentPanel = (): JSX.Element => {
|
export const GoddessEquipmentPanel = (): JSX.Element => {
|
||||||
const { state, formatNumber } = useGame();
|
const { state, formatNumber, goddessPreview } = useGame();
|
||||||
const [ activeTab, setActiveTab ] = useState<TabFilter>("all");
|
const [ activeTab, setActiveTab ] = useState<TabFilter>("all");
|
||||||
|
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
@@ -202,7 +202,8 @@ export const GoddessEquipmentPanel = (): JSX.Element => {
|
|||||||
const divinity = state.resources.divinity ?? 0;
|
const divinity = state.resources.divinity ?? 0;
|
||||||
const stardust = state.resources.stardust ?? 0;
|
const stardust = state.resources.stardust ?? 0;
|
||||||
|
|
||||||
const equipment = state.goddess?.equipment ?? [];
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
|
const equipment = goddess?.equipment ?? [];
|
||||||
|
|
||||||
const filteredEquipment = activeTab === "all"
|
const filteredEquipment = activeTab === "all"
|
||||||
? equipment
|
? equipment
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ const GoddessExplorationPanel = (): JSX.Element => {
|
|||||||
startGoddessExploration,
|
startGoddessExploration,
|
||||||
collectGoddessExploration,
|
collectGoddessExploration,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
goddessPreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
||||||
return (
|
return (
|
||||||
@@ -160,7 +161,7 @@ const GoddessExplorationPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { goddess } = state;
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
const explorationState = goddess?.exploration;
|
const explorationState = goddess?.exploration;
|
||||||
const goddessZones = goddess?.zones ?? [];
|
const goddessZones = goddess?.zones ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ const GoddessQuestCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const GoddessQuestsPanel = (): JSX.Element => {
|
const GoddessQuestsPanel = (): JSX.Element => {
|
||||||
const { state } = useGame();
|
const { state, goddessPreview } = useGame();
|
||||||
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
||||||
return sessionStorage.getItem("elysium_goddess_quest_zone")
|
return sessionStorage.getItem("elysium_goddess_quest_zone")
|
||||||
?? "goddess_celestial_garden";
|
?? "goddess_celestial_garden";
|
||||||
@@ -155,7 +155,7 @@ const GoddessQuestsPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const goddessState = state.goddess;
|
const goddessState = state.goddess ?? goddessPreview;
|
||||||
if (goddessState === undefined) {
|
if (goddessState === undefined) {
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ const GoddessUpgradeCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
export const GoddessUpgradesPanel = (): JSX.Element => {
|
export const GoddessUpgradesPanel = (): JSX.Element => {
|
||||||
const { state, formatNumber } = useGame();
|
const { state, formatNumber, goddessPreview } = useGame();
|
||||||
|
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
return <div className="panel"><p>{"Loading..."}</p></div>;
|
return <div className="panel"><p>{"Loading..."}</p></div>;
|
||||||
@@ -174,7 +174,8 @@ export const GoddessUpgradesPanel = (): JSX.Element => {
|
|||||||
const divinity = state.resources.divinity ?? 0;
|
const divinity = state.resources.divinity ?? 0;
|
||||||
const stardust = state.resources.stardust ?? 0;
|
const stardust = state.resources.stardust ?? 0;
|
||||||
|
|
||||||
const upgrades = state.goddess?.upgrades ?? [];
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
|
const upgrades = goddess?.upgrades ?? [];
|
||||||
|
|
||||||
const purchased = upgrades.filter((upgrade) => {
|
const purchased = upgrades.filter((upgrade) => {
|
||||||
return upgrade.purchased;
|
return upgrade.purchased;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const GoddessZoneCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const GoddessZonesPanel = (): JSX.Element => {
|
const GoddessZonesPanel = (): JSX.Element => {
|
||||||
const { state } = useGame();
|
const { state, goddessPreview } = useGame();
|
||||||
|
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
return (
|
return (
|
||||||
@@ -91,7 +91,7 @@ const GoddessZonesPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { goddess } = state;
|
const goddess = state.goddess ?? goddessPreview;
|
||||||
|
|
||||||
if (goddess === undefined) {
|
if (goddess === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ const VampireAchievementCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const VampireAchievementsPanel = (): JSX.Element => {
|
const VampireAchievementsPanel = (): JSX.Element => {
|
||||||
const { state, formatNumber } = useGame();
|
const { state, formatNumber, vampirePreview } = useGame();
|
||||||
const [ showLocked, setShowLocked ] = useState(true);
|
const [ showLocked, setShowLocked ] = useState(true);
|
||||||
|
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
@@ -189,7 +189,7 @@ const VampireAchievementsPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vampire } = state;
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ const VampireAwakeningPanel = (): JSX.Element => {
|
|||||||
formatInteger,
|
formatInteger,
|
||||||
awaken,
|
awaken,
|
||||||
buyAwakeningUpgrade,
|
buyAwakeningUpgrade,
|
||||||
|
vampirePreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
|
|
||||||
const [ isPending, setIsPending ] = useState(false);
|
const [ isPending, setIsPending ] = useState(false);
|
||||||
@@ -208,7 +209,7 @@ const VampireAwakeningPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vampire } = state;
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ const VampireBossPanel = (): JSX.Element => {
|
|||||||
dismissVampireBattle,
|
dismissVampireBattle,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
formatInteger,
|
formatInteger,
|
||||||
|
vampirePreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
|
|
||||||
const [ challengingBossId, setChallengingBossId ] = useState<string | null>(
|
const [ challengingBossId, setChallengingBossId ] = useState<string | null>(
|
||||||
@@ -323,7 +324,7 @@ const VampireBossPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vampire } = state;
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const bonusLabel: Record<string, string> = {
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const VampireCraftingPanel = (): JSX.Element => {
|
const VampireCraftingPanel = (): JSX.Element => {
|
||||||
const { state, craftVampireRecipe, formatNumber } = useGame();
|
const { state, craftVampireRecipe, formatNumber, vampirePreview } = useGame();
|
||||||
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
||||||
return (
|
return (
|
||||||
sessionStorage.getItem("elysium_vampire_craft_zone")
|
sessionStorage.getItem("elysium_vampire_craft_zone")
|
||||||
@@ -41,7 +41,7 @@ const VampireCraftingPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vampire } = state;
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ type TabFilter = "all" | VampireEquipmentType;
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const VampireEquipmentPanel = (): JSX.Element => {
|
const VampireEquipmentPanel = (): JSX.Element => {
|
||||||
const { state, formatNumber } = useGame();
|
const { state, formatNumber, vampirePreview } = useGame();
|
||||||
const [ activeTab, setActiveTab ] = useState<TabFilter>("all");
|
const [ activeTab, setActiveTab ] = useState<TabFilter>("all");
|
||||||
|
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
@@ -203,7 +203,8 @@ const VampireEquipmentPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { resources, vampire } = state;
|
const { resources } = state;
|
||||||
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ const VampireExplorationPanel = (): JSX.Element => {
|
|||||||
startVampireExploration,
|
startVampireExploration,
|
||||||
collectVampireExploration,
|
collectVampireExploration,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
vampirePreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
||||||
return (
|
return (
|
||||||
@@ -160,7 +161,7 @@ const VampireExplorationPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vampire } = state;
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
/* eslint-disable max-lines-per-function -- Complex component with many render paths */
|
/* eslint-disable max-lines-per-function -- Complex component with many render paths */
|
||||||
/* eslint-disable react/no-multi-comp -- QuestCard sub-component is tightly coupled */
|
/* eslint-disable react/no-multi-comp -- QuestCard sub-component is tightly coupled */
|
||||||
|
/* eslint-disable complexity -- Expansion preview fallback adds necessary branching */
|
||||||
import { useState, type JSX } from "react";
|
import { useState, type JSX } from "react";
|
||||||
import { useGame } from "../../context/gameContext.js";
|
import { useGame } from "../../context/gameContext.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -140,7 +141,7 @@ const VampireQuestCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const VampireQuestsPanel = (): JSX.Element => {
|
const VampireQuestsPanel = (): JSX.Element => {
|
||||||
const { state, toggleVampireAutoQuest } = useGame();
|
const { state, toggleVampireAutoQuest, vampirePreview } = useGame();
|
||||||
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
const [ activeZoneId, setActiveZoneId ] = useState(() => {
|
||||||
return sessionStorage.getItem("elysium_vampire_quest_zone")
|
return sessionStorage.getItem("elysium_vampire_quest_zone")
|
||||||
?? "vampire_haunted_catacombs";
|
?? "vampire_haunted_catacombs";
|
||||||
@@ -154,7 +155,7 @@ const VampireQuestsPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const vampireState = state.vampire;
|
const vampireState = state.vampire ?? vampirePreview;
|
||||||
if (vampireState === undefined) {
|
if (vampireState === undefined) {
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ const VampireSiringPanel = (): JSX.Element => {
|
|||||||
formatNumber,
|
formatNumber,
|
||||||
sire,
|
sire,
|
||||||
buySiringUpgrade,
|
buySiringUpgrade,
|
||||||
|
vampirePreview,
|
||||||
} = useGame();
|
} = useGame();
|
||||||
|
|
||||||
const [ isPending, setIsPending ] = useState(false);
|
const [ isPending, setIsPending ] = useState(false);
|
||||||
@@ -324,7 +325,7 @@ const VampireSiringPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vampire } = state;
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -198,7 +198,9 @@ const ThrallCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const VampireThrallsPanel = (): JSX.Element => {
|
const VampireThrallsPanel = (): JSX.Element => {
|
||||||
const { state, formatNumber, toggleVampireAutoThrall } = useGame();
|
const {
|
||||||
|
state, formatNumber, toggleVampireAutoThrall, vampirePreview,
|
||||||
|
} = useGame();
|
||||||
const [ selectedBatch, setSelectedBatch ] = useState<BatchSize>(() => {
|
const [ selectedBatch, setSelectedBatch ] = useState<BatchSize>(() => {
|
||||||
return parseBatchSize(localStorage.getItem("elysium_thrall_batch"));
|
return parseBatchSize(localStorage.getItem("elysium_thrall_batch"));
|
||||||
});
|
});
|
||||||
@@ -211,7 +213,7 @@ const VampireThrallsPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const vampireState = state.vampire;
|
const vampireState = state.vampire ?? vampirePreview;
|
||||||
if (vampireState === undefined) {
|
if (vampireState === undefined) {
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ const VampireUpgradeCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const VampireUpgradesPanel = (): JSX.Element => {
|
const VampireUpgradesPanel = (): JSX.Element => {
|
||||||
const { state, formatNumber } = useGame();
|
const { state, formatNumber, vampirePreview } = useGame();
|
||||||
|
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
return (
|
return (
|
||||||
@@ -177,7 +177,8 @@ const VampireUpgradesPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { resources, vampire } = state;
|
const { resources } = state;
|
||||||
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const VampireZoneCard = ({
|
|||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const VampireZonesPanel = (): JSX.Element => {
|
const VampireZonesPanel = (): JSX.Element => {
|
||||||
const { state } = useGame();
|
const { state, vampirePreview } = useGame();
|
||||||
|
|
||||||
if (state === null) {
|
if (state === null) {
|
||||||
return (
|
return (
|
||||||
@@ -91,7 +91,7 @@ const VampireZonesPanel = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vampire } = state;
|
const vampire = state.vampire ?? vampirePreview;
|
||||||
|
|
||||||
if (vampire === undefined) {
|
if (vampire === undefined) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
STORY_CHAPTERS,
|
STORY_CHAPTERS,
|
||||||
type Achievement,
|
type Achievement,
|
||||||
type ApotheosisResponse,
|
type ApotheosisResponse,
|
||||||
|
type AwakeningResponse,
|
||||||
type BossChallengeResponse,
|
type BossChallengeResponse,
|
||||||
type ConsecrationResponse,
|
type ConsecrationResponse,
|
||||||
type EnlightenmentResponse,
|
type EnlightenmentResponse,
|
||||||
@@ -22,14 +23,15 @@ import {
|
|||||||
type GameState,
|
type GameState,
|
||||||
type GoddessBossChallengeResponse,
|
type GoddessBossChallengeResponse,
|
||||||
type GoddessExploreCollectResponse,
|
type GoddessExploreCollectResponse,
|
||||||
type AwakeningResponse,
|
type GoddessState,
|
||||||
type SiringResponse,
|
|
||||||
type VampireBossChallengeResponse,
|
|
||||||
type VampireExploreCollectResponse,
|
|
||||||
type LoginBonusResult,
|
type LoginBonusResult,
|
||||||
type NumberFormat,
|
type NumberFormat,
|
||||||
type Quest,
|
type Quest,
|
||||||
|
type SiringResponse,
|
||||||
type TranscendenceResponse,
|
type TranscendenceResponse,
|
||||||
|
type VampireBossChallengeResponse,
|
||||||
|
type VampireExploreCollectResponse,
|
||||||
|
type VampireState,
|
||||||
computeUnlockedCompanionIds,
|
computeUnlockedCompanionIds,
|
||||||
isStoryChapterUnlocked,
|
isStoryChapterUnlocked,
|
||||||
} from "@elysium/types";
|
} from "@elysium/types";
|
||||||
@@ -865,6 +867,18 @@ interface GameContextValue {
|
|||||||
* Toggle the vampire auto-thrall setting on/off.
|
* Toggle the vampire auto-thrall setting on/off.
|
||||||
*/
|
*/
|
||||||
toggleVampireAutoThrall: ()=> void;
|
toggleVampireAutoThrall: ()=> void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial goddess state for expansion preview display.
|
||||||
|
* Never saved to game state.
|
||||||
|
*/
|
||||||
|
goddessPreview: GoddessState | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial vampire state for expansion preview display.
|
||||||
|
* Never saved to game state.
|
||||||
|
*/
|
||||||
|
vampirePreview: VampireState | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BattleResult {
|
export interface BattleResult {
|
||||||
@@ -953,6 +967,12 @@ export const GameProvider = ({
|
|||||||
const [ saveSchemaVersion, setSaveSchemaVersion ] = useState(0);
|
const [ saveSchemaVersion, setSaveSchemaVersion ] = useState(0);
|
||||||
const [ currentSchemaVersion, setCurrentSchemaVersion ] = useState(0);
|
const [ currentSchemaVersion, setCurrentSchemaVersion ] = useState(0);
|
||||||
const [ inGuild, setInGuild ] = useState(false);
|
const [ inGuild, setInGuild ] = useState(false);
|
||||||
|
const [ goddessPreview, setGoddessPreview ] = useState<
|
||||||
|
GoddessState | undefined
|
||||||
|
>(undefined);
|
||||||
|
const [ vampirePreview, setVampirePreview ] = useState<
|
||||||
|
VampireState | undefined
|
||||||
|
>(undefined);
|
||||||
const [ unlockedCodexEntryIds, setUnlockedCodexEntryIds ] = useState<
|
const [ unlockedCodexEntryIds, setUnlockedCodexEntryIds ] = useState<
|
||||||
Array<string>
|
Array<string>
|
||||||
>([]);
|
>([]);
|
||||||
@@ -991,6 +1011,8 @@ export const GameProvider = ({
|
|||||||
setSaveSchemaVersion(data.state.schemaVersion ?? 0);
|
setSaveSchemaVersion(data.state.schemaVersion ?? 0);
|
||||||
setCurrentSchemaVersion(data.currentSchemaVersion);
|
setCurrentSchemaVersion(data.currentSchemaVersion);
|
||||||
setInGuild(data.inGuild);
|
setInGuild(data.inGuild);
|
||||||
|
setGoddessPreview(data.expansionPreview.goddess);
|
||||||
|
setVampirePreview(data.expansionPreview.vampire);
|
||||||
|
|
||||||
// Fetch number format preference from profile (fire-and-forget, non-blocking)
|
// Fetch number format preference from profile (fire-and-forget, non-blocking)
|
||||||
void fetch(`/api/profile/${data.state.player.discordId}`).
|
void fetch(`/api/profile/${data.state.player.discordId}`).
|
||||||
@@ -1051,6 +1073,8 @@ export const GameProvider = ({
|
|||||||
setSaveSchemaVersion(data.state.schemaVersion ?? 0);
|
setSaveSchemaVersion(data.state.schemaVersion ?? 0);
|
||||||
setCurrentSchemaVersion(data.currentSchemaVersion);
|
setCurrentSchemaVersion(data.currentSchemaVersion);
|
||||||
setInGuild(data.inGuild);
|
setInGuild(data.inGuild);
|
||||||
|
setGoddessPreview(data.expansionPreview.goddess);
|
||||||
|
setVampirePreview(data.expansionPreview.vampire);
|
||||||
} catch (error_: unknown) {
|
} catch (error_: unknown) {
|
||||||
setError(
|
setError(
|
||||||
error_ instanceof Error
|
error_ instanceof Error
|
||||||
@@ -3577,6 +3601,8 @@ export const GameProvider = ({
|
|||||||
setOfflineGold(0);
|
setOfflineGold(0);
|
||||||
setOfflineEssence(0);
|
setOfflineEssence(0);
|
||||||
setLoginBonus(null);
|
setLoginBonus(null);
|
||||||
|
setGoddessPreview(data.expansionPreview.goddess);
|
||||||
|
setVampirePreview(data.expansionPreview.vampire);
|
||||||
if (data.signature !== undefined) {
|
if (data.signature !== undefined) {
|
||||||
signatureReference.current = data.signature;
|
signatureReference.current = data.signature;
|
||||||
localStorage.setItem("elysium_save_signature", data.signature);
|
localStorage.setItem("elysium_save_signature", data.signature);
|
||||||
@@ -3697,6 +3723,8 @@ export const GameProvider = ({
|
|||||||
setOfflineGold(0);
|
setOfflineGold(0);
|
||||||
setOfflineEssence(0);
|
setOfflineEssence(0);
|
||||||
setLoginBonus(null);
|
setLoginBonus(null);
|
||||||
|
setGoddessPreview(data.expansionPreview.goddess);
|
||||||
|
setVampirePreview(data.expansionPreview.vampire);
|
||||||
if (data.signature !== undefined) {
|
if (data.signature !== undefined) {
|
||||||
signatureReference.current = data.signature;
|
signatureReference.current = data.signature;
|
||||||
localStorage.setItem("elysium_save_signature", data.signature);
|
localStorage.setItem("elysium_save_signature", data.signature);
|
||||||
@@ -3796,6 +3824,7 @@ export const GameProvider = ({
|
|||||||
formatInteger,
|
formatInteger,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
goddessBattleResult,
|
goddessBattleResult,
|
||||||
|
goddessPreview,
|
||||||
handleClick,
|
handleClick,
|
||||||
inGuild,
|
inGuild,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -3841,6 +3870,7 @@ export const GameProvider = ({
|
|||||||
unlockedCodexEntryIds,
|
unlockedCodexEntryIds,
|
||||||
unlockedStoryChapterIds,
|
unlockedStoryChapterIds,
|
||||||
vampireBattleResult,
|
vampireBattleResult,
|
||||||
|
vampirePreview,
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
apotheosis,
|
apotheosis,
|
||||||
@@ -3907,6 +3937,7 @@ export const GameProvider = ({
|
|||||||
formatInteger,
|
formatInteger,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
goddessBattleResult,
|
goddessBattleResult,
|
||||||
|
goddessPreview,
|
||||||
handleClick,
|
handleClick,
|
||||||
inGuild,
|
inGuild,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -3951,6 +3982,7 @@ export const GameProvider = ({
|
|||||||
unlockedCodexEntryIds,
|
unlockedCodexEntryIds,
|
||||||
unlockedStoryChapterIds,
|
unlockedStoryChapterIds,
|
||||||
vampireBattleResult,
|
vampireBattleResult,
|
||||||
|
vampirePreview,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5415,3 +5415,29 @@ body.vampire-mode {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===================== EXPANSION COMING SOON ===================== */
|
||||||
|
.expansion-coming-soon {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--colour-surface),
|
||||||
|
var(--colour-surface-2)
|
||||||
|
);
|
||||||
|
border: 2px solid var(--colour-accent);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
color: var(--colour-accent-light);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== EXPANSION PREVIEW ===================== */
|
||||||
|
.expansion-preview button,
|
||||||
|
.expansion-preview input,
|
||||||
|
.expansion-preview select {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.4;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import type {
|
|||||||
EquipmentType,
|
EquipmentType,
|
||||||
} from "./equipment.js";
|
} from "./equipment.js";
|
||||||
import type { GameState } from "./gameState.js";
|
import type { GameState } from "./gameState.js";
|
||||||
|
import type { GoddessState } from "./goddessState.js";
|
||||||
import type { Player } from "./player.js";
|
import type { Player } from "./player.js";
|
||||||
import type { ProfileSettings } from "./profileSettings.js";
|
import type { ProfileSettings } from "./profileSettings.js";
|
||||||
import type { CompletedChapter } from "./story.js";
|
import type { CompletedChapter } from "./story.js";
|
||||||
|
import type { VampireState } from "./vampireState.js";
|
||||||
|
|
||||||
interface AuthResponse {
|
interface AuthResponse {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -114,6 +116,14 @@ interface LoadResponse {
|
|||||||
* The current expected schema version from the server.
|
* The current expected schema version from the server.
|
||||||
*/
|
*/
|
||||||
currentSchemaVersion: number;
|
currentSchemaVersion: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial expansion states for preview display — never saved to game state.
|
||||||
|
*/
|
||||||
|
expansionPreview: {
|
||||||
|
goddess: GoddessState;
|
||||||
|
vampire: VampireState;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BossChallengeRequest {
|
interface BossChallengeRequest {
|
||||||
|
|||||||
Reference in New Issue
Block a user