# Animated Background
A browser-based animated desktop background: a WebGL flight scene over
real terrain, driven by the real sun, moon, stars, and live weather.
## Features
- **WebGL flight scene** (`vendor/clouds3d.js`): raymarched cloud deck,
3D terrain mesh from real elevation data (Copernicus DEM), satellite
or street basemap, figure-eight flight path around the configured
location with a vertical bob between **15 and 35 km** flight altitude.
- **Real day/night cycle** driven by the actual sun and moon position
(SunCalc) for the configured latitude/longitude — or an accelerated
24 h simulation (`CONFIG.day`) for wall-decoration purposes.
- **Realistic night sky**: ~8700 stars from the Yale Bright Star
Catalogue (crisp GL points, size by magnitude, colour by temperature),
rotated with the sidereal rate for the site's latitude, plus a Milky
Way band and a phase-correct moon.
- **Aircraft**: low-poly jets (CC0 model) cruising the region on curved
routes between real airfields, rolling a random destination at every
take-off - sun-lit silhouettes with contrails by day, strobing position
lights by night (all parameters in `CONFIG.flights`).
- **Night lights**: animated car lights along the real motorway/road
network and pale-yellow settlement lights (OSM place nodes; bigger
towns get more and brighter dots), optionally a NASA VIIRS night-lights
satellite overlay. All faded in from dusk, density shaped by a
rush-hour curve.
- **Live weather** (Open-Meteo): current conditions + 4-day forecast
widget; the weather code selects the scene's cloud density and colour
mood (sunny / cloudy / stormy / night).
- **Fully cached, offline-capable**: basemap tiles, elevation grid,
Overpass road/place data, star catalogue and the last weather payload
live permanently in IndexedDB. Once warm, the scene renders fully offline, and a 15-minute cycle transparently retries anything that previously failed. Merged road chains and the decoded settlement list are cached as well and only recomputed when an Overpass cell is actually re-fetched.tiles.expireDays`) can refresh
basemap tiles periodically.
- **Location-aware**: set `CONFIG.location.lat/lon`; everything
(flight anchor, terrain tiles, sun/moon, weather, star rotation) is
computed from it. If the location is omitted, browser geolocation is
tried, falling back to the built-in default (London).
- Units (°C/°F), six UI languages, CSS-swappable colour themes
(Ash/Gruvbox/Nord), configurable clock format, optional debug panel.
## Run with Docker (recommended)
```sh
docker compose up --build
```
Then open .
The bundled nginx config (`default.conf`) is more than a file server: it
provides CORS proxies for the tile sources (`/esri/`, `/carto/`,
`/terrarium/`) that the scene needs for basemap and elevation data.
A plain static server without these proxies (e.g.
`python3 -m http.server 58881`) will run the UI but terrain and map
tiles will fail to load.
## Single-file build
`build.py` bundles everything into one self-contained HTML file
(`background-standalone.html`) — all scripts, the star catalogue and all
theme CSS are inlined, and theme switching keeps working:
```sh
python3 build.py # -> background-standalone.html (no sibling files needed)
```
Runtime *data* is still fetched at load (tiles, Overpass, weather): serve
the file from the Docker image for the full experience; opened via
`file://` or a proxy-less server it renders sky, clouds and UI with an
"OFFLINE"-labelled ground grid instead of basemap tiles.
## Configuration
Edit `config.js` and reload. All keys (with defaults):
```javascript
window.CONFIG = {
theme: 'ash', // 'ash' | 'gruvbox' | 'nord'
language: 'en', // 'en' | 'de' | 'it' | 'es' | 'fr' | 'ru'
timeFormat: 'DD. MMMM, YYYY \u2012 HH:mm:SS',
debug: 0, // 1 = show right-side debug panel
units: { temperature: 'C' }, // 'C' | 'F'
credits: true, // data-attribution block in info box
renderScale: 1, // 0.3..1 internal 3D resolution
maxFps: 30, // WebGL scene frame cap (0 = free)
location: { // drives scene anchor, sun/moon, weather
lat: 51.5074, // -90..+90, north positive
lon: -0.1278, // -180..+180, east positive
label: '' // optional name, skips reverse geocoding
},
day: {
simulation: false, // true: run 24 h in cycleSeconds;
cycleSeconds: 20, // false: real sun/moon positions
fixedTime: null // 'HH:MM' freezes the sim clock at
// that time of day (cars, clouds,
// weather keep moving); null = flow
},
weatherSim: false, // true: cycle weather themes, ignore API
weatherSimInterval: 30000, // ms per sim step
clouds: { top: 0.9, bottom: -0.3 },// cloud deck, world units
// (1 unit = ~12.9 km at z11 / 48.6 deg N)
orbit: { // null/omit = straight flight
radiusKm: 60, // lemniscate lobe tip (clamped ~190 km)
clockwise: true,
reorient: true, // random heading at each crossing
altBob: { // vertical bob, flight band 15-35 km
enabled: true, // false = constant 25 km
amp: 0.77, // half-band in world units
periodS: 90, // seconds per bob cycle
tilt: 0.3 // pitch reaction to climb/descent
}
},
fog: { distanceKm: 120 }, // ground haze; 0/false = off
terrain: {
enabled: true, // false = flat plane
exaggeration: 8, // vertical DEM scale factor
wireframe: false, // debug: orange mesh edges
nightLights: false, // NASA VIIRS city-lights overlay
carLights: true, // moving lights on real roads
cityLights: true // settlement glow dots
},
flights: { // see section above; all distances km
enabled: true,
altitudeKm: 14, climbKm: 100, // cruise / climb-descent run
count: 84, speedKmh: 850, speedVar: 0.18, minLegKm: 60,
holdMinS: 50, holdMaxS: 150, // ground hold at destination
wHub: 4, wField: 2, wStrip: 1, // destination roll weights
contrailChance: 0.6, // share of aircraft with a trail
contrailStartKm: 6, // formation altitude
contrailLifeS: 300, // visibility duration
contrailWidthM: 310 // full trail width
},
ground: { style: 'satellite' }, // 'satellite' | 'streets'
tiles: {
expireDays: null, // >0: re-fetch basemap tiles after n days
debug: false // tile borders + coords on the ground
}
};
```
## URL hash parameters
`http://host:58881/#flight=N&fetch=S`
- `flight=N|NE|E|SE|S|SW|W|NW` — heading of the straight flight corridor
(used when `CONFIG.orbit` is disabled)
- `fetch=N|NE|E|SE|S|SW|W|NW` — direction the tile mosaic is prefetched
ahead of the camera
## Time format tokens
```
YYYY 4-digit year (2026) MM month 01-12 (10)
DD day 01-31 (07) HH hour 00-23 (14)
mm minute 00-59 (35) ss second 00-59 (22)
sss millisecond 000-999 (456) a am/pm (pm)
A AM/PM (PM)
MMM month short name (Oct, Okt, Ott, ...)
MMMM month full name (October, Oktober, ...)
DDD weekday short name (Mon, Mo, Lu, ...)
dddd weekday full name (Monday, Montag, ...) lowercase!
Any other character is emitted verbatim (so a literal T in ISO-8601
needs no special token).
\n newline \t tab \\ literal backslash
```
## Adding a new language
Edit `i18n.js` and add entries for each existing key under your new
language code, e.g.:
```javascript
window.TRANSLATIONS['Sunrise']['xx'] = 'MyTranslation';
```
## Adding a new theme
Drop a new `*.css` file next to the others and add an entry to
`window.THEMES` in `config.js`:
```javascript
window.THEMES['mytheme'] = 'mytheme.css';
```
## Files
```
.
├── background.html the page (UI, weather, day/night driver)
├── config.js user-facing configuration
├── i18n.js translation strings (en/de/it/es/fr/ru)
├── ash.css default color theme
├── gruvbox.css alternative color theme
├── nord.css alternative color theme
├── vendor/
│ ├── clouds3d.js WebGL scene renderer (clouds, terrain, sky, lights)
│ ├── suncalc.js ISC (SunCalc by mourner)
│ ├── stars.json Yale Bright Star Catalogue subset
│ └── LICENSE per-library license attributions
├── default.conf nginx config: port 58881 + tile CORS proxies
├── build.py single-file HTML bundler (see "Single-file build")
├── Dockerfile builds nginx:alpine image
├── docker-compose.yml one-service Compose file
└── LICENSE MIT (application code)
```
## Licenses & data attribution
- **Background application code**: MIT (see `LICENSE`)
- **SunCalc** (vendored): ISC -
- **Open-Meteo** (weather API): CC BY 4.0 -
- **OpenStreetMap** (basemap tiles via ESRI/Carto, Overpass road and
place data): ODbL -
- **Copernicus DEM** (terrain via Mapzen terrarium tiles): open data
- **NASA GIBS VIIRS** (optional night-lights overlay): public domain
- **Yale Bright Star Catalogue** (star positions, via `vendor/stars.json`)
- **OpenStreetMap Nominatim** (reverse geocoding): ODbL -