theme.ts

Theme, color palette, rarity colors, and composable VFX trait table. Ported 1:1 from the legacy 01-config.js. Pure data module — no runtime side effects. Holds the single source of truth for raw color tokens (SP), semantic UI tokens (THEME), item rarity colors (RARITY_COLORS), and visual-effect trait recipes (VFX_TRAITS), plus one formatNum utility.

River of Hate (Styx) — Color governance

  • SP is raw tokens. Do not consume SP.* directly from UI; route through THEME.* semantic roles so palette swaps don’t require grepping the codebase.
  • THEME.bg is SP.navy which is 'transparent' — the canvas/nebula background is what shows through, not a solid color. Setting an opaque background anywhere upstream breaks the nebula reveal.
  • THEME.accent is teal (was green pre-port). Anywhere that still expects “green accent” is stale.
  • THEME.accentOrange is reserved for action CTAs (LAUNCH-class buttons). Don’t bleed orange into informational UI.
  • THEME.danger (#ff3344) is hardcoded — the only non-SP color in THEME. If a second danger shade is needed, lift it into SP first.
  • Rarity colors are exported as both RGB triplets and hex. Canvas particle code uses r/g/b; CSS uses hex. Keep both in sync if changing a rarity color.
  • VFX_TRAITS is a sparse heterogeneous record — VfxTrait declares every possible field as optional. Adding a new trait type with a new field requires extending the interface, not just the table.
  • colorKey: null in a VFX trait signals “inherit from caller” (rarity, faction, etc). colorKey as a string is a named lookup the consumer must resolve.
  • formatNum thresholds are asymmetric: <10K shows one decimal (“1.5K”), >=10K shows none (“12K”), >=1M shows one decimal (“1.2M”). Do not “normalize” — the bands are deliberate for HUD density.

Exports

SP (const)

Raw color token bag. Four families:

FamilyKeysNotes
Tealteal, tealBright, tealDim, tealGlow, tealTrackPrimary brand accent. tealGlow/tealTrack are pre-baked rgba.
Orangeorange, orangeHot, orangeGlow, peachAction/focus/LAUNCH. orangeGlow is pre-baked rgba at 0.30 alpha.
Navynavy, navyCard, navyMid, navyBordernavy is 'transparent', not a color — nebula bg shows through.
Textstarlight, white, dim, dimMorestarlight (#fff8d0) is the primary warm text; white (#e4f4ff) is cool secondary.

THEME (const)

Semantic design tokens — UI roles mapped onto SP. Keys: bg, surface, surfaceLight, border, text, textDim, accent, accentDim, accentOrange, gold, danger, heading, font, mono. Font stacks live here too: heading uses Cal Sans / Impact; body and mono both use Space Grotesk (mono falls back to Georgia).

RarityColor (interface)

{ r: number; g: number; b: number; hex: string; name: string }. RGB for canvas, hex for CSS, name for UI labels.

RARITY_COLORS (const)

KeyHexRGBDisplay name
green#50ff7880, 255, 120Common
blue#50a0ff80, 160, 255Rare
epic#b450ff180, 80, 255Epic
legend#ffd228255, 210, 40Legendary

Note key green maps to display name Common and legend maps to Legendary — internal keys are color-coded, not tier-named.

VfxTrait (interface)

Single shape covering all VFX trait variants. All fields except type are optional. Possible fields: radiusMult, alpha, colorKey, pulse, freq, amp, slots, color, width, rate, spread, heatMod, buildTime, burstRadiusMult, count, duration, symbol, radius.

VFX_TRAITS (const)

Recipe table for composable visual effects. 16 entries:

KeytypeNotable params
glowglowradiusMult 1.5, alpha 0.40, colorKey inherits
ringringradiusMult 2.0, alpha 0.30, pulsing
wigglewigglefreq 8, amp 3
engine_trailtrail5 slots, color #44aaff, width 3
exhaustexhaustrate 20, color #88aaff
spark_emitsparksrate 8, spread 30, color #ffcc44
heat_glowglowradiusMult 2.0, colorKey heat, heatMod: true
distortiondistortionradiusMult 1.2
charge_upchargebuildTime 1.5s, burstRadiusMult 3, color #ff4444
death_burstburstcount 20, spread 120, inherited color
shield_pulseshieldalpha 0.25, pulsing, color #44aaff
crit_flashflashduration 0.08s, white
boss_auraauraradiusMult 3.0, pulsing, color #aa00ff
elite_markmarksymbol (◆), color #ffcc44
warp_ripplerippleradius 80, duration 0.4s, color #44ffaa
pickup_glowglowradiusMult 1.8, alpha 0.50, pulsing, inherited color

formatNum(n: number): string

Compact number formatter for HUD/labels:

  • n < 1000 → bare integer string
  • 1000 ≤ n < 100001.5K (one decimal)
  • 10000 ≤ n < 1_000_00012K (zero decimal)
  • n ≥ 1_000_0001.2M (one decimal)

Negatives and floats below 1000 are passed through String(n) without truncation.

Dependencies

None. Pure constants + one pure function. Consumed broadly across UI, canvas, VFX, and HUD code.

EXTRACT-CANDIDATE

  • VFX trait table (VFX_TRAITS) likely belongs in its own data/vfx-traits.ts module — it has its own interface (VfxTrait), its own shape, and is logically separate from color/theme concerns. Co-located here only because of the legacy 1:1 port.
  • formatNum is a utility, not theme data. Move to src/starship-survivors/util/format.ts (or wherever a util barrel lives) — its presence in theme.ts is incidental.
  • RARITY_COLORS could move to data/rarity.ts if rarity metadata grows beyond color (drop weights, naming overrides, sort order). Today it’s small enough to stay.
  • The double representation (r/g/b + hex) in RarityColor invites drift. Consider deriving one from the other at module load instead of hand-maintaining both.