import { content, type Location } from "@deadhead/content"; import { realToGame } from "@deadhead/sim"; import { useState, type ReactNode } from "react"; import { Link } from "react-router"; import { useClock } from "../clock/clock"; import { formatGameTime } from "../clock/GameClock"; import { Countdown } from "../components/Countdown"; import { cargoName, formatMillionKm, locationName } from "../format"; import { distanceAt } from "../game"; import { DEFAULT_CAMERA, useCamera } from "../map/camera"; import type { MapSelection } from "../map/hitTest"; import { MapCanvas } from "../map/MapCanvas"; import { modelsReachingFromEarth, upgradedShipsReachingFromEarth } from "../map/reach"; import { useLoadedPlayer } from "../state/actions"; import { Art, buttonClass, Icon } from "../ui"; const TYPE_LABELS: Record = { "atmospheric-world": "Atmospheric world", "airless-body": "Airless body", "cloud-platform": "Cloud platform", "belt-station": "Belt station", }; /** The solar system map (Phase 10). Tap a location or ship for details. */ export function MapScreen() { const state = useLoadedPlayer(); const [selection, setSelection] = useState(null); return ( // Fill the space between the header and the bottom nav (none beside the // desktop rail); the negative margin cancels the layout's scroll padding so // the page never scrolls (CR-005).

Map

{selection?.kind === "location" && ( setSelection(null)} /> )} {selection?.kind === "flight" && ( setSelection(null)} /> )}
); } function Panel({ title, art, onClose, children, }: { title: string; art?: ReactNode; onClose: () => void; children: ReactNode; }) { return (
{art}

{title}

{children}
); } /** A tapped location: details and a link to its contracts once discovered (CR-002). */ function LocationPanel({ locationId, onClose }: { locationId: string; onClose: () => void }) { const state = useLoadedPlayer(); const now = useClock((s) => s.now); const location = content.location(locationId)!; const discovered = state.locations.some((l) => l.id === locationId && l.discovered); const hasBay = state.bays.some((b) => b.locationId === locationId); const reachable = discovered ? [] : modelsReachingFromEarth(locationId, now); // Owned ships whose upgrades get them there when their model can't (CR-009). const upgraded = discovered ? [] : upgradedShipsReachingFromEarth(state.ships, locationId, now); const reachers = [ ...reachable.map((m) => m.name), ...upgraded.map((s) => `your ${s.name} (upgraded)`), ]; return ( } > {discovered ? ( <>

{TYPE_LABELS[location.type]} ·{" "} {location.id === "earth" ? "Home" : `${formatMillionKm(distanceAt("earth", location.id, now))} from Earth`}

Produces {location.cargoClassMix.produces.map(cargoName).join(", ") || "nothing"} · demands {location.cargoClassMix.demands.map(cargoName).join(", ") || "nothing"}

{hasBay ? "Your HQ is here." : "No HQ here."}

View contracts ) : ( <>

Undiscovered — deliver here to unlock.

{reachers.length > 0 ? `Reachable with: ${reachers.join(", ")}` : "No ship can reach it from Earth right now."}

)}
); } /** A tapped ship in flight: its route, pilot and ETA. */ function FlightPanel({ dispatchId, onClose }: { dispatchId: string; onClose: () => void }) { const state = useLoadedPlayer(); const dispatch = state.activeDispatches.find((d) => d.id === dispatchId); if (!dispatch) return null; const shipName = dispatch.tutorial ? "Company loaner" : (state.ships.find((s) => s.id === dispatch.shipId)?.name ?? "Ship"); const pilotName = state.pilots.find((p) => p.id === dispatch.pilotId)?.name ?? "Unknown pilot"; const shipSprite = dispatch.tutorial ? "ship-company-courier" : (content.shipModel(state.ships.find((s) => s.id === dispatch.shipId)?.modelId ?? "") ?.sprite ?? "ship-courier"); return ( } >

{locationName(dispatch.originId)} → {locationName(dispatch.destinationId)}

Pilot: {pilotName}

Arrives in{" "} {" "} ({formatGameTime(realToGame(dispatch.arrivalAt, content.config))})

); }