PURPOSE
Modal popover that renders the full five-tier ladder for a single artifact. Each row shows the tier color band, tier name, the artifact’s ability text at that runtime tier, and the per-tier flat stat bonus. Used to let the player inspect what an artifact does at every tier from a single place.
OWNS
- The modal/portal DOM tree (backdrop, framed panel, close button, header, tier row list).
- The Escape-to-close keyboard handler (registered on
windowfor the lifetime of the popover). - The construction of the five tier-row view models (runtime tier 0 to
ARTIFACT_TIER_MAX).
READS FROM
@starship-survivors/data/artifacts:ARTIFACT_MAP— to look up the artifact definition byartifactId.ARTIFACT_TIER_COLOR_BY_IDX— per-tier color band.ARTIFACT_TIER_NAMES— tier display names.ARTIFACT_TIER_MAX— count of runtime tiers minus one.FLAT_BONUS_PCT_BY_TIER— per-tier flat bonus percentage (10/20/30/40/50).getArtifactFlatBonus(artifactId)— returns the flat-bonus descriptor (withlabel) for this artifact.getTierLabelAt(def, runtimeTier)— returns the artifact’s ability text at the given runtime tier.
- Props:
artifactId(string),onClose(callback).
PUSHES TO
- Invokes the
onCloseprop on backdrop click, X button click, and Escape key. - Mounts via
createPortaltodocument.body(escapes any parent clipping such as the hub’s portrait container).
DOES NOT
- Does not mutate any store or persistent state.
- Does not read from Zustand stores, the run loop, or telemetry.
- Does not perform network or Supabase calls.
- Does not handle equipping, upgrading, or any artifact gameplay logic.
- Does not animate or use timers beyond the keydown listener.
- Renders nothing (
return null) if the artifact definition or flat-bonus descriptor is missing.
Signals
- Click on the outer backdrop →
onClose(). - Click on the inner panel →
e.stopPropagation()(suppresses backdrop close). - Click on the X button →
onClose(). keydownEscape onwindow→onClose().
Entry points
- Default export: named export
ArtifactInfoPopover({ artifactId, onClose }). - Rendered by the ships screen when the player taps an artifact for details. Parent passes the artifact id and an
onClosecallback that clears its own selection state.
Pattern notes
- Portal-to-body pattern (
createPortal(..., document.body)) is used specifically to escape the hub’s portrait clipping; replicate this when another modal must overlay clipped containers. - Tier rows are built by mapping over
Array.from({ length: ARTIFACT_TIER_MAX + 1 }), ordered common at top to legendary at bottom; the count is data-driven viaARTIFACT_TIER_MAX. - Per-tier color is sourced from a single
ARTIFACT_TIER_COLOR_BY_IDXtable and applied to both the left border band and the tier-name/bonus text — keeps tier coloring consistent across the row. - Bonus label uses
flat.label.toUpperCase()fromgetArtifactFlatBonus, so the displayed stat name follows the artifact data table rather than a hardcoded string. - Inline styles only (no CSS module). Fonts are
'Cal Sans'for labels/eyebrows and'Space Grotesk'for headings and body text. Accent color#4fd6c7and dark background#05050cmatch the hub modal style. - Escape handler is registered and torn down in a single
useEffectkeyed ononClose, avoiding listener leaks. - Early return after the
useEffecthook keeps hook order stable across renders.