- atmosphere: physical terrain lighting (air mass, Rayleigh beam colour, skylight, moon, starlight floor), aerial perspective for terrain and cloud deck, configurable haze/albedo/ambient gains - clouds: march the deck in height instead of ray distance, IGN dither (no per-frame flicker), per-step height jitter, 4-octave fbm renormalised, softer density threshold, distance softening as a procedural mip, grazing path clamp, cloud shadows on the ground (cloudShadowDensity/ Floor/Ambient) and cloud haze factor - terrain: self-shadows from the elevation field (penumbra + shadow floor), soft mosaic rim (edgeFadeTiles), east/west axis corrected (+X = west) - lights: per-object twilight switching for cars, city dots and ground blocks - flights: contrail width 620 m, fade to zero by contrailFadeKm, aircraft size decoupled from trail width, count halved - render: CONFIG.renderScale with integer upscale + image-rendering: pixelated - data: nginx overpass proxy + cache, per-mirror cooldown ladder, ring-based cell ordering (disc fills evenly), settlements fetched before roads, interim chain persistence, two worker lanes (car/city) with progress logs - removed: NASA VIIRS night-lights layer (fetch, canvas, texture, uniforms)
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)
docker compose up --build
Then open http://localhost:58881/.
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:
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):
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 whenCONFIG.orbitis 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.:
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:
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 - https://github.com/mourner/suncalc
- Open-Meteo (weather API): CC BY 4.0 - https://open-meteo.com/
- OpenStreetMap (basemap tiles via ESRI/Carto, Overpass road and place data): ODbL - https://www.openstreetmap.org/copyright
- 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 - https://nominatim.org/release/