docs + nginx: full README rewrite (config reference, caches, theme lighting, attribution), drop unused carto/topo proxies, drop obsolete compose version key

This commit is contained in:
Richard Zink committed 2026-10-11 15:39:42 +02:00
1 parent 4369afd3ef
commit 3395ac8f8e
3 files changed
+183 -163

No files matched your search

+180 -146
View File
@@ -1,43 +1,51 @@
# Animated Background # Animated Background
A browser-based animated desktop background: a WebGL flight scene over A browser-based animated desktop background: a WebGL flight scene over real
real terrain, driven by the real sun, moon, stars, and live weather. terrain, driven by the real sun, moon, stars and live weather. Designed to be
dropped into an iframe or kiosk browser and left running.
## Features ## Features
- **WebGL flight scene** (`vendor/clouds3d.js`): raymarched cloud deck, - **WebGL flight scene** (`vendor/clouds3d.js`): a 3D terrain mesh built from
3D terrain mesh from real elevation data (Copernicus DEM), satellite real elevation data (Copernicus DEM), a raymarched volumetric cloud deck,
or street basemap, figure-eight flight path around the configured and a figure-eight flight path around the configured location with a
location with a vertical bob between **15 and 35 km** flight altitude. vertical bob inside the 15-35 km flight band.
- **Real day/night cycle** driven by the actual sun and moon position - **One physical light model** shared by sky, clouds, contrails, aircraft and
(SunCalc) for the configured latitude/longitude — or an accelerated ground: direct beam through a Kasten-Young air mass with Rayleigh, ozone and
24 h simulation (`CONFIG.day`) for wall-decoration purposes. aerosol extinction, diffuse skylight, moonlight, a starlight floor and aerial
- **Realistic night sky**: ~8700 stars from the Yale Bright Star perspective. Sun and moon come from the real positions (SunCalc) for
Catalogue (crisp GL points, size by magnitude, colour by temperature), `CONFIG.location`, or from an accelerated 24 h simulation (`CONFIG.day`).
rotated with the sidereal rate for the site's latitude, plus a Milky - **Theme-tinted lighting**: each theme may pull the physical colours toward
Way band and a phase-correct moon. its own palette and set its own day/night exposure, without replacing the
- **Aircraft**: low-poly jets (CC0 model) cruising the region on curved physics (`window.THEMES[...].lighting`).
routes between real airfields, rolling a random destination at every - **Shadows**: moving cloud shadows sampled from the same cloud field the sky
take-off - sun-lit silhouettes with contrails by day, strobing position renders, plus terrain self-shadowing over the DEM height field.
lights by night (all parameters in `CONFIG.flights`). - **Realistic night sky**: ~8700 stars from the Yale Bright Star Catalogue
- **Night lights**: animated car lights along the real motorway/road (points sized by magnitude, coloured by temperature), rotated at the sidereal
network and pale-yellow settlement lights (OSM place nodes; bigger rate for the site's latitude, a Milky Way band and a phase-correct moon.
towns get more and brighter dots), optionally a NASA VIIRS night-lights - **Aircraft**: low-poly jets (CC0 model) on curved routes between real
satellite overlay. All faded in from dusk, density shaped by a airfields, with a destination rolled at every take-off, sun-lit silhouettes
rush-hour curve. and contrails by day, strobing position lights by night.
- **Live weather** (Open-Meteo): current conditions + 4-day forecast - **Night lights**: vehicle lights moving along the real road network (Overpass)
widget; the weather code selects the scene's cloud density and colour with correct driving side per country, and settlement lights from OSM place
mood (sunny / cloudy / stormy / night). nodes (bigger towns get more and brighter dots). Both fade in light by light
- **Fully cached, offline-capable**: basemap tiles, elevation grid, across twilight instead of switching as one block.
Overpass road/place data, star catalogue and the last weather payload - **Live weather** (Open-Meteo): current conditions plus a 4-day forecast
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 widget; the weather code selects the cloud coverage and the colour mood.
basemap tiles periodically. - **Fully cached, offline-capable**: basemap tiles, the elevation grid, Overpass
- **Location-aware**: set `CONFIG.location.lat/lon`; everything road/place cells, the merged road chains and the decoded settlement list live
(flight anchor, terrain tiles, sun/moon, weather, star rotation) is permanently in IndexedDB; the star catalogue is inlined. Once warm, the scene
computed from it. If the location is omitted, browser geolocation is renders fully offline (only weather is fetched live), and a 15-minute cycle
tried, falling back to the built-in default (London). transparently retries anything that previously failed.
- Units (°C/°F), six UI languages, CSS-swappable colour themes - **Pixel-art rendering option**: `CONFIG.renderScale` renders the 3D scene at
(Ash/Gruvbox/Nord), configurable clock format, optional debug panel. a fraction of the display size and upscales it in exact integer blocks with
`image-rendering: pixelated`, while the DOM UI stays crisp.
- **Location-aware**: set `CONFIG.location.lat/lon` and everything (flight
anchor, terrain tiles, sun/moon, weather, star rotation, traffic side) is
derived from it. Without a location, browser geolocation is tried, falling
back to the built-in default.
- Six UI languages, CSS-swappable colour themes, configurable clock format,
optional debug panel.
## Run with Docker (recommended) ## Run with Docker (recommended)
@@ -47,112 +55,122 @@ docker compose up --build
Then open <http://localhost:58881/>. Then open <http://localhost:58881/>.
The bundled nginx config (`default.conf`) is more than a file server: it The bundled nginx config (`default.conf`) is more than a file server:
provides CORS proxies for the tile sources (`/esri/`, `/carto/`,
`/terrarium/`) that the scene needs for basemap and elevation data. - CORS proxies for the basemap styles (`/esri/streets/`, `/esri/imagery/`) and
A plain static server without these proxies (e.g. the elevation tiles (`/terrarium/`).
`python3 -m http.server 58881`) will run the UI but terrain and map - CORS proxies for the three Overpass mirrors (`/overpass/api/`,
tiles will fail to load. `/overpass/coffee/`, `/overpass/mail/`) with a browser-like request, long
timeouts and a 30-day `proxy_cache` (`overpass-cache.conf`) that also serves
stale entries while a mirror is down. Public Overpass instances refuse or
mis-handle cross-origin POSTs, and they are flaky; the cache means a cell the
browser already paid for is never lost.
- `Cache-Control: no-store` for HTML and JS, so a changed `CONFIG` takes effect
on reload.
A plain static server without these proxies (e.g. `python3 -m http.server
58881`) runs the UI, but terrain, basemap and road data will not load.
## Single-file build ## Single-file build
`build.py` bundles everything into one self-contained HTML file `build.py` bundles everything into one self-contained HTML file: all scripts,
(`background-standalone.html`) — all scripts, the star catalogue and all the star catalogue and every theme stylesheet are inlined, and theme switching
theme CSS are inlined, and theme switching keeps working: keeps working.
```sh ```sh
python3 build.py # -> background-standalone.html (no sibling files needed) python3 build.py # -> background-standalone.html
python3 build.py out.html # -> custom output path
python3 build.py --check # build to a temp file and verify, write nothing
``` ```
Runtime *data* is still fetched at load (tiles, Overpass, weather): serve `--check` fails if anything is left that would need a sibling file (an external
the file from the Docker image for the full experience; opened via `<script src>` or a CSS `<link>`).
`file://` or a proxy-less server it renders sky, clouds and UI with an
"OFFLINE"-labelled ground grid instead of basemap tiles. 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 ## Configuration
Edit `config.js` and reload. All keys (with defaults): `config.js` is the reference: every key is documented in place, and unused
options are listed commented-out with example values. Most knobs are read every
frame, so editing the file and reloading is enough - no rebuild needed.
Groups:
| Group | What it controls |
| --- | --- |
| `theme`, `language`, `timeFormat`, `units`, `credits` | palette, UI language, clock format, °C/°F, attribution line |
| `debug` | debug panel (weather, shader counters, theme palette and lighting) |
| `renderScale`, `maxFps` | internal 3D resolution and frame cap |
| `location` | `lat`/`lon` (+ optional `label`) - everything geographic |
| `weatherSim`, `weatherSimInterval` | ignore the API and cycle the weather looks |
| `tiles` | basemap re-fetch interval (`expireDays`), tile-border debug overlay |
| `ground.style` | `'satellite'` (ESRI World_Imagery) or `'streets'` (ESRI World_Street_Map) |
| `orbit` | figure-eight path, `radiusKm`, direction, reorientation, `altBob` |
| `fog.distanceKm` | distance haze (0/false = off) |
| `clouds` | deck height (`top`/`bottom` in world units) and `density` per weather level |
| `flights` | aircraft count, altitudes, speeds, destination weights, contrails, night strobes |
| `terrain` | DEM mesh, `exaggeration`, `edgeFadeTiles`, `carLights`, `cityLights`, `wireframe` |
| `carLights` | road width, light sizes, lane separation, traffic range/fade, `trafficSide`, spacing, speed, density, brightness |
| `lights` | twilight switching window of every artificial light (`offDeg`, `onDeg`, `softDeg`) |
| `atmosphere` | turbidity, moon/star gains, night floor, albedo/sky/air gains, haze residual, cloud albedo and cloud haze, cloud shadows, terrain shadows |
| `stars` | star colour `tint` and `brightness` |
| `day` | `simulation`, `cycleSeconds`, `fixedTime` |
### World scale
1 world unit = one zoom-11 tile ≈ 12.9 km at 48.6°N. World axes are
`+X = west`, `+Y = up`, `+Z = north`; the camera flies the 15-35 km band
(`y ≈ 1.16-2.70`). Cloud deck heights, fog distance and orbit radius are all
expressed in these units or in km where noted.
### Themes and theme lighting
A theme is a stylesheet plus an optional lighting block:
```javascript ```javascript
window.CONFIG = { window.THEMES['mytheme'] = {
theme: 'ash', // 'ash' | 'gruvbox' | 'nord' css: 'mytheme.css',
language: 'en', // 'en' | 'de' | 'it' | 'es' | 'fr' | 'ru' lighting: {
timeFormat: 'DD. MMMM, YYYY \u2012 HH:mm:SS', tint: 0.45, // 0 = pure physics, 1 = theme colour
debug: 0, // 1 = show right-side debug panel sun: '#f2e3c0', // direct beam
units: { temperature: 'C' }, // 'C' | 'F' sky: '#5e81ac', // skylight and horizon
credits: true, // data-attribution block in info box air: '#81a1c1', // aerial perspective / haze
renderScale: 1, // 0.3..1 internal 3D resolution moon: '#d8e3ee', // moonlight
maxFps: 30, // WebGL scene frame cap (0 = free) gain: 0.95, // day exposure
nightGain: 1.12 // night exposure
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
} }
}; };
``` ```
The physical model stays the base in every theme; `lighting` only shifts hues
and exposure. Omit `lighting` for a purely physical theme. The palette itself is
read from the CSS custom properties of the active stylesheet, so the scene and
the UI always agree.
### Debug switches
All off by default, all documented in `config.js`:
- `debug: 1` - debug panel (bottom left): weather state, sun/moon, shader
counters, the active theme palette and its lighting values.
- `tiles: { debug: true }` - bake tile borders, N/S and x/y labels into the
ground texture (orientation check).
- `terrain: { wireframe: true }` - draw the mesh edges.
- `ground: { style: 'streets' }` - labelled street map instead of satellite.
- `day: { simulation: true }` / `weatherSim: true` - deterministic day cycle and
weather cycle for comparing looks.
## URL hash parameters ## URL hash parameters
`http://host:58881/#flight=N&fetch=S` `http://host:58881/#flight=N&fetch=S`
- `flight=N|NE|E|SE|S|SW|W|NW` — heading of the straight flight corridor - `flight=N|NE|E|SE|S|SW|W|NW` - heading of the straight flight corridor (used
(used when `CONFIG.orbit` is disabled) when `CONFIG.orbit` is disabled)
- `fetch=N|NE|E|SE|S|SW|W|NW` — direction the tile mosaic is prefetched - `fetch=N|NE|E|SE|S|SW|W|NW` - direction the tile mosaic is prefetched ahead of
ahead of the camera the camera
## Time format tokens ## Time format tokens
@@ -173,42 +191,54 @@ needs no special token).
\n newline \t tab \\ literal backslash \n newline \t tab \\ literal backslash
``` ```
## Adding a new language ## Caches
Edit `i18n.js` and add entries for each existing key under your new Everything is stored in IndexedDB, database `background-tiles`, store `tiles`:
language code, e.g.:
| Key pattern | Content |
| --- | --- |
| `sat/{z}/{x}/{y}` / `{z}/{x}/{y}` | basemap tiles, kept per style so satellite and street maps cache separately |
| `elev/...` | decoded elevation grid cells |
| `geo/mw11/...`, `geo/mw_e/...`, `geo/sec10/...` | Overpass road cells (motorway network and side roads) |
| `geo/place_...` | Overpass place-node cells |
| `geo/roadchains_v1` | merged road polylines (rebuilt only when a cell is re-fetched) |
| `geo/placecache_v1` | decoded settlement list |
| `geo/airports_v1` | airfield list used by the aviation layer |
The star catalogue is not cached: `build.py` inlines `vendor/stars.json` into
`window.__starsData`. Weather is the only thing fetched live on every load.
Cached data is never re-fetched unless `CONFIG.tiles.expireDays` is set. To
start clean, delete the `background-tiles` database in the browser.
## Adding a language
Add the key under the new language code in `i18n.js`:
```javascript ```javascript
window.TRANSLATIONS['Sunrise']['xx'] = 'MyTranslation'; 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 ## Files
``` ```
. .
├── background.html the page (UI, weather, day/night driver) ├── background.html the page (UI, weather, day/night driver, debug panel)
├── config.js user-facing configuration ├── config.js user-facing configuration + window.THEMES
├── i18n.js translation strings (en/de/it/es/fr/ru) ├── i18n.js translation strings (en/de/it/es/fr/ru)
├── ash.css default color theme ├── ash.css default colour theme
├── gruvbox.css alternative color theme ├── gruvbox.css alternative colour theme
├── nord.css alternative color theme ├── nord.css alternative colour theme
├── vendor/ ├── vendor/
│ ├── clouds3d.js WebGL scene renderer (clouds, terrain, sky, lights) │ ├── clouds3d.js WebGL scene renderer (sky, clouds, terrain, lights)
│ ├── suncalc.js ISC (SunCalc by mourner) │ ├── suncalc.js sun/moon positions - ISC (SunCalc by mourner)
│ ├── plane3d.js low-poly aircraft model - CC0
│ ├── stars.json Yale Bright Star Catalogue subset │ ├── stars.json Yale Bright Star Catalogue subset
│ └── LICENSE per-library license attributions │ └── LICENSE per-library license attributions
├── default.conf nginx config: port 58881 + tile CORS proxies ├── default.conf nginx: port 58881, CORS proxies, cache headers
├── overpass-cache.conf nginx proxy_cache_path for the Overpass cache
├── build.py single-file HTML bundler (see "Single-file build") ├── build.py single-file HTML bundler (see "Single-file build")
├── Dockerfile builds nginx:alpine image ├── Dockerfile builds the nginx:alpine image
├── docker-compose.yml one-service Compose file ├── docker-compose.yml one-service Compose file
└── LICENSE MIT (application code) └── LICENSE MIT (application code)
``` ```
@@ -217,11 +247,15 @@ window.THEMES['mytheme'] = 'mytheme.css';
- **Background application code**: MIT (see `LICENSE`) - **Background application code**: MIT (see `LICENSE`)
- **SunCalc** (vendored): ISC - <https://github.com/mourner/suncalc> - **SunCalc** (vendored): ISC - <https://github.com/mourner/suncalc>
- **Aircraft model** (`vendor/plane3d.js`): CC0
- **Open-Meteo** (weather API): CC BY 4.0 - <https://open-meteo.com/> - **Open-Meteo** (weather API): CC BY 4.0 - <https://open-meteo.com/>
- **OpenStreetMap** (basemap tiles via ESRI/Carto, Overpass road and - **ESRI** basemaps (`World_Imagery`, `World_Street_Map`) served through the
place data): ODbL - <https://www.openstreetmap.org/copyright> bundled proxy: Esri, its licensors and contributors -
- **Copernicus DEM** (terrain via Mapzen terrarium tiles): open data <https://www.esri.com/en-us/legal/acknowledgmentsandstandards>
- **NASA GIBS VIIRS** (optional night-lights overlay): public domain - **OpenStreetMap** (Overpass road and place data): ODbL -
<https://www.openstreetmap.org/copyright>
- **Copernicus DEM** (terrain, via Mapzen/TILEZEN terrarium tiles): open data -
<https://registry.opendata.aws/terrain-tiles/>
- **Yale Bright Star Catalogue** (star positions, via `vendor/stars.json`) - **Yale Bright Star Catalogue** (star positions, via `vendor/stars.json`)
- **OpenStreetMap Nominatim** (reverse geocoding): ODbL - - **OpenStreetMap Nominatim** (reverse geocoding): ODbL -
<https://nominatim.org/release/> <https://nominatim.org/release/>
+3 -16
View File
@@ -23,25 +23,12 @@ server {
access_log off; access_log off;
} }
# CORS proxy for Carto basemaps (place names, OSM-derived). # CORS proxies for the two basemap styles the scene can use.
# URL format: /carto/voyager/{z}/{x}/{y}.png # /esri/streets/{z}/{y}/{x}.jpg -> ESRI World_Street_Map (place names)
location ^~ /carto/ { # /esri/imagery/{z}/{y}/{x}.jpg -> ESRI World_Imagery (aerial)
resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s;
proxy_pass https://a.basemaps.cartocdn.com/;
proxy_ssl_server_name on;
proxy_set_header Host a.basemaps.cartocdn.com;
proxy_set_header User-Agent "Mozilla/5.0 (compatible; tile-proxy)";
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
add_header Access-Control-Max-Age 86400 always;
}
# CORS proxy for ESRI World_Street_Map. URL format:
# /esri/wsm/{z}/{y}/{x}.jpg -> ESRI /ArcGIS/.../World_Street_Map/.../tile/{z}/{y}/{x}
location ^~ /esri/ { location ^~ /esri/ {
resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s; resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s;
rewrite ^/esri/streets/(.*)$ /ArcGIS/rest/services/World_Street_Map/MapServer/tile/$1 break; rewrite ^/esri/streets/(.*)$ /ArcGIS/rest/services/World_Street_Map/MapServer/tile/$1 break;
rewrite ^/esri/topo/(.*)$ /ArcGIS/rest/services/World_Topo_Map/MapServer/tile/$1 break;
rewrite ^/esri/imagery/(.*)$ /ArcGIS/rest/services/World_Imagery/MapServer/tile/$1 break; rewrite ^/esri/imagery/(.*)$ /ArcGIS/rest/services/World_Imagery/MapServer/tile/$1 break;
proxy_pass https://server.arcgisonline.com; proxy_pass https://server.arcgisonline.com;
proxy_ssl_server_name on; proxy_ssl_server_name on;
-1
View File
@@ -1,4 +1,3 @@
version: '3.8'
services: services:
wireframe: wireframe: