PURPOSE

Ships-screen tab where the player picks the starting alien artifact for the next run. Renders a hero card for the currently selected artifact plus a scrollable grid of every artifact in the game, each card showing one of four collection states (selected / unlocked / seen / hidden). Built on the medical-UI design system with the artifact’s “alien aqua” identity folded into the medical cyan tech accent. Card layout mirrors the SHIPS tab.

OWNS

  • ArtifactsTab exported tab component.
  • ArtifactCard internal card component with four visual states (selected, unlocked, seen, hidden) keyed off the CardState union.
  • ArtifactGlyph internal circular monogram glyph (cyan-tech accent when unlocked, recessed-well surface when locked).
  • CardState local type ('selected' | 'unlocked' | 'seen' | 'hidden').
  • ARTIFACT_ACTIVE palette object — hex literals mirroring --med-accent-bright / --med-accent / --med-accent-deep so JS-side rgba() / box-shadow strings can stay literal without runtime CSS reads.
  • headerStripStyle module-level CSS object for the inventory header strip.
  • popoverId local state — id of the artifact whose info popover is open, or null when closed.
  • Sort policy: selected first, then unlocked alphabetical, then seen-but-locked alphabetical, then hidden alphabetical.
  • Card-click routing: unlocked selects, seen opens popover, hidden and selected no-op (the hero (i) button reaches the popover for the selected artifact).

READS FROM

  • @starship-survivors/data/artifactsARTIFACT_DEFS, ArtifactDef type, ARTIFACT_TIER_COLOR_BY_IDX, ARTIFACT_TIER_NAMES.
  • @starship-survivors/stores/artifactUnlocksStore via useArtifactUnlocksStore — selectors unlockedIds, selectedId, bestTier, and the select action.
  • ./ArtifactInfoPopover — sibling component rendered when popoverId is non-null.
  • useMemo, useState from React.

PUSHES TO

  • useArtifactUnlocksStore.select(id) when an unlocked card is tapped — sets the starting artifact for the next run.
  • Internal setPopoverId state setter — opens the popover for the hero card’s (i) button or for any seen-but-not-unlocked card.
  • ArtifactInfoPopover via artifactId + onClose props.

DOES NOT

  • Does not fetch, persist, or unlock artifacts. Unlock state is owned by artifactUnlocksStore and seeded / updated elsewhere (default starters seeded as legendary best-tier, others promoted to unlocked when brought to legendary in a run).
  • Does not render the tier ladder, ability text, or flat-stat bonus details — those live in ArtifactInfoPopover.
  • Does not own the artifact catalog. ARTIFACT_DEFS is a read-only import.
  • Does not handle the tab-bar chrome that hosts this tab; the parent ships screen owns tab switching.
  • Does not read CSS variables at runtime; uses literal hex values in ARTIFACT_ACTIVE that intentionally mirror the medical accent tokens.
  • Does not animate beyond a hover/active translateY(-2px) lift and a box-shadow transition on the selected card.

Signals

  • select(id) — store action fired on tap of an unlocked card.
  • popoverId state transitions — null → id opens popover (hero (i) button or seen card), id → null on ArtifactInfoPopover.onClose.
  • Derived state per card (selected | unlocked | seen | hidden) computed from selectedId, unlockedIds, and bestTier[def.id].

Entry points

  • ArtifactsTab() — named export, mounted as one tab inside the ships screen.
  • ArtifactCard({ def, state, bestTier, onClick }) — internal, one per artifact in the grid.
  • ArtifactGlyph({ icon, size, unlocked }) — internal, used by both the hero card (size 84) and each grid card (size 56).

Pattern notes

  • Four-state card model: state is derived once per render from store selectors (isSelected, isUnlocked, best >= 0) — no separate flag flow.
  • Sorting is memoized on [unlockedIds, bestTier]; the selected artifact is hoisted to the hero card and remains in the grid (its grid card shows the selected chip and lifted glow).
  • Surface palette splits between visible and hidden: visible cards use a linear-gradient over --med-panel-raised with the cyan accent fill; hidden cards use --med-panel-recessed. Selected/unlocked share the cyan border; seen and hidden share the muted --med-border.
  • Tier-color badges on each card pull from ARTIFACT_TIER_COLOR_BY_IDX / ARTIFACT_TIER_NAMES. Tier colors are preserved as semantic identity per ADR 20260513-001 exception (they bypass the otherwise-strict medical palette).
  • “Alien aqua” identity is implemented as the cyan tech accent — ARTIFACT_ACTIVE.edge / .edgeDim / .fillTop / .fill / .glow. Hero card layers a soft radial cyan wash over the white medical panel.
  • Hidden cards are disabled with cursor: 'not-allowed', a padlock glyph, and an “NEVER SEEN / UNLOCK BY RUNNING” caption — no name shown (renders ???).
  • Accessibility: per-card aria-label reflects state (“Select X” / “View X details” / “X (locked)”); selected card sets aria-pressed; hero (i) button carries an explicit aria-label.
  • Grid uses CSS repeat(auto-fill, minmax(108px, 1fr)) for responsive sizing on phone widths.
  • Layout matches the SHIPS tab (hero card → INVENTORY header → grid) to keep the two collection tabs visually parallel.
  • Tick 99 medical-UI rollout, ship tabs PR 2/4.