/* * Wallpaper background config. * * Properties: * theme - Active palette theme. One of the keys in window.THEMES * (default 'ash'). The matching CSS file is loaded at * startup. * language - UI / i18n language code (default 'en'). * timeFormat - Format string for the clock using the token set below * (Moment.js-like names, own mini-formatter). * debug - If 1, the right-side debug panel is shown. * units - Object with 'temperature' ('C' or 'F'). * location - Geographic coordinates for sun/moon calculations. Used * by SunCalc (https://github.com/mourner/suncalc). Object * with 'lat' (latitude, -90 to +90) and 'lon' (longitude, * -180 to +180). Positive lat = north, positive lon = east. * Optionally add 'label' (string) to override the * reverse-geocoded city name. * Note: the height grid is axis-aligned (N up); terrain * matches the square ground mosaic and straight flights * (prefetch direction via #fetch=N). Heights: Copernicus * DEM via terrarium tiles (z9, ~200 m). * terrain - 3D ground from real elevation data. Object: * enabled - true (default) / false = flat plane. * exaggeration - vertical scale factor on the DEM * (default 8; 1 would be physically * true and nearly invisible at this * horizontal scale). * weatherSim - If true, ignore open-meteo and cycle through * ['sunny','cloudy','stormy','night'] automatically every * weatherSimInterval ms. Useful for A/B testing every * theme without waiting on the API. * weatherSimInterval - Sim cycle period in ms (default 30000). * orbit - Figure-eight flight (Bernoulli lemniscate) around * CONFIG.location instead of a straight drift. The * eight's self-crossing sits exactly on the configured * coordinate. Each time the aircraft crosses that point * a new random orientation is picked and blended in * over one lobe (spline through the crossing). Object: * radiusKm - lobe tip reach from the centre in km * (~13 km per tile at z11/48deg N). * Clamped (~190 km) so tips + fog horizon * fit inside the 43x43 tile mosaic. * clockwise - travel direction along the eight. * reorient - random re-aim at every crossing * (default true; false keeps the bearing). * Set to null (or remove) for the straight-line flight. * fog - Ground fog / distance haze. Object with 'distanceKm' * (visibility range at which terrain fully blends into * a milky haze; default 120). false/0 disables fog. * clouds - Vertical extent of the cloud deck in world units * (1 unit = ~12.9 km at zoom 11; camera band 15-35 km, * i.e. y ~ 1.16-2.70). Default puts the deck top (~11.6 * km) below the lowest flight altitude. * tiles - All downloads (street tiles, elevation grid, last * weather payload, geo label) live permanently in * IndexedDB and are never re-fetched once cached; a * warm cache = fully offline rendering. Set * expireDays (number > 0) to re-fetch street tiles * periodically (e.g. 90). * * Time format tokens (subset): * YYYY 4-digit year (2026) * MMMM full month name (October) * MM month with leading zero (10) * DD day with leading zero (07) * HH 24-hour hour with zero (18) * mm minute with leading zero (14) * ss second with leading zero (25) * a am/pm lowercase (pm) * A AM/PM uppercase (PM) * dddd full weekday name (lowercase!) * Any other character is emitted verbatim. */ window.CONFIG = { theme: 'ash', language: 'en', timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS', debug: 0, units: { temperature: 'C' }, // Data attribution at the bottom of the info box (automatic text // based on the enabled layers). false = hidden. credits: true, // Internal 3D resolution scale (0.3..1, default 1). Lower values // render the clouds/map shader at reduced resolution and upscale // via CSS - use it if fps is low (GPU-bound) despite a quiet CPU. // 1 = native. 0.5 = a quarter of the pixels (half width, half height), // upscaled 2x as hard blocks; the DOM UI on top stays crisp. renderScale: 0.5, // WebGL scene frame cap (default 30; 0 = free-running/vsync). All // scene motion is time-based, so the cap only changes smoothness. maxFps: 30, location: { // Latitude and longitude for sun/moon calculations lat: 48.5667, lon: 13.4319 }, // weatherSim: true = Szenario-Wetter statt Open-Meteo (A/B-Durchlauf // aller Wetterstufen alle weatherSimInterval ms). false = echtes // Wetter von Open-Meteo. weatherSim: false, weatherSimInterval: 30000, // All downloads (basemap tiles, elevation grid, weather JSON, geo // label) stay in IndexedDB permanently and are never re-fetched once // cached. expireDays (days, > 0) re-enables periodic basemap refresh. tiles: { expireDays: null, // true = draw tile borders + N/S + x/y coords into the ground // texture (debug orientation). Off = clean map. debug: true }, // Ground imagery: 'satellite' = ESRI World_Imagery (aerial, no // labels), 'streets' = ESRI World_Street_Map (with place names). // Both cached permanently (separate cache prefix per style). ground: { style: 'streets' }, // Figure-eight course around location (lat/lon); the crossing point // sits exactly on the coordinate. null/removed = straight flight. orbit: { radiusKm: 60, // lobe tip: max distance from the centre clockwise: true, reorient: true, // random heading at every crossing pass // Slow vertical bobbing inside the 15-35 km flight band: // centre 25 km (~1.93 world units), amp = half the band // (10 km ~ 0.77); hard clamp at the band limits. // periodS = seconds per cycle, tilt = camera nose reaction to // climb/descent rate. enabled: false = constant 25 km. altBob: { enabled: true, amp: 0.77, periodS: 90, tilt: 0.3 } }, // Ground fog: distance haze. distanceKm = visibility until fully // saturated haze (false/0 = off). fog: { distanceKm: 120 }, // ---- Civil aviation (CONFIG.flights) --------------------------------- // Low-poly jets (CC0 model, vendor/plane3d.js) with randomized target // airports: the destination is rolled at every take-off (hubs weighted // via wHub/wField/wStrip). Paths are gentle Bezier curves resampled to // arc length (constant ground speed) flown as a full profile: take-off // and climb, cruise, descent and landing, a short ground hold, then a // fresh destination. By day sun-lit silhouettes; by night white // strobing position lights with a red beacon. Contrails form above // contrailStartKm on contrailChance of the aircraft and stay visible // for contrailLifeS. All distances in km (world scale: 1 unit = one // z11 tile ~ 12.9 km at 48.6 deg N). flights: { enabled: true, altitudeKm: 14, // cruise altitude climbKm: 100, // climb/descent run to/from cruise altitude count: 21, // max simultaneous aircraft speedKmh: 850, // mean ground speed speedVar: 0.18, // +/- deviation (0..0.5) minLegKm: 60, // shortest leg (no taxi hops) holdMinS: 50, // ground hold at destination (min..max) holdMaxS: 150, wHub: 4, // destination weights: hub / airfield / strip wField: 2, wStrip: 1, contrailChance: 0.85, // share of aircraft with a persistent trail contrailStartKm: 6, // trail formation altitude contrailLifeS: 300, // trail visibility duration contrailWidthM: 620, // full trail width // trails fade out completely by this distance (start is 35% of it) contrailFadeKm: 100, // Night strobes. Size shrinks with distance but never below // lightPx, and never grows past lightMaxPx for a jet right above. // The white strobe is a double flash: two equal flashes // blinkGapS apart, then blinkPeriodS - blinkGapS - blinkFlashS // of darkness (1.4 - 0.16 - 0.08 = 1.16 s pause). lightPx: 7, lightMaxPx: 16, lightBoost: 9, blinkPeriodS: 1.4, blinkGapS: 0.16, blinkFlashS: 0.08 }, // 3D ground from real elevation data (Copernicus DEM, terrarium // tiles). enabled: false = flat plane. exaggeration: vertical // elevation scale (1 = real, nearly invisible; default 8). // wireframe: true = overlay mesh edges as orange lines (debug). // carLights: moving lights on the real road network (OSM/Overpass // curves, additive over the terrain at night). JSON once, cached. // cityLights: settlement lights - OSM place nodes (city/town/ // village/hamlet) as slightly yellow glow dots from dusk on; // bigger settlements (place class + population tag) get more and // brighter dots. One Overpass query, cached permanently. // Data (c) OpenStreetMap contributors, ODbL. terrain: { // soft rim at the mosaic border, in tiles (1 tile ~ 13 km) edgeFadeTiles: 4, enabled: true, exaggeration: 8, wireframe: false, // How far the day map is dimmed at full night: 0 = pitch black, // 1 = unchanged. Only affects night; the day map is untouched. // (The NASA VIIRS city-lights layer was removed; city lights come // from OSM place nodes via cityLights.) nightDim: 0.22, carLights: true, cityLights: true }, // Car lights on the real road network (on/off stays in terrain.carLights). // Two layers now: a thin road line (bandPx / bandBrightness) and one // round point sprite per vehicle (minPx..maxPx), same idea as the city // dots' gl_PointSize floor. laneM is the real distance between the two // traffic directions, so they split close to the camera and merge into // one line far away instead of drifting apart with the camera distance. // dashM: mean vehicle spacing (secondary roads use dashM * 2.8). // speed: multiplier on the real driving speed (motorway ~120 km/h, // secondary ~70 km/h at dashM spacing). carLights: { roadWidthM: 24, // real carriageway width of the road line bandPx: 1.2, // pixel floor for the road line bandBrightness: 0.10, // road line glow (points are the main layer) minPx: 3.6, // guaranteed vehicle light diameter maxPx: 10, // ceiling for a light right above the camera boost: 5, // extra light size close to the camera laneM: 8, // real distance between the two directions lanePx: 2.6, // screen floor for that distance, so the two // directions never collapse into one stream // Traffic lives on a disc around the configured location: roads // outside rangeKm carry no vehicles. fadeKm / fadeSpanKm are the // distance fade belonging to that disc (start, and how far past it // until nothing is left) - keep them at or beyond rangeKm so the // region never ends in a hard cut. rangeKm: 200, fadeKm: 150, fadeSpanKm: 60, // 'auto' = side of the road taken from location (lat/lon): left in // the UK, Japan, Australia, India, Southern Africa, ... and right // everywhere else. Set 'left' or 'right' to force it. trafficSide: 'auto', dashM: 620, // mean vehicle spacing on motorways // Traffic spread around the motorway network (gaussian): peak on the // motorway itself, thinning with distance to the nearest one. // spreadKm = sigma, spreadFloor = share that far side roads keep. spreadKm: 45, spreadFloor: 0.30, speed: 1.0, // flow speed multiplier density: 1.0, // vehicle density multiplier brightness: 1.3 // vehicle light intensity }, // Headlights and house lights do not ignite at once: every single // light gets its own sun-altitude threshold between offDeg (nothing // lit) and onDeg (everything lit), drawn per vehicle / per city block. // offDeg +5 deg is roughly 30 min before sunset here, onDeg -6 deg is // civil twilight. In the morning the same thresholds switch the lights // off again as the sun climbs. lights: { offDeg: 5, onDeg: -6, softDeg: 1.2 }, // Physical lighting: direct beam from a Kasten-Young air mass with // Rayleigh + ozone + aerosol extinction, diffuse skylight and moonlight // from the same model. terrainLighting: 'palette' switches back to the // old hand-painted hillshade. turbidity scales the aerosol load, // moonGain the moonlight strength, nightFloor the darkest skylight // (airglow + starlight), albedoTint is the theme's share on surfaces. atmosphere: { terrainLighting: 'physical', turbidity: 1.0, // moonlight strength; scales the direct term AND the diffuse sky // lift, both with the illuminated fraction (new moon -> none) moonGain: 0.60, // stylised starlight + airglow floor on a dark night (real // integrated starlight is ~1e-6 of daylight and would be invisible) starGain: 0.18, // extra share on the ground. The product is the starlit floor and // must stay below the skylight at sunset, otherwise the night would // be brighter than the moment of sunset. Moonlight (moonGain) is // what lifts a night above that floor. starGainGround: 0.4, nightFloor: 0.10, // cloud albedo in physical mode: theme hue kept, brightness comes // from the light model (the night palette alone would double-darken) cloudBrightness: 1.0, // moving cloud patches darken the ground beneath them (the terrain // samples the same cloud field as the sky pass) cloudShadows: true, cloudShadowDensity: 1.8, // optical depth through the deck cloudShadowFloor: 0.06, // darkest shade under a thick cloud cloudShadowAmbient: 0.65, // share of the skylight a cloud also blocks // ridges occlude the sun for the ground behind them (march over the // same height field the mesh was built from) terrainShadows: true, // brightness a fully shadowed slope keeps (0 = pitch black, 1 = off) terrainShadowFloor: 0.42, // how much of the ground haze applies to the cloud deck (clouds sit // above most of the air that causes it) cloudHaze: 0.22, albedoGain: 1.0, // share of the daylight column that the distance fog / horizon takes, // and the diffuse skylight share on surfaces skyGain: 0.45, // Aerial perspective in physical mode: the terrain is attenuated by // exp(-tau) and the scattered light of the same air column is added. // airGain scales the Rayleigh airlight (blue-biased), hazeResidual // is how much of the surface survives at the full fog distance // (0.3 = distant ground keeps 30 % of its own colour). airGain: 0.55, hazeResidual: 0.30 }, // Star colour: every catalogue star gets its real blackbody tint, mixed // towards white by this factor. 0 = pure white points, 1 = full colour. stars: { tint: 0.10, // 90 % white + 10 % of the star's real colour brightness: 1.6 // overall glow of the catalogue points }, // Day simulation: true = a full 24 h cycle runs in cycleSeconds // (sun rises east, culminates south, sets west). // false = real sun/moon positions via SunCalc. // fixedTime: 'HH:MM' (or a number 0-24) freezes the sim clock at // that time of day (moving objects, clouds, cars and the weather // sim keep running unaffected); null/omitted = the clock advances. day: { // simulation: true runs a full 24 h cycle in cycleSeconds. // 60 s = one simulated day per minute of wall time (one simulated // hour every 2.5 s). The analog clock and the sun/moon tables follow // the simulated date. simulation: true, cycleSeconds: 60, fixedTime: null }, // Cloud deck in world units (1 unit ~ 12.9 km at zoom 11; flight // band 15-35 km = y ~ 1.16-2.70). Keep the top below the lowest // flight altitude. clouds: { top: 0.9, bottom: -0.3, // per weather level (0 = clear, 1 = overcast); overrides the built-in // sunny 0.20 / cloudy 0.70 / stormy 0.80 / night 0.45 density: { stormy: 0.80 } }, }; // List of available themes. The key is the theme id (used in config.theme), // the value is the CSS file name. Add a new theme by adding a new entry here // and dropping the corresponding CSS file in the same directory. window.THEMES = { 'ash': 'ash.css', 'gruvbox': 'gruvbox.css', 'nord': 'nord.css' };