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 the levelup flash state.
  • The rendered DOM: a button.hub-planet-progress-sticker containing hub-planet-progress-track, hub-planet-progress-fill, and hub-planet-progress-badge (with optional notif-badge exclamation).

READS FROM

  • usePlanetProgressStore selectors: getXp(planetId), getLevel(planetId), hasAnyClaim(planetId).
  • getXpProgress(planetId, xp) from @starship-survivors/data/planet-progression — returns per-level progress with a pct field used for fill width.
  • PlanetId type from @starship-survivors/data/planet-config.
  • Props: planetId: PlanetId, onOpenTrack: () => void.

PUSHES TO

  • Parent component via onOpenTrack() callback fired on button click (after stopPropagation).
  • 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() then onOpenTrack().
  • Level-up detection: useEffect keyed on level compares level to prevLevelRef.current; if greater, sets flash = 'levelup' and schedules a clear after 600 ms.
  • hasAnyClaim(planetId) true renders a notif-badge exclamation inside the badge.
  • aria-label exposes 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.
  • prevLevelRef is updated unconditionally at the top of the effect before comparison, so the next render’s comparison reflects the latest seen level.
  • The flash union 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.