PURPOSE
Compact horizontal XP bar shown on HubScreen. Shows progress toward the next level (not overall progress). Animates fill via CSS transition when XP changes, and plays a gold flash plus badge pop animation on level-up. Acts as a button that opens the planet reward track.
OWNS
- Local state
flash: 'none' | 'fill' | 'levelup'driving CSS class toggles for the level-up animation. prevLevelRef(useRef) tracking the previous level value across renders to detect level-up transitions.- A
setTimeout(600 ms) that clears thelevelupflash state. - The rendered DOM: a
button.hub-planet-progress-stickercontaininghub-planet-progress-track,hub-planet-progress-fill, andhub-planet-progress-badge(with optionalnotif-badgeexclamation).
READS FROM
usePlanetProgressStoreselectors:getXp(planetId),getLevel(planetId),hasAnyClaim(planetId).getXpProgress(planetId, xp)from@starship-survivors/data/planet-progression— returns per-level progress with apctfield used for fill width.PlanetIdtype from@starship-survivors/data/planet-config.- Props:
planetId: PlanetId,onOpenTrack: () => void.
PUSHES TO
- Parent component via
onOpenTrack()callback fired on button click (afterstopPropagation). - DOM only. No store mutations, no telemetry, no network.
DOES NOT
- Does not award XP, compute level thresholds, or mutate planet progress state.
- Does not claim rewards or open the reward track itself — only signals intent via
onOpenTrack. - Does not display overall progress across all levels — only per-level fill.
- Does not handle the actual level-up audio, particles, or screen-wide effects.
- Does not render label text for the level beyond the badge number.
Signals
- Click on the button calls
e.stopPropagation()thenonOpenTrack(). - Level-up detection:
useEffectkeyed onlevelcomparesleveltoprevLevelRef.current; if greater, setsflash = 'levelup'and schedules a clear after 600 ms. hasAnyClaim(planetId)true renders anotif-badgeexclamation inside the badge.aria-labelexposes current level and tap affordance for assistive tech.
Entry points
- Exported function component
PlanetProgressBar({ planetId, onOpenTrack }). - Consumed by
HubScreen(the hub-level planet selector / progress display).
Pattern notes
- Per-level fill width is computed as
Math.max(2, fillPct * 100)%to guarantee a minimum visible sliver even at zero progress. - Animation is driven entirely by CSS — the component only toggles class names (
flash-levelup,badge-levelup); transition timing is owned by the stylesheet. prevLevelRefis updated unconditionally at the top of the effect before comparison, so the next render’s comparison reflects the latest seen level.- The
flashunion includes a'fill'variant that is declared but not currently assigned — only'none'and'levelup'are produced by the effect. - Click handler intentionally stops propagation to avoid triggering parent handlers on
HubScreen.