import { lazy, Suspense } from "react";
import { barrierProtects, upcomingEncounterMechanic, useGameStore } from "../game/store";
import { HEALER_CLASSES } from "../game/healers";
import { BOSS_DEFINITIONS } from "../game/bossCatalog";
import { bossRoomFor } from "../game/bossRooms";
import { tankAuraProtects } from "../game/partyCombat";
import { BuffDraftPanel } from "./BuffDraftPanel";
const GameScene = lazy(() => import("./GameScene").then((module) => ({ default: module.GameScene })));
function CompactParty() {
const party = useGameStore((state) => state.party);
const time = useGameStore((state) => state.time);
const selected = useGameStore((state) => state.selectedMemberId);
const barrier = useGameStore((state) => state.barrier);
const tankAura = useGameStore((state) => state.partyCombat.tankAura);
const partyPositions = useGameStore((state) => state.partyPositions);
const selectMember = useGameStore((state) => state.selectMember);
const mana = useGameStore((state) => state.mana);
const maxMana = useGameStore((state) => state.maxMana);
return (
{party.map((member) => {
const health = (member.hp / member.maxHp) * 100;
const shield = (member.absorb / member.maxHp) * 100;
return (
);
})}
);
}
function CastingBar() {
const healerClassId = useGameStore((state) => state.healerClassId);
const abilityName = HEALER_CLASSES[healerClassId].abilities.mend.name;
const activeCast = useGameStore((state) => state.activeCast);
const time = useGameStore((state) => state.time);
const party = useGameStore((state) => state.party);
if (!activeCast) return null;
const duration = activeCast.completesAt - activeCast.startedAt;
const progress = Math.min(1, Math.max(0, (time - activeCast.startedAt) / duration));
const target = party.find((member) => member.id === activeCast.targetId);
return (
{abilityName}{target?.name}
{Math.max(0, activeCast.completesAt - time).toFixed(1)}s
);
}
function BossBar() {
const boss = useGameStore((state) => state.boss);
const additionalBosses = useGameStore((state) => state.additionalBosses);
const phase = useGameStore((state) => state.phase);
if (phase === "briefing") return null;
const bosses = [boss, ...additionalBosses.map((entry) => entry.boss)];
return (
1 ? "is-multi" : ""} ${bosses.length === 3 ? "is-trio" : ""}`}>
{bosses.map((entry) =>
Vault Beast{entry.name}{Math.ceil((entry.hp / entry.maxHp) * 100)}%
)}
);
}
function EncounterCallout() {
const phase = useGameStore((state) => state.phase);
const time = useGameStore((state) => state.time);
const boss = useGameStore((state) => state.boss);
const bossMotion = useGameStore((state) => state.bossMotion);
const additionalBosses = useGameStore((state) => state.additionalBosses);
if (phase !== "combat") return null;
const mechanic = upcomingEncounterMechanic({ boss, bossMotion, additionalBosses, time });
return (
{mechanic.name}
{mechanic.remaining.toFixed(1)}s
);
}
function PhaseOverlay() {
const phase = useGameStore((state) => state.phase);
const runMode = useGameStore((state) => state.runMode);
const primaryBoss = useGameStore((state) => state.boss);
const additionalBosses = useGameStore((state) => state.additionalBosses);
if (phase === "intermission") return ;
const bosses = [primaryBoss, ...additionalBosses.map((entry) => entry.boss)];
const definitions = bosses.map((boss) => BOSS_DEFINITIONS[boss.id]);
const room = bossRoomFor(primaryBoss.id);
const bossNames = bosses.map((boss) => boss.name).join(" & ");
const briefingMode = runMode === "rogue-trials"
? bosses.length === 3 ? "Rogue Trials · Trio Finale" : "Rogue Trials · Dual Round"
: bosses.length > 1 ? "Roguelike PVE · Dual Encounter" : definitions[0].trial;
if (phase === "combat") return null;
const title = phase === "briefing"
? room.name
: phase === "victory"
? `${bossNames} Broken`
: "Party Broken";
const eyebrow = phase === "briefing"
? `${briefingMode} · ${room.biome}`
: phase === "victory"
? "Encounter Complete"
: "Encounter Failed";
const copy = phase === "briefing"
? definitions.map((boss) => boss.briefing).join(" ")
: phase === "victory"
? "Five entered. Five endured."
: definitions.map((boss) => boss.failure).join(" ");
return (
✦
{eyebrow}
{title}
{copy}
{phase === "briefing" ? "Begin from lower display" : "Restart from lower display"}
);
}
function PauseOverlay({ onExit }: { onExit?: () => void }) {
const paused = useGameStore((state) => state.paused);
const selection = useGameStore((state) => state.pauseSelection);
const setPaused = useGameStore((state) => state.setPaused);
const setPauseSelection = useGameStore((state) => state.setPauseSelection);
if (!paused) return null;
const exit = () => {
setPaused(false);
onExit?.();
};
return (
Encounter suspended
Paused
Simulation, damage, and movement are stopped.
);
}
export function TopScreen({ onExit }: { onExit?: () => void }) {
const phase = useGameStore((state) => state.phase);
const bossCount = useGameStore((state) => state.additionalBosses.length + 1);
const round = useGameStore((state) => state.round);
const runMode = useGameStore((state) => state.runMode);
const setPaused = useGameStore((state) => state.setPaused);
return (
}>
{runMode !== "encounter" ? `Round ${round}` : "Objective"}{bossCount === 3 ? "Defeat trio · keep five alive" : bossCount === 2 ? "Defeat both · keep five alive" : "Keep all five alive"}
WASD Move Q / E Target 1–6 Cast
{onExit &&
}
);
}