import type { ExploreCollectResponse } from "@elysium/types"; import { useState } from "react"; import { useGame } from "../../context/GameContext.js"; import { EXPLORATION_AREAS } from "../../data/explorations.js"; import { ZoneSelector } from "./ZoneSelector.js"; const formatDuration = (seconds: number): string => { if (seconds >= 86400) { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); return hours > 0 ? `${days}d ${hours}h` : `${days}d`; } if (seconds >= 3600) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`; if (seconds >= 60) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`; return `${seconds}s`; }; const timeRemaining = (startedAt: number, durationSeconds: number): number => { const elapsed = (Date.now() - startedAt) / 1000; return Math.max(0, durationSeconds - elapsed); }; interface CollectResult { areaId: string; response: ExploreCollectResponse; } export const ExplorationPanel = (): React.JSX.Element => { const { state, startExploration, collectExploration, formatNumber } = useGame(); const [activeZoneId, setActiveZoneId] = useState("verdant_vale"); const [pendingAreaId, setPendingAreaId] = useState(null); const [lastResult, setLastResult] = useState(null); if (!state) return

Loading...

; const zones = state.zones ?? []; const explorationState = state.exploration; const zoneAreas = EXPLORATION_AREAS.filter((a) => a.zoneId === activeZoneId); const hasActiveExploration = explorationState?.areas.some((a) => a.status === "in_progress") ?? false; const handleStart = async (areaId: string): Promise => { setPendingAreaId(areaId); try { await startExploration(areaId); } finally { setPendingAreaId(null); } }; const handleCollect = async (areaId: string): Promise => { setPendingAreaId(areaId); try { const result = await collectExploration(areaId); setLastResult({ areaId, response: result }); } finally { setPendingAreaId(null); } }; return (

πŸ—ΊοΈ Exploration

{lastResult && (
{lastResult.response.foundNothing ? (

{lastResult.response.nothingMessage}

) : ( <> {lastResult.response.event && (

{lastResult.response.event.text}

)}
{(lastResult.response.event?.goldChange ?? 0) !== 0 && ( 0 ? "" : "negative"}`}> πŸͺ™ {(lastResult.response.event?.goldChange ?? 0) > 0 ? "+" : ""}{formatNumber(lastResult.response.event?.goldChange ?? 0)} gold )} {(lastResult.response.event?.essenceChange ?? 0) > 0 && ( ✨ +{formatNumber(lastResult.response.event?.essenceChange ?? 0)} essence )} {lastResult.response.event?.materialGained && ( πŸ“¦ +{lastResult.response.event.materialGained.quantity} {lastResult.response.event.materialGained.materialId.replace(/_/g, " ")} (event) )} {lastResult.response.materialsFound.map((m) => ( πŸ“¦ +{m.quantity} {m.materialId.replace(/_/g, " ")} ))}
)}
)} { setActiveZoneId(id); setLastResult(null); }} />
{zoneAreas.map((area) => { const areaState = explorationState?.areas.find((a) => a.id === area.id); const status = areaState?.status ?? "locked"; const startedAt = areaState?.startedAt ?? 0; const isReady = status === "in_progress" && timeRemaining(startedAt, area.durationSeconds) <= 0; const isPending = pendingAreaId === area.id; return (

{area.name} {areaState?.completedOnce && πŸ“–}

{area.description}

⏱️ {formatDuration(area.durationSeconds)}
{status === "locked" && ( πŸ”’ Locked )} {status === "available" && ( )} {status === "in_progress" && !isReady && ( ⏳ {formatDuration(Math.ceil(timeRemaining(startedAt, area.durationSeconds)))} remaining )} {(status === "in_progress" && isReady) && ( )}
); })} {zoneAreas.length === 0 && (

No exploration areas in this zone.

)}
); };