metagame/components

PURPOSE — Shared React components used across the metagame screens (hub, shop, ships, profile, collection, overlays, modals). The screen layer composes these into pages; this layer owns the reusable chrome, the medical-UI panel/modal primitives, the rarity-card visual kit, the live nebula backdrop, and the cross-screen overlays (challenges, planet track, reward popup, update banner, feedback modal, welcome / claim-account modals).

OWNS

  • The shared metagame chrome (top bar with account pill, build-version stamp, currency display; bottom nav with SHOP / PLAY / SHIPS tabs; an overlay-mode bottom bar with BACK + optional right-aligned tabs).
  • The medical-UI primitives: card / flat / doors panel variants, the fullscreen scrim modal wrapper, the size-aware door-open audio cue, the one-time module-level haptic listener that pulses on primary-button presses.
  • The rarity-card visual kit: rarity badge, trimmed ship sprite (uniform alpha-bbox crop drawn to a DPR-correct canvas with a sprite memo cache), collectible card (sprite + hull name + star/XP bar + locked overlay + selected-state pulse keyframes injected once at module scope).
  • The live WebGL nebula backdrop wrapper around the nebula engine singleton (RAF loop, resize observer, post-FX subscription, blackout-overlay fade).
  • The decorative red-orange sun layer with drifting glow lobes (currently hidden).
  • The challenge popover (tier KPI claim row + per-planet challenge card list grouped by category and sorted by rarity, with live progress from challenge-store raw state).
  • The planet progress sticker on the hub (per-level XP fill + level badge + level-up gold flash).
  • The planet track viewer (fullscreen scrollable reward track with per-segment progress bar, glass-orb level badge, rarity-colored reward card, locked-card variant, claim handler, reward-reveal popup with auto-dismiss).
  • The leaderboard table wrapper (rank / name / kills / weapon icons / highest tier, self-row highlight).
  • The reward popup queue renderer (auto-dismiss timer, tap-to-skip, inline fade/pop keyframes).
  • The update-detection module (version.txt polling, shared subscriber list, hub-mounted slide-up banner, route-gated fullscreen overlay).
  • The feedback modal (canvas screenshot during gameplay or html2canvas DOM capture on menus, textarea, screenshot preview well, mission pause/resume bridge via a module-level active-mission ref).
  • The welcome modal (callsign entry after magic-link claim) and claim-account modal (email entry for magic link).
  • The onboarding shell (chrome-free centered layout for the prologue).

READS FROM

  • metagame/stores (player profile, wallet, display overrides, reward queue, challenge completions + per-planet stats, planet progress + claims, tier highest + next-milestone + can-claim, inventory star/XP).
  • metagame/services (supabase client for direct inserts, invokeRpc for claim / leaderboard RPCs, profileService for display-name updates, anchor-registry for fly-to anchor refs).
  • engine/core/config for the build-version stamp polled against version.txt.
  • engine/rendering/nebula-engine for the WebGL nebula singleton lifecycle.
  • engine/rendering/post-fx-store for the live post-FX uniform bundle.
  • engine/rendering/ships-v4-loader for ship sprite paths.
  • engine/rendering/card-theme for rarity gradients, badge letters/fills, accent colors, effect tiers, card shadow.
  • engine/vfx/juice for the panel-open audio cue.
  • engine/bridge for the MissionHandle type used by the feedback pause bridge.
  • data/ships for ship defs, rarity colors, display-name helper.
  • data/ship-progression for XP-to-next-star math.
  • data/challenges for per-planet challenge defs, rarity ordering, and the rarity/category enums.
  • data/planet-config for planet metadata and the planet-id type.
  • data/planet-progression for the planet track, per-level XP math, and reward-rarity colors.
  • data/weapon-icons for the leaderboard weapon-icon paths and emoji fallback.
  • React Router (useNavigate, useLocation) for nav-tab active state and account-pill routing.
  • window.location / version.txt for the update-poll loop.
  • html2canvas for menu-screen DOM screenshots.

PUSHES TO

  • metagame/stores (wallet snapshot replace on tier-milestone claim, local claim records on tier-milestone + planet-reward claims, welcome-dismiss, reward-queue dismiss).
  • metagame/services (Supabase player_feedback insert; RPC dispatches for claim_tier_milestone, claim_planet_reward, get_tier_leaderboard; magic-link send via claimWithEmail; display-name update).
  • engine/rendering/nebula-engine (init, resize, render, destroy across mount lifecycle).
  • engine/vfx/juice (size-aware panel_open cue on door-variant panel mount).
  • React Router (programmatic navigation from the nav tabs and account pill).
  • navigator.vibrate (15ms pulse on every .med-btn-primary pointer-down).
  • window.location.reload (forced refresh when an update is available).
  • Window events (ss:open-feedback listener, ss:mission-changed dispatch on bridge writes).

DOES NOT

  • Define screens, routes, or page-level orchestration — screens compose these components and own per-page state and side effects.
  • Mutate game-engine state directly. The feedback bridge calls pause / resume on a passed mission handle; nothing else reaches into the engine.
  • Compute progression curves, rarity rolls, claim eligibility, or reward contents — those live in data/* and the stores; components only render the resolved values and dispatch claim RPCs.
  • Render canvas gameplay, HUD, or in-game popups — only metagame-side React.
  • Persist user data locally beyond the stores it writes to; no direct localStorage writes from this layer.
  • Own audio routing, signal buses, or telemetry — talks to Juice.fire for the panel cue and to Supabase for feedback, nothing else.
  • Resolve auth, session, or OAuth flows beyond invoking the player-store and profile-service methods that handle them.

Signals fired / Signals watched

  • Listens for window event ss:open-feedback (canvas ”?” tap → open feedback modal).
  • Dispatches window event ss:mission-changed whenever the active-mission bridge ref is set or cleared.
  • Subscribes to the post-FX store for live uniform updates on the nebula loop.
  • No engine-signal traffic; everything else is React props, store selectors, and direct method calls.

Entry points

  • BottomNav — main-menu bottom tabs (SHOP / PLAY / SHIPS) with active-pill highlight from the current route.
  • OverlayBottomBar — overlay-mode bottom bar with a wide BACK button and optional right-aligned tab buttons.
  • V32Shell — shared metagame chrome (top bar + scrollable main stage + bottom nav), with a chrome-hide mode for fullscreen overlays.
  • OnboardingShell — chrome-free centered layout used during the prologue beats.
  • MedPanel — medical-UI panel primitive in card / flat / doors variants; door variant plays a size-aware open cue.
  • MedPanelModal — fullscreen scrim wrapper for centered modal content with optional scrim-dismiss.
  • RarityBadge — circular letter badge that dangles off the corner of a rarity card.
  • TrimmedShipImage — canvas-rendered ship sprite with a shared uniform alpha-bbox crop and an optional zoom factor.
  • CollectibleCard — rarity-frame ship card with sprite, hull name, star/XP bar, locked overlay, and selected-state white pulse.
  • NebulaBackground — live WebGL nebula backdrop with archetype prop and a blackout-overlay opacity prop.
  • SunEffect — red-orange sun with drifting glow lobes (currently rendered hidden).
  • ChallengePopover — fullscreen popover with a tier-milestone claim row plus per-planet challenge card list.
  • PlanetProgressBar — compact hub-screen XP sticker with per-level fill, level badge, level-up flash, and an unclaimed-reward indicator.
  • PlanetTrackViewer — fullscreen scrollable reward track with claim flow and reward-reveal popup.
  • LeaderboardWrapper — table of top players by highest tier, with self-row highlight and weapon-icon row.
  • RewardPopup — front-of-queue reward overlay with auto-dismiss and tap-to-skip.
  • useUpdateAvailable — hook returning true once a newer build is published.
  • HubUpdateBanner — slide-up bottom banner on the hub that reloads on tap.
  • UpdateBanner — fullscreen update overlay shown only on safe menu routes.
  • FeedbackFAB — feedback modal triggered by the canvas ”?” event, with pause/resume bridge.
  • setActiveMission / clearActiveMission — bridge setters used by the game screen so the feedback modal can pause/resume gameplay and grab the canvas for a screenshot.
  • WelcomeModal — callsign-entry modal shown after a successful magic-link claim.
  • ClaimAccountModal — email-entry modal that sends the magic link to upgrade a guest profile.

Pattern notes

  • Cross-cutting concerns ride on module-level state rather than props or context: the active-mission bridge ref (set by the game screen, read by the feedback modal), the update-poll subscriber list (one polling loop shared across mounts), and the haptic delegation listener (single capture-phase pointer-down listener installed once at module load).
  • The selected-state card pulse and the reward-popup fade/pop animations are injected as <style> tags on first module evaluation rather than living in a stylesheet; idempotent via an id check.
  • Ship sprites are loaded once per URL and cached in module-level maps (one for resolved images, one for in-flight promises); the canvas reads them through a ResizeObserver on the parent so card layouts repaint without a React re-render.
  • The medical-UI variants enforce a consistent shape: a <MedPanelModal> scrim wrapping a <MedPanel variant="doors"> is the shared pattern for the welcome / claim-account / feedback / update modals.
  • Optimistic claim flows write to the local store before awaiting the RPC and rely on bootstrap to reconcile any RPC failure; the reward-reveal popup fires regardless of RPC outcome.
  • Selectors that consume store maps read raw state (e.g. s.completions.has(id), s.planetStats[planetId]) and compute derived values inline, to avoid the new-object-per-call infinite loop that occurs when methods are invoked inside Zustand selectors.
  • The overlay-bottom-bar variant shares the same height, background, and hairline-border-top as the main bottom-nav so swapping into overlay mode does not shift content above it.