Weather ID System Design

Refactor live-window to use OpenWeatherMap weather condition IDs (200-804) instead of icon codes for granular visual control.

Decisions

  • Mapping architecture: Flat config map — one WEATHER_EFFECTS entry per weather ID (~50 entries)
  • Override API: override-weather accepts numeric weather IDs (e.g., "615") instead of icon codes
  • Mixed precipitation: Dual particle layers — render two independent precipitation types simultaneously
  • Atmosphere effects: Distinct color + density tiers for each 7xx condition
  • Playground: Grouped <optgroup> dropdown with all conditions; intensity dropdown removed

API Changes

Add id (numeric weather condition code) to the API proxy response in api/src/proxy.ts.

{ id: 615, main: "Snow", description: "light rain and snow", icon: "13d", temp: 2, sunrise: ..., sunset: ... }

Frontend types WeatherCurrent and WeatherInfo gain id: number | null. The icon field is kept for day/night detection.

override-weather-description attribute is removed (intensity is baked into weather IDs). override-weather changes from icon codes to numeric IDs.

New Types

interface WeatherEffectConfig {
clouds: "none" | "light" | "medium" | "heavy";
precip: PrecipLayer[];
lightning: boolean;
atmosphere: AtmosphereConfig | null;
snowAccumulation: boolean;
}
interface PrecipLayer {
type: string; // key into PRECIP_CONFIG
intensityScale: number; // multiplier on base particle count
}
interface AtmosphereConfig {
color: string; // hex
opacity: number; // 0-1
layers: number; // 1-3
}

Precipitation Configs

Keep existing: lightRain, rain, thunderstorm, snow, sleet

New types:

  • drizzle — smaller drops, slower, lower opacity
  • showerRain — slightly faster/more intense than rain
  • freezingRain — blue-white drops, slightly slower (icy feel)
  • lightSnow — fewer, smaller flakes
  • heavySnow — more, larger flakes
  • showerSnow — fast-falling dense snow

Atmosphere Tiers

ConditionColorOpacityLayersFeel
Mist (701)#c8c8c8 grey0.152Light, translucent
Fog (741)#b0b0b0 grey0.353Dense, obscuring
Smoke (711)#8b7355 brown-grey0.33Warm, heavy
Haze (721)#d4c89a warm yellow-grey0.22Warm, diffuse
Dust (761) / Sand (751)#c4a86a tan0.252Sandy, warm
Dust whirls (731)#c4a86a tan0.353Dense, swirling
Volcanic ash (762)#555555 dark grey0.43Dark, oppressive
Squalls (771)#888888 grey0.33Grey, turbulent
Tornado (781)#666666 dark grey0.453Very dark, dense

Full Weather ID Mapping

2xx: Thunderstorm

IDCloudsPrecipLightning
200heavy[lightRain × 0.6]yes
201heavy[rain × 1.0]yes
202heavy[rain × 1.4]yes
210heavy[]yes
211heavy[]yes
212heavy[]yes
221heavy[]yes
230heavy[drizzle × 0.6]yes
231heavy[drizzle × 1.0]yes
232heavy[drizzle × 1.4]yes

3xx: Drizzle

IDCloudsPrecip
300medium[drizzle × 0.6]
301medium[drizzle × 1.0]
302medium[drizzle × 1.4]
310medium[drizzle × 0.6, lightRain × 0.4]
311medium[drizzle × 0.7, lightRain × 0.7]
312medium[drizzle × 1.0, rain × 0.7]
313medium[showerRain × 0.7, drizzle × 0.5]
314heavy[showerRain × 1.2, drizzle × 0.6]
321medium[drizzle × 1.2]

5xx: Rain

IDCloudsPrecipNotes
500medium[lightRain × 0.6]
501medium[rain × 1.0]
502heavy[rain × 1.4]
503heavy[rain × 1.6]
504heavy[rain × 1.8]
511heavy[freezingRain × 1.0]+ snow accumulation
520medium[showerRain × 0.6]
521medium[showerRain × 1.0]
522heavy[showerRain × 1.4]
531medium[showerRain × 1.0]

6xx: Snow

IDCloudsPrecipAccumulation
600medium[lightSnow × 0.6]yes
601medium[snow × 1.0]yes
602heavy[heavySnow × 1.4]yes
611medium[sleet × 1.0]no
612medium[sleet × 0.6]no
613medium[sleet × 1.2]no
615medium[lightRain × 0.5, lightSnow × 0.5]yes
616heavy[rain × 0.7, snow × 0.7]yes
620medium[showerSnow × 0.6]yes
621medium[showerSnow × 1.0]yes
622heavy[showerSnow × 1.4]yes

7xx: Atmosphere

IDCloudsAtmosphere config key
701nonemist
711nonesmoke
721nonehaze
731nonedustWhirls
741nonefog
751nonedust
761nonedust
762nonevolcanicAsh
771heavysqualls
781heavytornado

800+: Clear & Clouds

IDClouds
800none
801light
802medium
803heavy
804heavy

Rendering Changes

WeatherLayer.update() changes from icon-based to ID-based lookup:

  1. Clouds: config.clouds level → render appropriate cloud divs (none/light/medium/heavy)
  2. Precipitation: Loop over config.precip[] — each layer gets a separate .droplets container with its own particle config and intensity
  3. Atmosphere: Render .atmosphere-layer divs with dynamic background and opacity inline styles
  4. Lightning: Rendered when config.lightning === true
  5. Snow accumulation: Rendered when config.snowAccumulation === true

CSS Renames

  • .mist.atmosphere-layer
  • .mist-lg.atmosphere-lg
  • .mist-md.atmosphere-md
  • .mist-sm.atmosphere-sm
  • mist-roll animation → atmosphere-roll
  • .weather-{icon} CSS classes → .weather-{id} (for thunderstorm dark clouds, etc.)

Removed

  • ICON_WEATHER_MAP — replaced by WEATHER_EFFECTS
  • intensityMultiplier() function — intensity baked into weather ID configs
  • override-weather-description attribute — no longer needed

Playground Updates

  • Replace condition dropdown with <optgroup>-based select showing all 50+ conditions grouped by category
  • Remove intensity dropdown (intensity inherent in weather IDs)
  • override-weather attribute set to numeric ID (e.g., "615")
  • Remove override-weather-description attribute usage

Test Impact

  • weather.test.ts: Rewrite to test ID-based lookup, dual precipitation layers, atmosphere rendering, new precip types
  • overrides.test.ts: Update override-weather tests for numeric IDs, remove override-weather-description tests
  • helpers.ts: Update makeTestState() for new WeatherInfo.id field
  • API tests: Add id field to weather response tests