/* * animated_wallpaper - configuration * * Everything the scene reads at runtime lives here. Values are plain * JavaScript so the file can also be edited while the page is open * (window.CONFIG is read every frame for most knobs). * * TOP LEVEL * theme active palette, a key of window.THEMES (default 'ash') * language UI language code for i18n.js ('en', 'de', ...) * timeFormat clock format string, token list at the bottom * debug 1 = show the debug panel (bottom left: weather, shader * counters, theme palette). 0 (default) = hidden * credits true = attribution line at the bottom of the info box * units { temperature: 'C' | 'F' } * location { lat, lon, label? } - sun/moon geometry, traffic side, * tile/DEM/Overpass origin, figure-eight centre * renderScale internal 3D resolution (0.2..1) with blocky upscale * maxFps scene frame cap (0 = free running) * antialias MSAA on the WebGL framebuffer (true/false) * weatherSim ignore Open-Meteo and cycle the weather themes * tiles basemap/elevation cache behaviour + debug overlays * ground basemap style * orbit flight path (figure eight or straight line) * fog distance haze * clouds cloud deck height, coverage and wind motion * flights civil aviation layer (aircraft + contrails) * terrain 3D ground, car lights, settlement lights * carLights tuning of the vehicle light layer * lights twilight switching of artificial lights * atmosphere physical light model + shadow/haze knobs * stars star catalogue tint and brightness * planets Keplerian planet points in the night sky * curvature round-world curvature for terrain, roads, lights and sky paths * day simulated day cycle or the real sun * * WORLD SCALE * 1 world unit = 1 zoom-11 tile ~ 12.9 km at 48.6 deg N. * World axes: +X = east, +Y = up, +Z = north (verified against the * basemap and the star frame). Camera flies a 15-35 km band (y ~ 1.16-2.70). * * TIME FORMAT TOKENS (own mini formatter, Moment.js-like names) * YYYY 4-digit year MMMM full month name MM month 01-12 * DD day 01-31 HH hour 00-23 mm minute * ss second a am/pm A AM/PM * dddd full weekday name DDD short weekday name * any other character is copied verbatim */ window.CONFIG = { // ------------------------------------------------------------------ // Identity and UI // ------------------------------------------------------------------ theme: 'ash', // 'ash' | 'nord' | 'gruvbox' (see window.THEMES) // theme: 'nord' // theme: 'gruvbox' language: 'en', // 'en', 'de', 'it', 'es', 'fr', 'ru' // language: 'de' timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS', // timeFormat: 'YYYY-MM-DD HH:mm:ss' // timeFormat: 'dddd, DD. MMMM YYYY HH:mm' debug: 0, // 1 = debug panel + progress logs in the console // debug: 1 credits: true, // false = hide the attribution line // credits: false units: { temperature: 'C' }, // 'F' for Fahrenheit // units: { temperature: 'F' } // ------------------------------------------------------------------ // Rendering budget // ------------------------------------------------------------------ // Internal 3D resolution. The value is snapped to an exact integer // divisor of the CSS size (0.5 -> 2x blocks, 0.33 -> 3x, 0.25 -> 4x) // and upscaled with image-rendering: pixelated, so it reads as pixel // art instead of a blurry rescale. The DOM UI stays crisp at any value. // 1 = native resolution (sharpest, most expensive). renderScale: 1, // 1 native, 0.5 pixel-art 2x, 0.25 4x // Scene frame cap. All motion is time-based, so this only changes // smoothness, never speed. 0 = free running (vsync). maxFps: 30, // 0 = free running // MSAA on the WebGL framebuffer. Costs fill rate, and with renderScale // below 1 the pixel blocks dominate anyway - set false if the scene is // GPU bound. Read once at context creation, so a reload is needed. antialias: true, // false saves GPU fill rate // ------------------------------------------------------------------ // Location // ------------------------------------------------------------------ // Drives sun/moon positions, the traffic side lookup, the tile/DEM // origin, the Overpass grid and the figure-eight centre. location: { lat: 48.5667, lon: 13.4319 // label: 'Passau' // overrides the reverse-geocoded name // lat: 51.5074, lon: -0.1278 // London example }, // ------------------------------------------------------------------ // Weather // ------------------------------------------------------------------ // false = live Open-Meteo conditions (also used for the theme choice). // true = cycle sunny -> cloudy -> stormy -> night, useful to compare // all weather looks without waiting on the API. weatherSim: true, // test mode: fixed sunny weather weatherSimInterval: 15000, // ms per weather step when weatherSim is true // weatherSim: false, weatherSimInterval: 30000 // Synthetic weather states used by weatherSim. Each state sets theme, // Open-Meteo-like weather_code, temperature, humidity, precipitation and wind. weatherSimStates: ['sunny'], weatherSimProfile: { sunny: { code: 0, tempC: 18, humidity: 45, precipMm: 0.0, snowMm: 0.0, windKmh: 8, windDirDeg: 240 }, cloudy: { code: 3, tempC: 14, humidity: 70, precipMm: 0.0, snowMm: 0.0, windKmh: 14, windDirDeg: 260 }, rain: { code: 61, tempC: 10, humidity: 92, precipMm: 1.2, snowMm: 0.0, windKmh: 22, windDirDeg: 270 }, snow: { code: 73, tempC: -2, humidity: 85, precipMm: 0.4, snowMm: 0.8, windKmh: 12, windDirDeg: 300 }, stormy: { code: 95, tempC: 16, humidity: 88, precipMm: 2.5, snowMm: 0.0, windKmh: 35, windDirDeg: 250 }, night: { code: 0, tempC: 8, humidity: 65, precipMm: 0.0, snowMm: 0.0, windKmh: 6, windDirDeg: 220 } // weatherSimStates: ['sunny', 'rain', 'stormy'] // weatherSimProfile: { rain: { code: 80, tempC: 9, precipMm: 2.0, windKmh: 28 } } }, // ------------------------------------------------------------------ // Emergency vehicles // ------------------------------------------------------------------ // Emergency vehicles start from the nearest city/town to CONFIG.location // and drive a selected secondary/primary road chain out and back. The // route selection is intentionally conservative: it uses already loaded // road chains and does not run a full routing engine. emergencyVehicles: { enabled: true, count: 3, speedKmh: 85, minKm: 40, maxKm: 80, rankMax: 1, // 0=city, 1=town color: [0.22, 0.55, 1.0], flashHz: 2.2, nightOnly: false, bright: 1.35 // emergencyVehicles: { enabled: false } // emergencyVehicles: { count: 6, speedKmh: 110, minKm: 25, maxKm: 120 } // emergencyVehicles: { color: [1.0, 0.18, 0.12], flashHz: 3.0 } }, // ------------------------------------------------------------------ // Weather effects // ------------------------------------------------------------------ // Visual precipitation overlays driven by window.__currentWeather. // intensity multiplies Open-Meteo values; maxAlpha caps the overlay. weatherEffects: { enabled: false, rain: { enabled: false, intensity: 1.0, // 0.5 subtle, 2.0 heavy maxAlpha: 0.35, // maximum screen overlay opacity speed: 1.0 // fall speed multiplier }, snow: { enabled: false, intensity: 1.0, maxAlpha: 0.45, speed: 0.35 // slow drifting flakes }, lightning: { enabled: false, gain: 1.2, // brightness of one flash minDelayS: 8, // minimum delay between storm flashes maxDelayS: 45, // maximum delay between storm flashes doubleFlash: true // some flashes flicker twice }, wetRoads: { enabled: false, dryMinutes: 30, // roads dry out after this many dry minutes roadGain: 0.8, // extra road-ribbon glow when wet lightGain: 0.35 // extra halo on vehicle lights when wet }, snowCover: { enabled: false, meltMinutes: 90, // snow melts after this many warm minutes baseLineM: 900, // snowline at about +5 C tempLineMPerC: 80, // snowline moves down as temperature drops minLineM: 0, // full snowline in deep cold maxLineM: 2500, // no snow above this line when warm transitionM: 120, // soft transition band around the snowline slopeMax: 0.72, // steep slopes keep less snow gain: 0.85 // maximum whiteness of the snow albedo } // weatherEffects: { enabled: false } // weatherEffects: { rain: { maxAlpha: 0.55, speed: 1.4 }, snow: { enabled: false } } // weatherEffects: { lightning: { gain: 2.0, minDelayS: 3, maxDelayS: 12 } } // weatherEffects: { wetRoads: { enabled: false } } // weatherEffects: { wetRoads: { dryMinutes: 10, roadGain: 1.4, lightGain: 0.7 } } // weatherEffects: { snowCover: { baseLineM: 600, tempLineMPerC: 120, gain: 1.0 } } // weatherEffects: { snowCover: { enabled: false } } }, // ------------------------------------------------------------------ // Weather data // ------------------------------------------------------------------ // Open-Meteo fields used by the scene. These knobs only control which // optional weather values are requested and exposed as window.__currentWeather. weather: { precipitation: true, // mm/h total precipitation snowfall: true, // cm/h snowfall hourly: true, // use hourly values for the simulated clock // weather: { precipitation: false, snowfall: false, hourly: false } // weather: { precipitation: true, snowfall: true } }, // ------------------------------------------------------------------ // Tiles and caches // ------------------------------------------------------------------ // Basemap tiles, the elevation grid, Overpass cells, the star catalogue // and the last weather payload live permanently in IndexedDB // ('background-tiles' / store 'tiles') and are never re-fetched once // cached: a warm cache renders fully offline. tiles: { expireDays: null, // null = never expire; e.g. 90 = re-fetch every 90 days debug: false // true = tile borders + N/S + x/y labels baked // into the ground texture (orientation debug) // tiles: { expireDays: 30, debug: true } }, ground: { // 'satellite' = ESRI World_Imagery (aerial, no labels) // 'streets' = ESRI World_Street_Map (with place names) // Both are cached separately (cache id prefix 'sat/' vs plain). style: 'satellite' // style: 'streets' }, // ------------------------------------------------------------------ // Flight path // ------------------------------------------------------------------ // Figure eight (Bernoulli lemniscate) whose crossing sits exactly on // CONFIG.location. At every crossing a new bearing is picked and blended // over one lobe. Set orbit: null for a straight drift. orbit: { radiusKm: 60, // lobe tip reach (clamped ~190 km so the // tips plus the fog dome fit the mosaic) // radiusKm: 35 // tighter flight // radiusKm: 120 // wider flight, more terrain visible clockwise: true, // travel direction along the eight reorient: true, // false keeps the bearing across crossings // Vertical bobbing inside the 15-35 km band: centre 25 km, amp is // half the band (10 km ~ 0.77 world units), tilt is how much the // camera nose follows the climb/descent rate. altBob: { enabled: true, amp: 0.77, periodS: 90, tilt: 0.3 } // altBob: { enabled: false } // constant 25 km, no bobbing }, // ------------------------------------------------------------------ // Distance haze // ------------------------------------------------------------------ fog: { distanceKm: 120 // fully saturated haze at this range; 0/false = off // distanceKm: 0 // no distance fog // distanceKm: 220 // deeper aerial perspective }, // ------------------------------------------------------------------ // Cloud deck // ------------------------------------------------------------------ // World units (1 unit ~ 12.9 km). Keep 'top' below the lowest flight // altitude so aircraft fly above the deck. clouds: { enabled: true, // false removes the volumetric cloud deck top: 1.0, // ~12.9 km bottom: -0.2, // ~-2.6 km (deck base slightly below terrain) // Coverage per weather level, 0 = clear, 1 = overcast. Only the // levels listed here override the built-in defaults // (sunny 0.20, cloudy 0.70, stormy 0.80, night 0.45). Live weather // only ever reports sunny/cloudy/stormy, so the deck keeps that // coverage through the night as well - 'night' is used by the weather // simulation (weatherSim: true) alone. density: { sunny: 0.25, cloudy: 0.50, rain: 0.75, snow: 0.65, stormy: 0.80 }, // 'night' intentionally has no own density: it follows the last // non-night weather density (sunny/cloudy/stormy). // Cloud motion from Open-Meteo wind. Open-Meteo reports wind FROM // the reported compass direction; clouds advect toward the opposite // bearing. speedScale exaggerates real advection so the motion is // visible at the world scale (1 unit ~ 12.9 km). wind: { speedScale: 80, // 0 = no wind motion, 40 slow, 120 fast minSpeed: 0.004, // world units/s floor when API wind is calm maxSpeed: 0.08 // world units/s ceiling for storm winds // wind: { speedScale: 0 } // static clouds // wind: { speedScale: 140, maxSpeed: 0.12 } // very visible motion } // density: { sunny: 0.05, cloudy: 0.9, rain: 0.95, snow: 0.85, stormy: 1.0 } }, // ------------------------------------------------------------------ // Moon // ------------------------------------------------------------------ // The disc is drawn artistically larger than the real one (0.0046 rad // = 0.26 deg, about 9 px at 1080p) so that the surface texture and the // phase are actually readable. The terminator itself stays geometric: // lit side = surface normal dot sun direction. // The map is sampled in a selenographic frame built from the north // celestial pole, so the disc shows north up and east to the right - // like a printed lunar map - and rotates as the moon crosses the sky. moon: { angularRadius: 0.0552, // 12x the physical 0.0046 rad texture: 'vendor/moon.jpg', // equirectangular near-side map // Scene moonlight (terrain, aircraft, contrails, airlight) is // scaled with the disc, but only half of the excess over the // physical size counts: 1 + 0.5 * (angularRadius/0.0046 - 1) = 6.5. // Set 1.0 for the strictly physical night brightness. lightGain: 6.5, earthshine: 0.10, // faint light on the dark side limbDarkening: 0.30, // surface dims towards the limb brightness: 2.20, // per-pixel luminance of the disc halo: 1.0, // glow around the disc, scales with phase flipU: false, // mirror the map if orientation is wrong flipV: false }, // ------------------------------------------------------------------ // Civil aviation // ------------------------------------------------------------------ // Low-poly jets (CC0 model in vendor/plane3d.js) with a destination // rolled at every take-off (hubs weighted by wHub/wField/wStrip). // Paths are Bezier curves resampled to arc length, flown as a full // profile: climb, cruise, descent, landing, ground hold, new leg. flights: { enabled: true, // false removes aircraft and contrails altitudeKm: 14, // cruise altitude climbKm: 100, // climb/descent run to and from cruise count: 21, // simultaneous aircraft (max 240) speedKmh: 850, // mean ground speed speedVar: 0.18, // +/- speed deviation (0..0.5) minLegKm: 60, // shortest leg (no taxi hops) holdMinS: 50, // ground hold at the destination holdMaxS: 150, wHub: 4, // destination weights wField: 2, wStrip: 1, // Contrails: contrailChance of the aircraft leaves a trail once // above contrailStartKm, visible for contrailLifeS seconds. contrailChance: 1.00, // 0 = no contrails, 1 = every eligible jet contrailStartKm: 4, // start after this distance from the airport contrailLifeS: 300, // seconds before a trail disappears contrailWidthM: 1400, // full trail width contrailFadeKm: 300, // fully faded at this distance (ramp starts at 35 %) // contrailChance: 0.35, contrailLifeS: 120 // short sparse trails // Night contrast: a physically scaled contrail is a faint grey thread // that disappears at 4x pixel scale, so this is the night share of its // opacity (0.34 was the old value) and it also drives the gain on the // moon/star light. 0 = night contrails off, 1 = strongest. contrailNight: 0.85, // Night strobes. Point size shrinks with distance but never below // lightPx and never above lightMaxPx for a jet directly overhead. // The white strobe is a double flash: two flashes blinkFlashS long, // blinkGapS apart, then the rest of blinkPeriodS is dark. lightPx: 7, lightMaxPx: 16, lightBoost: 9, blinkPeriodS: 1.4, blinkGapS: 0.16, blinkFlashS: 0.08 // flights: false // remove the aviation layer completely }, // ------------------------------------------------------------------ // 3D ground // ------------------------------------------------------------------ // Copernicus DEM via terrarium tiles (z10), mesh built from the same // height field the terrain shadows are cast against. terrain: { enabled: true, // false = flat plane (no DEM, no terrain shadows) // terrain: false // completely disable the 3D ground exaggeration: 8, // vertical scale (1 = real, nearly invisible here) edgeFadeTiles: 4, // soft rim at the mosaic border, in tiles (~13 km) edgeSkirtKm: 800, // coarse outer terrain pulled past the horizon edgeSkirtRings: 24, // coarse rings in the outer skirt edgeSkirtNoise: 0.28, // fallback height variation if outer DEM is off outerDem: { enabled: true, // false uses procedural fallback heights zoom: 7, // reduced-resolution terrarium zoom for the skirt maxMeters: 2500 // height normalization for skirt colors // outerDem: { enabled: false } }, carLights: true, // moving vehicle lights on the real road network cityLights: true, // settlement lights from OSM place nodes wireframe: false // true = draw the mesh edges (orientation debug) }, // ------------------------------------------------------------------ // Vehicle lights // ------------------------------------------------------------------ // Roads come from Overpass (motorway/trunk for the A-net, primary/ // secondary for the side layer), cached per grid cell. carLights: { // All distances are real metres/km unless the name says Px. roadWidthM: 24, // real carriageway width of the road line bandPx: 1.2, // pixel floor of the road line bandBrightness: 0.10, // road line glow (the 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 // streams never collapse into one line // Traffic lives on a disc around CONFIG.location. Roads outside // rangeKm carry no vehicles; fadeKm/fadeSpanKm are the matching // distance fade (start, and how far past it nothing is left) - keep // them at or beyond rangeKm so the disc never ends in a hard cut. rangeKm: 200, fadeKm: 150, fadeSpanKm: 60, // 'auto' derives the driving side from the location (left in the UK, // Japan, Australia, India, southern Africa, ...), 'left'/'right' forces it. trafficSide: 'auto', dashM: 620, // mean vehicle spacing on motorways // side roads use dashM * 2.4 // dashM: 350 // denser traffic // dashM: 1200 // sparse traffic spreadKm: 45, // gaussian sigma around the motorway network spreadFloor: 0.30, // share that far side roads keep speed: 1.0, // flow speed multiplier density: 1.0, // vehicle density multiplier brightness: 1.3, // vehicle light intensity // Time-based traffic profile. Values are multipliers around the // configured density. Times are local scene-clock hours. timeProfile: { enabled: true, weekday: 1.0, // Mon-Fri base traffic saturday: 0.65, // weekend reduction sunday: 0.55, // weekend reduction morningPeak: 7.7, // rush-hour morning peak morningWidth: 1.2, // gaussian sigma in hours morningGain: 0.55, // +55 % at the peak eveningPeak: 17.5, // rush-hour evening peak eveningWidth: 1.4, eveningGain: 0.75, // +75 % at the peak fridayEveningGain: 0.35, sundayEveningPeak: 18.5, sundayEveningWidth: 1.8, sundayEveningGain: 0.85, // commuter return traffic nightReduction: 0.70 // deep-night traffic reduced to 30 % // timeProfile: { enabled: false } // timeProfile: { morningGain: 0.8, eveningGain: 1.0, nightReduction: 0.85 } } // speed: 2.0, density: 0.6, brightness: 1.0 }, // ------------------------------------------------------------------ // Artificial light switching // ------------------------------------------------------------------ // Every single light (vehicle, city dot, ground block) gets its own // switch altitude inside this window, so a city does not ignite as one // piece. offDeg +5 deg is about 30 min before sunset here, onDeg -6 deg // is civil twilight. In the morning the same thresholds switch off again. lights: { offDeg: 5, // sun altitude where lights start switching off onDeg: -6, // sun altitude where lights are fully on softDeg: 1.2 // width of the individual transition // offDeg: 8, onDeg: -4, softDeg: 2.0 // earlier, softer twilight }, // ------------------------------------------------------------------ // Physical light model // ------------------------------------------------------------------ // Direct beam through a Kasten-Young air mass with Rayleigh + ozone + // aerosol extinction; diffuse skylight, moonlight, starlight floor and // aerial perspective all come from the same model, and sky, clouds, // terrain, contrails and aircraft share one light state per frame. atmosphere: { turbidity: 1.0, // aerosol load of the extinction moonGain: 0.60, // moonlight, scaled by the illuminated fraction starGain: 0.18, // stylised starlight + airglow (real integrated // starlight would be invisible) starGainGround: 0.4, // extra share on the ground; the product must // stay below the skylight at sunset nightFloor: 0.10, // darkest skylight before starlight takes over albedoGain: 1.0, // theme share on surface colours skyGain: 0.45, // diffuse skylight share on surfaces airGain: 0.55, // Rayleigh airlight strength (blue-biased) physicalSky: true, // Rayleigh sky scattering instead of pure theme gradient rayleighGain: 8.0, // exposure for the physical sky term haze: { enabled: false, // full-screen haze pass causes horizon/sky flicker heightKm: 100, scaleHeightKm: 8.5, density: 0.18, grayTau: 1.2, gain: 1.0, maxAlpha: 0.55 }, terrainHaze: { enabled: true, // terrain-distance based haze on the ground scaleHeightKm: 8.5, // exponential vertical falloff density: 0.22, // optical depth per world unit gain: 1.0, // overall multiplier maxAlpha: 0.65, // maximum haze opacity color: [0.58, 0.64, 0.72] // fallback colour; dynamic night tint overrides // terrainHaze: { enabled: false } // terrainHaze: { density: 0.35, maxAlpha: 0.80 } }, airHaze: { enabled: false, // replaced by stable front haze using terrain distance scaleHeightKm: 8.5, density: 0.16, gain: 1.0, maxAlpha: 0.45, color: [0.58, 0.64, 0.72] }, frontHaze: { enabled: true, // stable front haze pass using terrain distance scaleHeightKm: 8.5, density: 0.22, gain: 1.0, maxAlpha: 0.65, color: [0.58, 0.64, 0.72] // frontHaze: { enabled: false } // frontHaze: { density: 0.30, maxAlpha: 0.75 } }, model: { enabled: false // legacy full-screen atmosphere model // model: { enabled: true, heightKm: 80, density: 0.25 } }, hazeResidual: 0.30, // surface colour surviving at the full fog distance cloudBrightness: 1.0, // cloud albedo (theme hue, brightness from the model) cloudHaze: 0.22, // share of the ground haze applied to the deck // cloudBrightness: 1.35 // brighter cloud tops // cloudHaze: 0.10 // clouds stay cleaner against the haze // Cloud shadows: 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 deck also blocks // cloudShadows: false // cloudShadowDensity: 2.6 // stronger moving cloud shade // cloudShadowFloor: 0.18 // softer, less contrasty shade // Terrain self-shadowing over the DEM height field. terrainShadows: true, terrainShadowFloor: 0.42, // brightness a fully shadowed slope keeps (0 = black) // terrainShadows: false // terrainShadowFloor: 0.25 // deeper ridge shadows // Optional variants (leave commented out to use the defaults): // turbidity: 3.0 // hazy, warm, strongly reddened low sun // moonGain: 1.2 // bright full-moon nights // starGain: 0.30 // stronger starcast on clouds and contrails // hazeResidual: 0.10 // flatter, milkier distance haze // cloudShadows: false // no moving cloud shade on the ground // terrainShadows: false // no ridge shadows }, // ------------------------------------------------------------------ // Star catalogue // ------------------------------------------------------------------ stars: { tint: 0.10, // 0 = pure white points, 1 = full blackbody colour brightness: 1.6 // overall glow of the catalogue points // stars: { tint: 0.35, brightness: 2.2 } // stronger colour and glow }, // ------------------------------------------------------------------ // Planets // ------------------------------------------------------------------ // Approximate Keplerian positions for Mercury..Pluto, rendered as // larger coloured points in the night sky. Positions are geocentric and // use the same simulated clock as sun/moon/stars. planets: { enabled: true, brightness: 1.0, // 0.5 = subtle, 2.0 = clearly readable sizeScale: 1.0, // 0.6 = small, 1.4 = prominent showPluto: true // Pluto is faint; false removes it // planets: { enabled: false } // planets: { brightness: 1.6, sizeScale: 1.25, showPluto: false } }, // ------------------------------------------------------------------ // Earth curvature // ------------------------------------------------------------------ curvature: { enabled: true, earthRadiusKm: 6371, // physical Earth radius used for the bend exaggeration: 4.0 // 1 = physically true, 4 = visually readable // curvature: { enabled: false } // flat world // curvature: { exaggeration: 2.0 } // gentler horizon drop }, // ------------------------------------------------------------------ // Sun // ------------------------------------------------------------------ // The real sun is 0.00465 rad (0.27 deg). Like the moon, the disc is // drawn larger so it reads as a disc; the surface brightness and the // scene lighting are NOT scaled with it. sun: { angularRadius: 0.0558 // 12x the physical 0.00465 rad }, // ------------------------------------------------------------------ // Camera // ------------------------------------------------------------------ // The camera always flies the configured path (figure eight or straight // drift); 'aim' changes only where it LOOKS. 'moon' turns the view to // the moon - heading to its azimuth, pitch to its altitude - while the // flight path, the fetch corridor and the traffic stay as they are. // Below the horizon the view holds the moon's azimuth at the horizon. camera: { aim: 'off', // 'off' | 'moon' // aim: 'moon' // look at the moon while flying the path aimEase: 0.94, // 0 = instant turn, 0.99 = very slow aimBob: 0.25, // share of the altitude bob kept while aiming // Banking when the flight path curves. The camera keeps flying the // same path; roll only rotates the view around the forward axis. roll: { enabled: true, gain: 12, // turn-rate to bank-angle gain maxDeg: 12, // maximum bank angle ease: 0.92, // 0 = instant bank, 0.99 = very slow sign: -1 // flip to +1 if the bank direction feels inverted // roll: { enabled: false } // roll: { gain: 20, maxDeg: 18 } } // camera: { aim: 'moon' } // look at the moon }, // ------------------------------------------------------------------ // Clock // ------------------------------------------------------------------ // Sun and moon are always the real ones (SunCalc ephemeris) - this block // only moves the clock that the astronomy is evaluated at. startAt picks // the moment the scene begins at, speed advances it from there: 1 = real // time, 1440 = one simulated day per minute. Clock hands, digital readout, // weather and sky all use the same clock. Hash overrides: #at=... #speed=... day: { startAt: '2026-10-13T06:59:00', // frozen test clock speed: 0 // frozen time // startAt: null, speed: 1 // real time }, // ------------------------------------------------------------------ // Debug helpers (off by default, documented so they are findable) // ------------------------------------------------------------------ // debug: 1 // show the debug panel (weather, shader // counters, theme palette, theme lighting) // tiles: { debug: true } // bake tile borders and x/y labels into the map // terrain: { wireframe: true } // draw the mesh edges // ground: { style: 'streets' } // labelled street map instead of satellite }; /* * Themes. * * Each entry is an object: * css stylesheet loaded for this theme (also the palette source: * the scene reads the CSS custom properties of the active theme) * lighting optional tint of the physical light model. The physics stays * the base; the theme only pulls the colours toward its own hue * by 'tint' (0 = pure physics, 1 = theme colour) and scales the * exposure with gain (day) and nightGain (night). * sun/moon/air/sky accept '#rgb' or '#rrggbb'. * Omit 'lighting' entirely for a purely physical theme. * * Add a theme by adding an entry here and dropping the CSS file next to * config.js - build.py inlines every stylesheet referenced here. */ window.THEMES = { 'ash': { css: 'ash.css', lighting: { tint: 0.28, sun: '#ffd9a6', // warm amber beam sky: '#5b6f96', // cool slate skylight air: '#7d93bd', // haze and horizon colour moon: '#c9d6ea', gain: 1.00, nightGain: 1.00 } }, 'nord': { css: 'nord.css', lighting: { tint: 0.45, sun: '#f2e3c0', sky: '#5e81ac', air: '#81a1c1', moon: '#d8e3ee', gain: 0.95, nightGain: 1.12 // nord nights read a little brighter } }, 'gruvbox': { css: 'gruvbox.css', lighting: { tint: 0.50, sun: '#fabd7f', sky: '#83a598', air: '#b8a27f', moon: '#e6d5ae', gain: 1.05, nightGain: 1.05 } } // A theme without a lighting block keeps the pure physical colours: // 'mono': { css: 'mono.css' } };