PURPOSE
Fullscreen scrollable reward-track overlay for a single planet. Renders the planet’s name, overall progress bar, sprite, and a vertical scrollable list of reward segments (one per track level). Each segment shows a per-card vertical progress bar with a numbered circle badge and a reward card (header bar + body + collectible square that extends off the top). Locked rewards render as grey question-mark cards. Clicking an unclaimed but unlocked collectible fires the claim RPC, optimistically marks the reward claimed locally, and triggers a fullscreen reveal popup. Auto-dismisses the reveal after 2.5 s.
OWNS
- The
PlanetTrackViewercomponent (default export-style named export) — overlay shell, scroll container, top/title/sprite area, divider, scroll-hint triangle, and reveal-popup mount point. - The internal
TrackSegmentcomponent — per-row layout for one reward level (vertical bar + circle badge + card or locked card). - The internal
RewardRevealPopupcomponent — fullscreen celebration on claim with auto-dismiss timer. - Local UI state:
claimingLevel(which level is mid-claim),revealReward(which reward is being shown in the reveal popup),showScrollHint(whether the bottom triangle is visible). - The scroll-position ref
scrollRefand the scroll listener that togglesshowScrollHintbased on distance from the bottom. - Optimistic claim semantics: on RPC failure the local store is still updated and the reveal still fires; the server reconciles on next bootstrap.
- Per-card bar fill computation (
barFillPct) derived fromcurrentLevelandlevelPct. - Inline rarity styling on cards (header
bg.dark, bodybg.light, collectible--col-border/--col-darkCSS custom properties) and the reveal name’s stroked-text inline style.
READS FROM
@starship-survivors/data/planet-config—PLANETSmap andPlanetIdtype; readsplanet.nameandplanet.image.@starship-survivors/data/planet-progression—getPlanetTrack(reward list),getXpProgress(level + intra-level pct),TRACK_REWARD_RARITY(per-level rarity array),TRACK_RARITY_COLORS(rarity-to-color),TRACK_RARITY_BG(rarity-to-background pair{ dark, light }), and the typesPlanetTrackRewardandTrackRewardRarity.@starship-survivors/stores/planetProgressStore—getXp(planetId),isRewardClaimed,canClaimReward,addClaim../PlanetProgressBar— composed at the top of the panel../BottomNav—OverlayBottomBarused as the bottom bar with a BACK button../planet-track.css— all visual styling for the overlay, segments, cards, badge, scroll hint, and reveal.- Props:
planetId,onClose.
PUSHES TO
@metagame/services/supabase— callsinvokeRpc('claim_planet_reward', { p_planet_id, p_level })when the user clicks an unclaimed unlocked collectible.usePlanetProgressStore.addClaim(planetId, reward.level)— local store mutation (optimistic, runs even if the RPC throws).onClosecallback prop — invoked when theOverlayBottomBarBACK button is pressed.setRevealReward(reward)andsetRevealReward(null)— opens and closes the fullscreen reveal popup.console.error('Failed to claim planet reward:', err)on RPC failure.
DOES NOT
- Does not own or mutate XP totals; XP comes from the store via
getXp. - Does not compute rarity tables or color palettes; consumes them from
planet-progression. - Does not render the persistent main-screen progress bar — that is
PlanetProgressBar, composed here. - Does not gate clicks on locked levels; locked cards render as a separate
lockedbranch with no click handler. - Does not retry the RPC on failure — failure path is silent (console only) and relies on server reconciliation at next bootstrap.
- Does not animate the reveal explicitly; styling and any animation live in
planet-track.css. - Does not persist any UI state across mounts;
claimingLevel,revealReward, andshowScrollHintreset each time the overlay opens. - Does not handle the
onOpenTrackcallback fromPlanetProgressBar(passes a no-op since the user is already in the track view). - Does not currently use the
rarityColorvalue insideTrackSegment(it is read but not applied — onlybg.darkandbg.lightare used for card styling).
Signals
onClose: () => void(prop) — wired toOverlayBottomBar’sonBack. Closes the overlay.onClaim(reward)— passed fromPlanetTrackViewerinto eachTrackSegment; bound tohandleClaim, which performs the RPC + optimistic update + reveal trigger.- Scroll event on the inner scroll container — listened on mount, removed on unmount; recomputes
showScrollHint. setTimeout(onDismiss, 2500)inRewardRevealPopup— auto-dismisses the reveal after 2.5 s; cleared on unmount.- Click on the reveal background — also dismisses via the same
onDismiss. - Click on a collectible square —
stopPropagationthen conditionalonClaim(reward)ifcanClaim && !isClaimed && !isClaiming.
Entry points
- Named export
PlanetTrackViewer({ planetId, onClose })— the public component. - Mounted by the parent metagame screen whenever the user opens a planet’s reward track. The overlay sits above the planet hub via
.planet-track-overlay/.planet-track-panelCSS. - Internal helpers
TrackSegmentandRewardRevealPopupare not exported.
Pattern notes
- React 19 + hooks:
useState,useCallback,useRef,useEffect. Store reads use individual selectors (usePlanetProgressStore(s => s.getXp(planetId))) per Zustand 5 conventions. - Optimistic UI: local store updates and reveal popup fire regardless of RPC outcome; server is the eventual source of truth via bootstrap reconciliation.
- Inline styles are limited to dynamic rarity values (
bg.dark,bg.light, CSS custom properties on the collectible, rarity color + stroked text on the reveal name). Everything else lives inplanet-track.css. - Per-card bar fill is computed as a ternary chain: 100 for past levels,
Math.round(levelPct * 100)for the in-progress level, 0 for future levels. - The collectible square uses CSS custom properties
--col-borderand--col-darkso the stylesheet can drive border + dark-fill from rarity without per-element class explosion. - The
has-dividerclass is appended to every segment except the last (isLastflag) to draw a separator under each row. - The
OverlayBottomBaris reused here in “overlay” mode with a BACK button, matching other fullscreen overlays in the metagame. - The
PlanetProgressBaris reused with a no-oponOpenTracksince the user is already inside the track view. - The scroll-hint triangle (
▼) is a literal Unicode character in JSX, gated onshowScrollHint. - The reveal popup mounts as a sibling of
.planet-track-panel(still inside.planet-track-overlay) so its z-index can sit above the panel without re-parenting. - Lint note:
rarityColoris destructured inTrackSegmentbut never read; onlybg.darkandbg.lightreach the DOM. Safe to remove if a lint cleanup runs. - File organization uses section banners (
═══rules) to separate the main component,TrackSegment, andRewardRevealPopup.