clouds3d-only cleanup: drop Vanta/three.js/clouds2d, real-sky fixes, settlement lights, dynamic location anchor

This commit is contained in:
Richard Zink committed 2026-10-09 14:13:03 +02:00
1 parent e4170bee62
commit fbac24773a
17 files changed
+3217 -3411

No files matched your search

-2
View File
@@ -6,8 +6,6 @@ COPY nord.css /usr/share/nginx/html/nord.css
COPY config.js /usr/share/nginx/html/config.js COPY config.js /usr/share/nginx/html/config.js
COPY i18n.js /usr/share/nginx/html/i18n.js COPY i18n.js /usr/share/nginx/html/i18n.js
COPY background.html /usr/share/nginx/html/background.html COPY background.html /usr/share/nginx/html/background.html
COPY noise.png /usr/share/nginx/html/noise.png
COPY ground.jpg /usr/share/nginx/html/ground.jpg
COPY vendor/ /usr/share/nginx/html/vendor/ COPY vendor/ /usr/share/nginx/html/vendor/
EXPOSE 58881 EXPOSE 58881
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]
+19 -9
View File
@@ -4,6 +4,10 @@ An animated desktop background that runs in a browser. Features:
- Time-of-day simulation with realistic color gradients driven by the - Time-of-day simulation with realistic color gradients driven by the
actual sun position (computed from latitude/longitude via SunCalc). actual sun position (computed from latitude/longitude via SunCalc).
- WebGL flight scene (`vendor/clouds3d.js`): raymarched cloud deck, 3D
terrain from Copernicus DEM, real star sky (Yale BSC + Milky Way),
moon with phases, animated highway car lights and settlement lights
from OpenStreetMap/Overpass at night.
- Night brightness coupled to the moon phase (new moon = minimum, full - Night brightness coupled to the moon phase (new moon = minimum, full
moon = maximum), with a configurable minimum so the night is never moon = maximum), with a configurable minimum so the night is never
pitch-black. pitch-black.
@@ -17,7 +21,7 @@ An animated desktop background that runs in a browser. Features:
to add a new theme. to add a new theme.
- Customisable location: set `CONFIG.location.lat/lon`, or let the - Customisable location: set `CONFIG.location.lat/lon`, or let the
browser's geolocation API pick one up automatically (falls back to browser's geolocation API pick one up automatically (falls back to
London). the built-in default Passau).
- Optional custom location label (`CONFIG.location.label`) that - Optional custom location label (`CONFIG.location.label`) that
overrides the OpenStreetMap reverse-geocoding lookup. overrides the OpenStreetMap reverse-geocoding lookup.
- Configurable digital clock format string using ISO-8601-ish tokens - Configurable digital clock format string using ISO-8601-ish tokens
@@ -60,6 +64,11 @@ window.CONFIG = {
lon: 13.4319, // longitude, -180 to +180, east positive lon: 13.4319, // longitude, -180 to +180, east positive
label: '' // optional custom place name; label: '' // optional custom place name;
// if set, skips reverse-geocoding // if set, skips reverse-geocoding
},
orbit: { // omit/null = straight-line flight
radiusKm: 15, // circular flight around location;
// clamped to ~50 km at zoom 11
clockwise: true // turn direction
} }
}; };
``` ```
@@ -116,14 +125,13 @@ DDDD weekday full name (Monday, Montag, ...)
├── ash.css default color theme ├── ash.css default color theme
├── gruvbox.css alternative color theme ├── gruvbox.css alternative color theme
├── nord.css alternative color theme ├── nord.css alternative color theme
├── noise.png background noise texture for Vanta ├── vendor/ vendored third-party files (no network needed)
├── vendor/ vendored third-party JS (no network needed) │ ├── clouds3d.js WebGL scene: raymarched clouds, 3D terrain,
│ ├── three.min.js MIT (three.js) │ │ real sun/moon/stars, car + city lights
│ ├── suncalc.js ISC (SunCalc by mourner) │ ├── suncalc.js ISC (SunCalc by mourner)
│ ├── vanta.clouds.min.js MIT (Vanta.js Clouds) │ ├── stars.json Yale Bright Star Catalogue subset
│ ├── vanta.clouds2.min.js MIT (Vanta.js Clouds2)
│ └── LICENSE per-library license attributions │ └── LICENSE per-library license attributions
├── default.conf nginx server config (port 58881) ├── default.conf nginx server config (port 58881, tile CORS proxies)
├── Dockerfile builds nginx:alpine image ├── Dockerfile builds nginx:alpine image
└── docker-compose.yml one-service Compose file └── docker-compose.yml one-service Compose file
``` ```
@@ -131,9 +139,11 @@ DDDD weekday full name (Monday, Montag, ...)
## Licenses ## Licenses
- **Background application code**: MIT - **Background application code**: MIT
- **three.js** (vendored): MIT - <https://github.com/mrdoob/three.js>
- **SunCalc** (vendored): ISC - <https://github.com/mourner/suncalc> - **SunCalc** (vendored): ISC - <https://github.com/mourner/suncalc>
- **Vanta.js** (vendored): MIT - <https://github.com/tengbao/vanta>
- **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, Overpass road/place data): ODbL -
<https://www.openstreetmap.org/copyright>
- **Copernicus DEM** (terrain via terrarium tiles): open data
- **NASA GIBS VIIRS** (optional night-lights overlay): public domain
- **OpenStreetMap Nominatim** (reverse geocoding): ODbL - - **OpenStreetMap Nominatim** (reverse geocoding): ODbL -
<https://nominatim.org/release/> <https://nominatim.org/release/>
File diff suppressed because it is too large. Load diff
+310 -110
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Background Vanta Clouds2</title> <title>Background Clouds3D</title>
<script src="config.js"></script> <script src="config.js"></script>
<script src="i18n.js"></script> <script src="i18n.js"></script>
<script> <script>
@@ -18,7 +18,7 @@
<script src="vendor/suncalc.js"></script> <script src="vendor/suncalc.js"></script>
<style> <style>
html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; font-family: monospace; } html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; font-family: monospace; }
#vanta { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; } #scene { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; }
#debug-panel { #debug-panel {
position: fixed; position: fixed;
bottom: 2vh; bottom: 2vh;
@@ -291,6 +291,14 @@
color: var(--icon); color: var(--icon);
line-height: 1.3; line-height: 1.3;
} }
.credits {
margin-top: 1.2vh;
font-size: 0.72em;
color: var(--gray);
opacity: 0.75;
line-height: 1.45;
text-align: center;
}
.weather-day { .weather-day {
margin-bottom: 0.6vh; margin-bottom: 0.6vh;
} }
@@ -338,9 +346,9 @@
</style> </style>
</head> </head>
<body> <body>
<div id="vanta" style="background: #1c1b1f;"></div> <div id="scene"></div>
<div id="debug-panel"> <div id="debug-panel">
<div class="debug-title expanded" onclick="toggleDebugPanel()">\n<span>DEBUG - Palette: <span id="dbg-palette-name">Ash</span></span><span class="debug-title-toggle" id="debug-toggle-icon">▼</span></div> <div class="debug-title expanded" onclick="toggleDebugPanel()">\n<span>DEBUG · <span id="dbg-fps">--</span> fps</span><span class="debug-title-toggle" id="debug-toggle-icon">▼</span></div>
<div id="debug-content" class="debug-content"> <div id="debug-content" class="debug-content">
<div class="debug-controls"> <div class="debug-controls">
<select id="dbg-theme-select" class="debug-select" onchange="setDebugTheme(this.value)"> <select id="dbg-theme-select" class="debug-select" onchange="setDebugTheme(this.value)">
@@ -380,7 +388,7 @@
<div class="debug-row"><span class="libel">cloudColor</span> <div class="swatch" id="dbg-t-cloud"></div> <span class="hex" id="dbg-h-cloud">--</span></div> <div class="debug-row"><span class="libel">cloudColor</span> <div class="swatch" id="dbg-t-cloud"></div> <span class="hex" id="dbg-h-cloud">--</span></div>
<div class="debug-row"><span class="libel">lightColor</span> <div class="swatch" id="dbg-t-sunLight"></div> <span class="hex" id="dbg-h-sunLight">--</span></div> <div class="debug-row"><span class="libel">lightColor</span> <div class="swatch" id="dbg-t-sunLight"></div> <span class="hex" id="dbg-h-sunLight">--</span></div>
<div class="debug-row"><span class="libel">bgColor</span> <div class="swatch" id="dbg-t-bg"></div> <span class="hex" id="dbg-h-bg">--</span></div> <div class="debug-row"><span class="libel">bgColor</span> <div class="swatch" id="dbg-t-bg"></div> <span class="hex" id="dbg-h-bg">--</span></div>
<div class="debug-section">Vanta colors</div> <div class="debug-section">Scene palette</div>
<div class="debug-row"><span class="libel">skyColor</span> <div class="swatch" id="dbg-v-sky"></div> <span class="hex" id="dbg-vh-sky">--</span></div> <div class="debug-row"><span class="libel">skyColor</span> <div class="swatch" id="dbg-v-sky"></div> <span class="hex" id="dbg-vh-sky">--</span></div>
<div class="debug-row"><span class="libel">cloudColor</span> <div class="swatch" id="dbg-v-cloud"></div> <span class="hex" id="dbg-vh-cloud">--</span></div> <div class="debug-row"><span class="libel">cloudColor</span> <div class="swatch" id="dbg-v-cloud"></div> <span class="hex" id="dbg-vh-cloud">--</span></div>
<div class="debug-row"><span class="libel">lightColor</span> <div class="swatch" id="dbg-v-sunLight"></div> <span class="hex" id="dbg-vh-sunLight">--</span></div> <div class="debug-row"><span class="libel">lightColor</span> <div class="swatch" id="dbg-v-sunLight"></div> <span class="hex" id="dbg-vh-sunLight">--</span></div>
@@ -460,6 +468,13 @@
<line id="hour-hand" x1="140" y1="140" x2="140" y2="49" stroke="var(--bone)" stroke-width="7" stroke-linecap="round" opacity="0.7"/> <line id="hour-hand" x1="140" y1="140" x2="140" y2="49" stroke="var(--bone)" stroke-width="7" stroke-linecap="round" opacity="0.7"/>
<line id="minute-hand" x1="140" y1="140" x2="140" y2="28" stroke="var(--bone)" stroke-width="5" stroke-linecap="round" opacity="0.7"/> <line id="minute-hand" x1="140" y1="140" x2="140" y2="28" stroke="var(--bone)" stroke-width="5" stroke-linecap="round" opacity="0.7"/>
<line id="second-hand" x1="140" y1="140" x2="140" y2="20" stroke="var(--red)" stroke-width="2.5" stroke-linecap="round"/> <line id="second-hand" x1="140" y1="140" x2="140" y2="20" stroke="var(--red)" stroke-width="2.5" stroke-linecap="round"/>
<!-- Flight-direction compass needle: north tip points where the
camera looks (driven by window.__flightBearing from clouds3d) -->
<g id="compass-needle" transform="rotate(0 140 140)" style="display:none">
<polygon points="140,103 134,140 140,146 146,140" fill="var(--red)" opacity="0.9"/>
<polygon points="140,177 134,140 140,134 146,140" fill="var(--stone)" opacity="0.6"/>
<circle cx="140" cy="140" r="6" fill="var(--dark)" stroke="var(--bone)" stroke-width="1.5"/>
</g>
<circle cx="140" cy="140" r="5" fill="var(--red)"/> <circle cx="140" cy="140" r="5" fill="var(--red)"/>
</svg> </svg>
<div id="digital-clock">00:00:00</div> <div id="digital-clock">00:00:00</div>
@@ -502,20 +517,14 @@
<div id="weather-divider" class="weather-divider"></div> <div id="weather-divider" class="weather-divider"></div>
<div id="weather-current" class="weather-current"></div> <div id="weather-current" class="weather-current"></div>
<div id="weather-forecast" class="weather-forecast"></div> <div id="weather-forecast" class="weather-forecast"></div>
<div id="credits" class="credits"></div>
</div> </div>
<script src="vendor/three.min.js"></script>
<script src="vendor/vanta.clouds.min.js"></script>
<script src="vendor/vanta.clouds2.min.js"></script>
<script src="vendor/clouds3d.js"></script> <script src="vendor/clouds3d.js"></script>
<script src="vendor/clouds2d.js"></script>
<script> <script>
window.__clouds2dStarted = true; window.__cloudsStarted = true;
try { try {
window.__noiseDataUri = 'data:image/png;base64,PLACEHOLDER';
var noiseDataUri = 'data:image/png;base64,PLACEHOLDER';
// Parse URL hash params: #key=value&key=value... // Parse URL hash params: #key=value&key=value...
// Supported: #renderer=clouds2d|clouds3d|vanta, #flight=N|NE|E|SE|S|SW|W|NW, // Supported: #flight=N|NE|E|SE|S|SW|W|NW, #fetch=N|NE|E|SE|S|SW|W|NW
// #fetch=N|NE|E|SE|S|SW|W|NW
function _parseHashParams(hash) { function _parseHashParams(hash) {
var out = {}; var out = {};
hash = (hash || '').replace(/^#/, ''); hash = (hash || '').replace(/^#/, '');
@@ -530,10 +539,6 @@ function _parseHashParams(hash) {
} }
var _hashParams = _parseHashParams(location.hash); var _hashParams = _parseHashParams(location.hash);
// Renderer override
var _urlRenderer = _hashParams.renderer;
var renderer = _urlRenderer || (window.CONFIG && window.CONFIG.renderer) || 'vanta';
// Compass directions for flight and fetch (independent). Each direction // Compass directions for flight and fetch (independent). Each direction
// has cos/sin (corridor rotation matrix) and dx/dz (camera motion unit vec). // has cos/sin (corridor rotation matrix) and dx/dz (camera motion unit vec).
var FLIGHT_DIRS = { var FLIGHT_DIRS = {
@@ -552,48 +557,20 @@ function _pickDir(key) {
} }
window.__flightDir = _pickDir('flight'); window.__flightDir = _pickDir('flight');
window.__fetchDir = _pickDir('fetch'); window.__fetchDir = _pickDir('fetch');
var useVanta = (renderer !== 'clouds2d' && renderer !== 'clouds3d');
var effect; // ---- WebGL scene (clouds3d.js) ------------------------------------------
if (useVanta && typeof VANTA !== 'undefined') { var sceneEl = document.getElementById('scene');
effect = (VANTA.CLOUDS2 || VANTA.CLOUDS)({ function sceneRender() {
el: "#vanta", if (sceneEl && typeof window.clouds3dRender === 'function') {
mouseControls: false, window.clouds3dRender(sceneEl);
touchControls: false,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 4,
speed: 1.0,
backgroundColor: 0x1c1b1f,
backgroundAlpha: 1,
skyColor: 0x3a4458,
cloudColor: 0x6d6a63,
lightColor: 0xe6dfd1,
texturePath: noiseDataUri
}) || { setOptions: function() {} };
} else {
// Pick the right renderer based on CONFIG.renderer
var _renderFn = (renderer === 'clouds3d' && window.clouds3dRender)
? window.clouds3dRender
: window.clouds2dRender;
effect = { setOptions: function() {} };
function c2dRender() {
var c2dCanvas = document.getElementById('vanta');
if (!c2dCanvas) return;
if (typeof _renderFn === 'function') {
_renderFn(c2dCanvas);
}
} }
c2dRender();
window.addEventListener('resize', c2dRender);
// Continuously update animation via rAF
function c2dLoop() {
c2dRender();
window._clouds2dRaf = requestAnimationFrame(c2dLoop);
}
window._clouds2dRaf = requestAnimationFrame(c2dLoop);
} }
sceneRender();
window.addEventListener('resize', sceneRender);
(function sceneLoop() {
sceneRender();
window._sceneRaf = requestAnimationFrame(sceneLoop);
})();
function hexToRgb(h) { function hexToRgb(h) {
return [(h >> 16) & 255, (h >> 8) & 255, h & 255]; return [(h >> 16) & 255, (h >> 8) & 255, h & 255];
@@ -612,10 +589,26 @@ if (useVanta && typeof VANTA !== 'undefined') {
return t * t * (3 - 2 * t); return t * t * (3 - 2 * t);
} }
var CYCLE = 20000; // Day simulation: CONFIG.day.simulation runs a full 24h cycle in
// CONFIG.day.cycleSeconds (sun rises east, culminates south, sets
// west). Off = real SunCalc positions.
var DAY_SIM = !!(window.CONFIG && window.CONFIG.day && window.CONFIG.day.simulation);
var CYCLE = ((window.CONFIG && window.CONFIG.day && window.CONFIG.day.cycleSeconds) || 20) * 1000;
// simulated sun altitude (rad): t=0 midnight, 0.25 sunrise, 0.5 noon
function simSunAlt(t) {
return Math.sin((t - 0.25) * Math.PI * 2) * 1.45;
}
// Virtual calendar for day-switching features (weather forecast):
// same wall clock as reality, but the DATE advances one day per
// simulated cycle. The visible clock hands/digital stay real.
function simNow() {
if (!DAY_SIM) return new Date();
return new Date(startTime + (Date.now() - startTime) * (86400000 / CYCLE));
}
var __simT = 0;
var PASSAU_LAT = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lat) || 48.5667; var SITE_LAT = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lat) || 48.5667;
var PASSAU_LON = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lon) || 13.4319; var SITE_LON = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lon) || 13.4319;
var realDayProgress = 0; var realDayProgress = 0;
@@ -839,9 +832,13 @@ window.sunPalettes = [
} }
function getColor(t) { function getColor(t) {
// t is no longer used - now uses real sun/moon if (DAY_SIM) {
// t: 0 = Mitternacht, 0.25 Sonnenaufgang (Osten),
// 0.5 Mittag (Sueden), 0.75 Sonnenuntergang (Westen).
return getDayColor(simSunAlt(t));
}
var now = new Date(); var now = new Date();
var sunPos = SunCalc.getPosition(now, PASSAU_LAT, PASSAU_LON); var sunPos = SunCalc.getPosition(now, SITE_LAT, SITE_LON);
var moonIllum = SunCalc.getMoonIllumination(now); var moonIllum = SunCalc.getMoonIllumination(now);
// The day palette covers sunAlt down to sunPalettes[0].alt // The day palette covers sunAlt down to sunPalettes[0].alt
@@ -918,6 +915,7 @@ window.sunPalettes = [
var hourHand = document.getElementById('hour-hand'); var hourHand = document.getElementById('hour-hand');
var minuteHand = document.getElementById('minute-hand'); var minuteHand = document.getElementById('minute-hand');
var secondHand = document.getElementById('second-hand'); var secondHand = document.getElementById('second-hand');
var compassNeedle = document.getElementById('compass-needle');
var hourMarks = document.getElementById('hour-marks'); var hourMarks = document.getElementById('hour-marks');
var rpgSunArc = document.getElementById('sun-arc'); var rpgSunArc = document.getElementById('sun-arc');
var rpgMoonArc = document.getElementById('moon-arc'); var rpgMoonArc = document.getElementById('moon-arc');
@@ -1018,9 +1016,7 @@ window.sunPalettes = [
result += date.getFullYear(); result += date.getFullYear();
i += 1; i += 1;
} else if (ch === 'M' && format.substr(i, 4) === 'MMMM') { } else if (ch === 'M' && format.substr(i, 4) === 'MMMM') {
var tMonthFull = t(monthKeys[date.getMonth()]); result += t(monthKeys[date.getMonth()]);
console.log('MMMM token', date.getMonth(), '->', tMonthFull, 'lang=', window.CONFIG && window.CONFIG.language);
result += tMonthFull;
i += 4; i += 4;
} else if (ch === 'M' && format.substr(i, 3) === 'MMM') { } else if (ch === 'M' && format.substr(i, 3) === 'MMM') {
result += t(monthKeys[date.getMonth()].substring(0, 3)); result += t(monthKeys[date.getMonth()].substring(0, 3));
@@ -1279,7 +1275,10 @@ window.sunPalettes = [
// Calculate real day progress based on sun position // Calculate real day progress based on sun position
// 0% = midnight, 50% = noon, 100% = next midnight // 0% = midnight, 50% = noon, 100% = next midnight
var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON); if (DAY_SIM) {
realDayProgress = __simT;
} else {
var sunTimes = SunCalc.getTimes(now, SITE_LAT, SITE_LON);
var sunrise = sunTimes.sunrise; var sunrise = sunTimes.sunrise;
var sunset = sunTimes.sunset; var sunset = sunTimes.sunset;
if (now < sunrise) { if (now < sunrise) {
@@ -1290,7 +1289,8 @@ window.sunPalettes = [
} else { } else {
realDayProgress = 0.5 + (now.getTime() - sunset.getTime()) / (sunrise.getTime() + 24 * 3600 * 1000 - sunset.getTime()) * 0.5; realDayProgress = 0.5 + (now.getTime() - sunset.getTime()) / (sunrise.getTime() + 24 * 3600 * 1000 - sunset.getTime()) * 0.5;
} }
realDayProgress = Math.max(0, Math.min(1, realDayProgress)); realDayProgress = Math.max(0, Math.min(1, realDayProgress));
}
// Real-time clock hands and digital // Real-time clock hands and digital
var h = now.getHours(); var h = now.getHours();
@@ -1308,9 +1308,9 @@ window.sunPalettes = [
secondHand.setAttribute('transform', 'rotate(' + secondAngle + ' 140 140)'); secondHand.setAttribute('transform', 'rotate(' + secondAngle + ' 140 140)');
// Sun times // Sun times
var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON); var sunTimes = SunCalc.getTimes(now, SITE_LAT, SITE_LON);
var sunriseNext = getNextEvent(sunTimes.sunrise, now, PASSAU_LAT, PASSAU_LON, 'sunrise'); var sunriseNext = getNextEvent(sunTimes.sunrise, now, SITE_LAT, SITE_LON, 'sunrise');
var sunsetNext = getNextEvent(sunTimes.sunset, now, PASSAU_LAT, PASSAU_LON, 'sunset'); var sunsetNext = getNextEvent(sunTimes.sunset, now, SITE_LAT, SITE_LON, 'sunset');
document.getElementById('sunrise-value').textContent = formatTimeHM(sunriseNext.date); document.getElementById('sunrise-value').textContent = formatTimeHM(sunriseNext.date);
document.getElementById('sunrise-when').textContent = sunriseNext.label ? '· ' + sunriseNext.label : ''; document.getElementById('sunrise-when').textContent = sunriseNext.label ? '· ' + sunriseNext.label : '';
@@ -1323,16 +1323,16 @@ window.sunPalettes = [
if (sunZenith <= now) { if (sunZenith <= now) {
var tomorrow = new Date(now); var tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setDate(tomorrow.getDate() + 1);
sunZenith = SunCalc.getTimes(tomorrow, PASSAU_LAT, PASSAU_LON).solarNoon; sunZenith = SunCalc.getTimes(tomorrow, SITE_LAT, SITE_LON).solarNoon;
sunZenithWhen = formatDateLong(tomorrow); sunZenithWhen = formatDateLong(tomorrow);
} }
document.getElementById('sun-zenith-value').textContent = formatTimeHM(sunZenith); document.getElementById('sun-zenith-value').textContent = formatTimeHM(sunZenith);
document.getElementById('sun-zenith-when').textContent = '· ' + sunZenithWhen; document.getElementById('sun-zenith-when').textContent = '· ' + sunZenithWhen;
// Moon times // Moon times
var moonTimes = SunCalc.getMoonTimes(now, PASSAU_LAT, PASSAU_LON); var moonTimes = SunCalc.getMoonTimes(now, SITE_LAT, SITE_LON);
var moonriseNext = getNextEvent(moonTimes.rise, now, PASSAU_LAT, PASSAU_LON, 'moonrise'); var moonriseNext = getNextEvent(moonTimes.rise, now, SITE_LAT, SITE_LON, 'moonrise');
var moonsetNext = getNextEvent(moonTimes.set, now, PASSAU_LAT, PASSAU_LON, 'moonset'); var moonsetNext = getNextEvent(moonTimes.set, now, SITE_LAT, SITE_LON, 'moonset');
document.getElementById('moonrise-value').textContent = formatTimeHM(moonriseNext.date); document.getElementById('moonrise-value').textContent = formatTimeHM(moonriseNext.date);
document.getElementById('moonrise-when').textContent = moonriseNext.label ? '· ' + moonriseNext.label : ''; document.getElementById('moonrise-when').textContent = moonriseNext.label ? '· ' + moonriseNext.label : '';
@@ -1388,7 +1388,7 @@ window.sunPalettes = [
sunSetArrow.setAttribute('transform', 'translate(' + ssP.x + ', ' + ssP.y + ')'); sunSetArrow.setAttribute('transform', 'translate(' + ssP.x + ', ' + ssP.y + ')');
// Sun zenith marker (next occurrence) // Sun zenith marker (next occurrence)
var sunZenithDate = sunTimes.solarNoon > now ? sunTimes.solarNoon : SunCalc.getTimes(new Date(now.getTime() + 86400000), PASSAU_LAT, PASSAU_LON).solarNoon; var sunZenithDate = sunTimes.solarNoon > now ? sunTimes.solarNoon : SunCalc.getTimes(new Date(now.getTime() + 86400000), SITE_LAT, SITE_LON).solarNoon;
var szP = pointOnCircle(sunZenithDate, SUN_ORBIT_RADIUS); var szP = pointOnCircle(sunZenithDate, SUN_ORBIT_RADIUS);
sunZenithMarker.setAttribute('transform', 'translate(' + szP.x + ', ' + szP.y + ')'); sunZenithMarker.setAttribute('transform', 'translate(' + szP.x + ', ' + szP.y + ')');
@@ -1494,7 +1494,7 @@ window.weatherThemes = {
// ---- Weather simulation ----------------------------------------- // ---- Weather simulation -----------------------------------------
// Cycles through a list of weather states automatically. Useful // Cycles through a list of weather states automatically. Useful
// for A/B testing the renderer against every theme without // for A/B testing the scene against every theme without
// waiting on the open-meteo API. // waiting on the open-meteo API.
var simWeathers = ['sunny', 'cloudy', 'stormy', 'night']; var simWeathers = ['sunny', 'cloudy', 'stormy', 'night'];
var simIndex = 0; var simIndex = 0;
@@ -1565,6 +1565,14 @@ window.weatherThemes = {
result[k] = targetThemeColors[k]; result[k] = targetThemeColors[k];
} }
} }
if (prevThemeColors && prevThemeColors.speed !== undefined &&
targetThemeColors.speed !== undefined) {
// ramp cloud speed with the colour transition instead of
// stepping it when the transition completes (a speed step
// reads as a sudden jump of the whole scene)
currentThemeSpeed = prevThemeColors.speed +
(targetThemeColors.speed - prevThemeColors.speed) * smoothT;
}
if (smoothT >= 1 && prevThemeColors !== targetThemeColors) { if (smoothT >= 1 && prevThemeColors !== targetThemeColors) {
prevThemeColors = targetThemeColors; prevThemeColors = targetThemeColors;
currentThemeSpeed = targetThemeColors.speed; currentThemeSpeed = targetThemeColors.speed;
@@ -1572,6 +1580,29 @@ window.weatherThemes = {
return result; return result;
} }
// ---- Data credits (bottom of the info box, CONFIG.credits) ----
(function renderCredits() {
var el = document.getElementById('credits');
if (!el) return;
if (window.CONFIG && window.CONFIG.credits === false) {
el.style.display = 'none';
return;
}
var cfg = window.CONFIG || {};
var lines = [];
var style = (cfg.ground && cfg.ground.style) || 'streets';
lines.push('Basemap: ' + (style === 'satellite'
? 'Esri World Imagery' : 'Esri World Street Map'));
var t = cfg.terrain || {};
if (t.enabled !== false) {
lines.push('Terrain: Copernicus DEM via Mapzen Terrarium');
if (t.nightLights !== false) lines.push('Night lights: NASA GIBS VIIRS');
if (t.carLights !== false || t.cityLights !== false) lines.push('Roads: \u00a9 OpenStreetMap contributors (ODbL)');
}
lines.push('Weather: Open-Meteo');
el.innerHTML = lines.join('<br>');
})();
function applyWeatherTheme(c) { function applyWeatherTheme(c) {
var themeColors = getEffectiveThemeColors(); var themeColors = getEffectiveThemeColors();
if (!themeColors) return c; if (!themeColors) return c;
@@ -1579,8 +1610,9 @@ window.weatherThemes = {
// At night, weather only tints slightly - we don't want a // At night, weather only tints slightly - we don't want a
// 'cloudy' sky colour washing over deep night sky and // 'cloudy' sky colour washing over deep night sky and
// making it look bright. // making it look bright.
var _sunPos = SunCalc.getPosition(new Date(), PASSAU_LAT, PASSAU_LON); var _sunAlt = DAY_SIM ? (window.__currentSunAlt || 0)
var nightFactor = Math.max(0, Math.min(1, -_sunPos.altitude / 0.4)); : SunCalc.getPosition(new Date(), SITE_LAT, SITE_LON).altitude;
var nightFactor = Math.max(0, Math.min(1, -_sunAlt / 0.4));
// Daytime: 40% weather mix (was 60%) — keeps day/night // Daytime: 40% weather mix (was 60%) — keeps day/night
// palette visible underneath, weather only adds tint. // palette visible underneath, weather only adds tint.
var mix = 0.4 * (1 - nightFactor * 0.7); var mix = 0.4 * (1 - nightFactor * 0.7);
@@ -1702,7 +1734,8 @@ window.weatherThemes = {
if (css && cssFile) css.href = cssFile; if (css && cssFile) css.href = cssFile;
setTimeout(function() { setTimeout(function() {
rebuildFromColors(); rebuildFromColors();
document.getElementById('dbg-palette-name').textContent = theme.charAt(0).toUpperCase() + theme.slice(1); var pal = document.getElementById('dbg-palette-name');
if (pal) pal.textContent = theme.charAt(0).toUpperCase() + theme.slice(1);
}, 50); }, 50);
window.CONFIG.theme = theme; window.CONFIG.theme = theme;
} }
@@ -1713,18 +1746,19 @@ window.weatherThemes = {
if (typeof fetchWeather === 'function') fetchWeather(); if (typeof fetchWeather === 'function') fetchWeather();
} }
function updateDebug(t, palette, vantaColors, themeName, realDayProgress) { function updateDebug(t, palette, sceneColors, themeName, realDayProgress) {
var now = new Date(); var now = new Date();
var hh = now.getHours(), mm = now.getMinutes(), ss = now.getSeconds(); var hh = now.getHours(), mm = now.getMinutes(), ss = now.getSeconds();
document.getElementById('dbg-time').textContent = document.getElementById('dbg-time').textContent =
(hh < 10 ? '0' : '') + hh + ':' + (mm < 10 ? '0' : '') + mm + ':' + (ss < 10 ? '0' : '') + ss; (hh < 10 ? '0' : '') + hh + ':' + (mm < 10 ? '0' : '') + mm + ':' + (ss < 10 ? '0' : '') + ss;
document.getElementById('dbg-day').textContent = realDayProgress != null ? (realDayProgress * 100).toFixed(1) + '%' : (t * 100).toFixed(1) + '%'; document.getElementById('dbg-day').textContent = realDayProgress != null ? (realDayProgress * 100).toFixed(1) + '%' : (t * 100).toFixed(1) + '%';
document.getElementById('dbg-weather').textContent = document.getElementById('dbg-weather').textContent =
currentWeatherCode + ' (' + (window.weatherThemes[themeName] ? themeName : '?') + ')'; currentWeatherCode + ' (' + (window.weatherThemes[themeName] ? themeName : '?') + ')'
+ (window.__weatherStale ? ' cache' : '');
// RPG angles // RPG angles
var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON); var sunTimes = SunCalc.getTimes(now, SITE_LAT, SITE_LON);
var moonTimes = SunCalc.getMoonTimes(now, PASSAU_LAT, PASSAU_LON); var moonTimes = SunCalc.getMoonTimes(now, SITE_LAT, SITE_LON);
document.getElementById('dbg-sun-rise-angle').textContent = (angleForTime(sunTimes.sunrise) * 180 / Math.PI).toFixed(1) + '°'; document.getElementById('dbg-sun-rise-angle').textContent = (angleForTime(sunTimes.sunrise) * 180 / Math.PI).toFixed(1) + '°';
document.getElementById('dbg-sun-set-angle').textContent = (angleForTime(sunTimes.sunset) * 180 / Math.PI).toFixed(1) + '°'; document.getElementById('dbg-sun-set-angle').textContent = (angleForTime(sunTimes.sunset) * 180 / Math.PI).toFixed(1) + '°';
document.getElementById('dbg-sun-now-angle').textContent = (angleForTime(now) * 180 / Math.PI).toFixed(1) + '°'; document.getElementById('dbg-sun-now-angle').textContent = (angleForTime(now) * 180 / Math.PI).toFixed(1) + '°';
@@ -1755,12 +1789,12 @@ window.weatherThemes = {
} }
} }
if (vantaColors) { if (sceneColors) {
var vKeys = ['sky', 'cloud', 'light', 'bg']; var vKeys = ['sky', 'cloud', 'light', 'bg'];
var vKeyMap = {sky: 'sky', cloud: 'cloud', light: 'sunLight', bg: 'bg'}; var vKeyMap = {sky: 'sky', cloud: 'cloud', light: 'sunLight', bg: 'bg'};
for (var j = 0; j < vKeys.length; j++) { for (var j = 0; j < vKeys.length; j++) {
var vk = vKeys[j]; var vk = vKeys[j];
var vc = vantaColors[vk]; var vc = sceneColors[vk];
var kk = vKeyMap[vk]; var kk = vKeyMap[vk];
document.getElementById('dbg-v-' + kk).style.backgroundColor = '#' + ('00000' + vc.toString(16)).slice(-6); document.getElementById('dbg-v-' + kk).style.backgroundColor = '#' + ('00000' + vc.toString(16)).slice(-6);
document.getElementById('dbg-vh-' + kk).textContent = '#' + ('00000' + vc.toString(16)).slice(-6); document.getElementById('dbg-vh-' + kk).textContent = '#' + ('00000' + vc.toString(16)).slice(-6);
@@ -1794,7 +1828,12 @@ window.weatherThemes = {
tableHtml += '<span class="theme-table-speed">' + th.speed + '</span>'; tableHtml += '<span class="theme-table-speed">' + th.speed + '</span>';
tableHtml += '</div>'; tableHtml += '</div>';
} }
document.getElementById('dbg-theme-table').innerHTML = tableHtml; var __sigTheme = themeName + '|' + currentWeatherCode + '|' +
((window.CONFIG && window.CONFIG.theme) || '');
if (__sigTheme !== window.__dbgThemeSig) {
window.__dbgThemeSig = __sigTheme;
document.getElementById('dbg-theme-table').innerHTML = tableHtml;
}
// Color palette table - all colors from the palette // Color palette table - all colors from the palette
var colorKeys = [ var colorKeys = [
@@ -1813,40 +1852,136 @@ window.weatherThemes = {
'<span class="theme-table-speed">' + cv + '</span>' + '<span class="theme-table-speed">' + cv + '</span>' +
'</div>'; '</div>';
} }
document.getElementById('dbg-color-table').innerHTML = colorHtml; var __sigPal = (window.CONFIG && window.CONFIG.theme) || '';
if (__sigPal !== window.__dbgPalSig) {
window.__dbgPalSig = __sigPal;
document.getElementById('dbg-color-table').innerHTML = colorHtml;
}
} }
var __fpsCount = 0, __fpsT0 = performance.now(), __dbgT0 = 0;
function animate() { function animate() {
var t = (Date.now() - startTime) / CYCLE % 1; var t = (Date.now() - startTime) / CYCLE % 1;
var c = getColor(t); var c = getColor(t);
c = applyWeatherTheme(c); c = applyWeatherTheme(c);
// Expose current palette + weather info for the 2D renderer // Expose current palette + weather info for the WebGL scene
window.__currentPalette = c; window.__currentPalette = c;
window.__currentWeatherName = currentWeatherThemeName; window.__currentWeatherName = currentWeatherThemeName;
window.__currentDayProgress = realDayProgress; window.__currentDayProgress = realDayProgress;
window.__currentThemeSpeed = currentThemeSpeed; window.__currentThemeSpeed = currentThemeSpeed;
// Sun altitude in radians. Negative = below horizon (night). // Sun altitude in radians. Negative = below horizon (night).
var _sunPos = SunCalc.getPosition(new Date(), PASSAU_LAT, PASSAU_LON); if (DAY_SIM) {
window.__currentSunAlt = _sunPos.altitude; __simT = t;
effect.setOptions({ window.__currentDayProgress = t;
skyColor: c.sky, // virtual weekday: starts at the real one and advances with
cloudColor: c.cloud, // every simulated day, so weekends pass by in the 20s cycle
lightColor: c.light, if (typeof window.__simDow !== 'number') {
backgroundColor: c.bg, window.__simDow = new Date().getDay();
backgroundAlpha: 0, window.__simLastT = t;
speed: currentThemeSpeed } else if (t < window.__simLastT) {
}); window.__simDow = (window.__simDow + 1) % 7;
document.body.style.backgroundColor = '#' + ('00000' + c.sky.toString(16)).slice(-6); }
updateDebug(t, null, c, currentWeatherThemeName, realDayProgress); window.__simLastT = t;
window.__flightDow = window.__simDow;
var _alt = simSunAlt(t);
window.__currentSunAlt = _alt;
// bearing: 6:00 (t=0.25) = east (pi/2), 12:00 = south (pi),
// 18:00 = west (3pi/2) -> bearing = 2*PI*t
var _sunBearing = t * Math.PI * 2;
window.__currentSunDir = [
-Math.cos(_alt) * Math.sin(_sunBearing),
Math.sin(_alt),
Math.cos(_alt) * Math.cos(_sunBearing)
];
} else {
var _sunPos = SunCalc.getPosition(new Date(), SITE_LAT, SITE_LON);
window.__currentSunAlt = _sunPos.altitude;
window.__flightDow = new Date().getDay();
// Unit vector towards the sun in world space (X east, Z north,
// Y up). SunCalc azimuth is measured from south, clockwise ->
// bearing from north = azimuth + PI.
var _sunBearingR = _sunPos.azimuth + Math.PI;
window.__currentSunDir = [
-Math.cos(_sunPos.altitude) * Math.sin(_sunBearingR),
Math.sin(_sunPos.altitude),
Math.cos(_sunPos.altitude) * Math.cos(_sunBearingR)
];
}
// ---- Moon direction ------------------------------------------
// Start elongation = real lunar phase (SunCalc: 0 new .. 0.5
// full); in day simulation the elongation advances 360/29.53
// deg per simulated day -> correct waxing/waning as cycles pass.
if (typeof window.__moonElong0 !== 'number') {
window.__moonElong0 = SunCalc.getMoonIllumination(new Date()).phase * Math.PI * 2;
window.__moonSimStartMs = Date.now();
}
var _simDays = DAY_SIM ? (Date.now() - window.__moonSimStartMs) / CYCLE : 0;
var _elong = window.__moonElong0 + _simDays * (Math.PI * 2 / 29.53);
var _sd = window.__currentSunDir;
var _cp = SITE_LAT * Math.PI / 180;
var _kp = [0, Math.sin(_cp), Math.cos(_cp)]; // celestial pole
var _ecs = Math.cos(_elong), _esn = Math.sin(_elong);
var _kdv = _kp[0]*_sd[0] + _kp[1]*_sd[1] + _kp[2]*_sd[2];
var _cx = _kp[1]*_sd[2] - _kp[2]*_sd[1];
var _cy = _kp[2]*_sd[0] - _kp[0]*_sd[2];
var _cz = _kp[0]*_sd[1] - _kp[1]*_sd[0];
window.__currentMoonDir = [
_sd[0]*_ecs + _cx*_esn + _kp[0]*_kdv*(1-_ecs),
_sd[1]*_ecs + _cy*_esn + _kp[1]*_kdv*(1-_ecs),
_sd[2]*_ecs + _cz*_esn + _kp[2]*_kdv*(1-_ecs)
];
var __skyHex = '#' + ('00000' + c.sky.toString(16)).slice(-6);
if (__skyHex !== window.__lastSkyHex) {
window.__lastSkyHex = __skyHex;
document.body.style.backgroundColor = __skyHex;
}
// Flight-direction compass needle (only while clouds3d feeds it)
if (compassNeedle) {
if (typeof window.__flightBearing === 'number') {
var _nd = (window.__flightBearing * 180 / Math.PI).toFixed(1);
if (_nd !== window.__lastNeedleDeg) {
window.__lastNeedleDeg = _nd;
compassNeedle.style.display = '';
compassNeedle.setAttribute('transform', 'rotate(' + _nd + ' 140 140)');
}
} else if (compassNeedle.style.display !== 'none') {
compassNeedle.style.display = 'none';
}
}
// FPS counter (rendered frames/second of the rAF loop)
__fpsCount++;
var __nowMs = performance.now();
if (__nowMs - __fpsT0 >= 1000) {
window.__fps = Math.round(__fpsCount * 1000 / (__nowMs - __fpsT0));
var __fpsEl = document.getElementById('dbg-fps');
if (__fpsEl) __fpsEl.textContent = window.__fps;
__fpsCount = 0;
__fpsT0 = __nowMs;
}
// Debug DOM writes are expensive; 4 Hz is plenty for readouts
if (__nowMs - __dbgT0 >= 250) {
__dbgT0 = __nowMs;
updateDebug(t, null, c, currentWeatherThemeName, realDayProgress);
}
requestAnimationFrame(animate); requestAnimationFrame(animate);
} }
var __lastWeatherHour = null;
function tick() { function tick() {
try { try {
updateClockAndEvents(); updateClockAndEvents();
} catch (e) { } catch (e) {
console.error('Tick error:', e); console.error('Tick error:', e);
} }
// simulated day/hour switch -> refresh the weather block
if (DAY_SIM && weatherData) {
var wh = Math.floor(simNow().getTime() / 3600000);
if (wh !== __lastWeatherHour) {
__lastWeatherHour = wh;
try { updateWeatherDisplay(); } catch (e) { console.error(e); }
}
}
setTimeout(tick, 1000); setTimeout(tick, 1000);
} }
@@ -1883,6 +2018,16 @@ window.weatherThemes = {
// set their own place name in CONFIG.location.label). // set their own place name in CONFIG.location.label).
function reverseGeocode(lat, lon) { function reverseGeocode(lat, lon) {
if (window.CONFIG && window.CONFIG.location && window.CONFIG.location.label) return; if (window.CONFIG && window.CONFIG.location && window.CONFIG.location.label) return;
// cached label first: works offline and avoids hitting
// Nominatim on every start
if (window.BackgroundCache) {
window.BackgroundCache.get('meta/geolabel').then(function(rec) {
if (rec && rec.value && window.CONFIG && window.CONFIG.location &&
!window.CONFIG.location.label) {
window.CONFIG.location.label = rec.value;
}
});
}
var url = 'https://nominatim.openstreetmap.org/reverse?format=json&lat=' + var url = 'https://nominatim.openstreetmap.org/reverse?format=json&lat=' +
lat + '&lon=' + lon + '&zoom=10&addressdetails=1'; lat + '&lon=' + lon + '&zoom=10&addressdetails=1';
fetch(url, { fetch(url, {
@@ -1897,6 +2042,9 @@ window.weatherThemes = {
if (window.CONFIG && window.CONFIG.location) { if (window.CONFIG && window.CONFIG.location) {
window.CONFIG.location.label = name; window.CONFIG.location.label = name;
} }
if (window.BackgroundCache) {
window.BackgroundCache.put('meta/geolabel', { value: name });
}
} }
}) })
.catch(function() { /* ignore errors */ }); .catch(function() { /* ignore errors */ });
@@ -2002,12 +2150,29 @@ window.weatherThemes = {
.then(function(response) { return response.json(); }) .then(function(response) { return response.json(); })
.then(function(data) { .then(function(data) {
weatherData = data; weatherData = data;
window.__weatherStale = false;
if (window.BackgroundCache) {
window.BackgroundCache.put('meta/weather', { json: data });
}
currentWeatherCode = data.current.weather_code; currentWeatherCode = data.current.weather_code;
setWeatherTheme(getWeatherThemeName(currentWeatherCode)); setWeatherTheme(getWeatherThemeName(currentWeatherCode));
updateWeatherDisplay(); updateWeatherDisplay();
}) })
.catch(function(err) { .catch(function(err) {
console.log('Weather fetch failed:', err); console.log('Weather fetch failed:', err);
// offline fallback: last stored payload keeps the
// widgets and weather-driven colours alive
if (window.BackgroundCache) {
window.BackgroundCache.get('meta/weather').then(function(rec) {
if (rec && rec.json && rec.json.current) {
weatherData = rec.json;
window.__weatherStale = true;
currentWeatherCode = rec.json.current.weather_code;
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
updateWeatherDisplay();
}
});
}
}); });
} }
@@ -2028,32 +2193,67 @@ window.weatherThemes = {
function updateWeatherDisplay() { function updateWeatherDisplay() {
if (!weatherData) return; if (!weatherData) return;
var now = new Date(); var now = simNow(); // virtual calendar: real day-of-week start,
// one simulated day per CYCLE (clock stays real!)
var current = weatherData.current; var current = weatherData.current;
var hourly = weatherData.hourly; var hourly = weatherData.hourly;
var daily = weatherData.daily; var daily = weatherData.daily;
// how many simulated days have passed since the API's 'today' -
// wrapped into the 5-day forecast window, so an ever-advancing
// sim keeps cycling fresh-looking conditions instead of freezing
var firstDay = new Date(daily.time[0]);
var spanMs = daily.time.length * 86400000;
var eff = now;
var wrapGuard = 0;
while (DAY_SIM && eff.getTime() >= firstDay.getTime() + spanMs && wrapGuard++ < 2000) {
eff = new Date(eff.getTime() - spanMs);
}
if (DAY_SIM && eff.getTime() < firstDay.getTime()) eff = firstDay;
var dayOffset = DAY_SIM
? Math.max(0, Math.min(daily.time.length - 4,
Math.floor((eff - firstDay) / 86400000)))
: 0;
// "Current": in simulation mode use the hourly value at the
// simulated time; theme follows it too (unless the weather-sim
// button has manual control)
var curTemp = current.temperature_2m;
var curCode = current.weather_code;
if (DAY_SIM) {
var curIdx = findHourlyIndex(eff, hourly.time);
curTemp = hourly.temperature_2m[curIdx];
curCode = hourly.weather_code[curIdx];
if (!__weatherSimActive && curCode !== currentWeatherCode) {
currentWeatherCode = curCode;
setWeatherTheme(getWeatherThemeName(curCode));
}
}
// Current // Current
var currentUnits = (window.CONFIG && window.CONFIG.units) || 'metric'; var currentUnits = (window.CONFIG && window.CONFIG.units) || 'metric';
var tempDisplay = currentUnits === 'imperial' var tempDisplay = currentUnits === 'imperial'
? Math.round(current.temperature_2m * 9 / 5 + 32) + '\u00b0F' ? Math.round(curTemp * 9 / 5 + 32) + '\u00b0F'
: Math.round(current.temperature_2m) + '\u00b0C'; : Math.round(curTemp) + '\u00b0C';
var windDisplay = currentUnits === 'imperial' var windDisplay = currentUnits === 'imperial'
? Math.round(current.wind_speed_10m * 0.621371) + ' mph' ? Math.round(current.wind_speed_10m * 0.621371) + ' mph'
: Math.round(current.wind_speed_10m) + ' km/h'; : Math.round(current.wind_speed_10m) + ' km/h';
var currentHtml = '<div class="weather-current-icon">' + weatherSVG(current.weather_code) + '</div>' + var currentHtml = '<div class="weather-current-icon">' + weatherSVG(curCode) + '</div>' +
'<div><div class="weather-current-temp"><span>' + tempDisplay + '</span><span class="weather-location" id="weather-location">' + '<div><div class="weather-current-temp"><span>' + tempDisplay + '</span><span class="weather-location" id="weather-location">' +
((window.CONFIG && window.CONFIG.location && (window.CONFIG.location.label || (window.CONFIG.location.lat.toFixed(2) + ',' + window.CONFIG.location.lon.toFixed(2)))) || '') + '</span></div>' + ((window.CONFIG && window.CONFIG.location && (window.CONFIG.location.label || (window.CONFIG.location.lat.toFixed(2) + ',' + window.CONFIG.location.lon.toFixed(2)))) || '') + '</span></div>' +
'<div class="weather-current-detail">' + '<div class="weather-current-detail">' +
t('Humidity') + ' ' + Math.round(current.relative_humidity_2m) + '% · ' + t('Wind') + ' ' + windDisplay + '</div></div>'; t('Humidity') + ' ' + Math.round(current.relative_humidity_2m) + '% · ' + t('Wind') + ' ' + windDisplay + '</div></div>';
document.getElementById('weather-current').innerHTML = currentHtml; document.getElementById('weather-current').innerHTML = currentHtml;
// Forecast // Forecast - window starts at the simulated "today" so the
// list shifts forward each simulated cycle (tomorrow becomes
// today, a new day enters at the end)
var forecastHtml = ''; var forecastHtml = '';
for (var d = 0; d < 4; d++) { for (var d = dayOffset; d < dayOffset + 4 && d < daily.time.length; d++) {
var dayDate = new Date(daily.time[d]); var dayDate = new Date(daily.time[d]);
var dayLabel = d === 0 ? t('today') : t('tomorrow'); var dayLabel = d === dayOffset ? t('today')
if (d > 1) { : (d === dayOffset + 1 ? t('tomorrow') : '');
if (!dayLabel) {
var weekdayKey = ['Su.', 'Mo.', 'Tu.', 'We.', 'Th.', 'Fr.', 'Sa.'][dayDate.getDay()]; var weekdayKey = ['Su.', 'Mo.', 'Tu.', 'We.', 'Th.', 'Fr.', 'Sa.'][dayDate.getDay()];
dayLabel = t(weekdayKey) + ' ' + dayLabel = t(weekdayKey) + ' ' +
(dayDate.getDate() < 10 ? '0' : '') + dayDate.getDate() + '.' + (dayDate.getDate() < 10 ? '0' : '') + dayDate.getDate() + '.' +
@@ -2123,7 +2323,7 @@ window.weatherThemes = {
setInterval(fetchWeather, 15 * 60 * 1000); setInterval(fetchWeather, 15 * 60 * 1000);
} }
} catch (e) { } catch (e) {
window.__clouds2dError = e.message || String(e); window.__cloudsError = e.message || String(e);
console.error('Background init error:', e); console.error('Background init error:', e);
} }
</script> </script>
-96
View File
@@ -1,96 +0,0 @@
#!/usr/bin/env python3
"""
Build a single self-contained HTML file from the source files.
Usage:
python3 build.py [output.html]
If output is not given, defaults to 'background-final.html'.
This reads config.js to pick the active theme and inlines all CSS,
JS, and the noise.png texture into a single HTML document. After
building, the resulting standalone HTML can be opened directly in
a browser (file://) because all external resources are inlined.
"""
import base64
import os
import re
import sys
ROOT = os.path.dirname(os.path.abspath(__file__))
def read(path):
with open(os.path.join(ROOT, path), 'r', encoding='utf-8') as f:
return f.read()
def parse_config():
"""Extract window.CONFIG values from config.js."""
src = read('config.js')
out = {}
for key in ['debug', 'theme', 'units', 'language', 'timeFormat']:
m = re.search(r"\b%s\s*:\s*'([^']*)'" % key, src) or re.search(r'\b%s\s*:\s*(\d+)' % key, src)
if m:
out[key] = m.group(1)
m = re.search(r'lat\s*:\s*([\d.]+)', src)
if m:
out['lat'] = m.group(1)
m = re.search(r'lon\s*:\s*([-\d.]+)', src)
if m:
out['lon'] = m.group(1)
return out
def main():
out = sys.argv[1] if len(sys.argv) > 1 else 'background-final.html'
cfg = parse_config()
theme = cfg.get('theme', 'ash')
html = read('background.html')
# Read noise.png and inline as base64 data URI
with open(os.path.join(ROOT, 'noise.png'), 'rb') as f:
noise_b64 = base64.b64encode(f.read()).decode('ascii')
# Replace noiseDataUri placeholder with the actual data URI
html = html.replace(
"'data:image/png;base64,PLACEHOLDER'",
"'data:image/png;base64," + noise_b64 + "'"
)
# Resolve active theme css
theme_css = read(theme + '.css')
# Inline the active theme stylesheet at the end of <head>
html = html.replace('</head>',
'<style>\n' + theme_css + '\n</style>\n</head>',
1)
# Inline vendor scripts in the body, before the main script block
for _, path in [
('three', 'vendor/three.min.js'),
('suncalc', 'vendor/suncalc.js'),
('vanta2', 'vendor/vanta.clouds2.min.js'),
('clouds2d', 'vendor/clouds2d.js'),
('i18n', 'i18n.js'),
('config', 'config.js'),
]:
body = read(path)
html = html.replace('<script src="' + path + '"></script>',
'<script>\n' + body + '\n</script>',
1)
with open(os.path.join(ROOT, out), 'w', encoding='utf-8') as f:
f.write(html)
print('Wrote ' + out + ' (' + str(len(html)) + ' bytes)')
print('Config used:')
for k in ('debug', 'theme', 'units', 'language', 'timeFormat', 'lat', 'lon'):
if k in cfg:
print(' ' + k + ' = ' + cfg[k])
if __name__ == '__main__':
main()
+117 -7
View File
@@ -9,21 +9,54 @@
* timeFormat - Moment.js-compatible format string for the clock. * timeFormat - Moment.js-compatible format string for the clock.
* debug - If 1, the right-side debug panel is shown. * debug - If 1, the right-side debug panel is shown.
* units - Object with 'temperature' ('C' or 'F'). * units - Object with 'temperature' ('C' or 'F').
* renderer - Background renderer: 'vanta' (default, GPU 3D volumetric
* raymarched clouds via three.js), 'clouds2d' (lightweight
* WebGL fragment shader, 2D fBm scrolling), or 'clouds3d'
* (volumetric raymarcher with camera fly-through).
* location - Geographic coordinates for sun/moon calculations. Used * location - Geographic coordinates for sun/moon calculations. Used
* by SunCalc (https://github.com/mourner/suncalc). Object * by SunCalc (https://github.com/mourner/suncalc). Object
* with 'lat' (latitude, -90 to +90) and 'lon' (longitude, * with 'lat' (latitude, -90 to +90) and 'lon' (longitude,
* -180 to +180). Positive lat = north, positive lon = east. * -180 to +180). Positive lat = north, positive lon = east.
* Optionally add 'label' (string) to override the * Optionally add 'label' (string) to override the
* reverse-geocoded city name. * reverse-geocoded city name.
* Note: the height grid is axis-aligned (N up); terrain
* matches the 8-mode square mosaic and straight flights
* with fetch=N. Heights: Copernicus DEM via terrarium
* tiles (z9, ~200 m), fetched once per session.
* 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 * weatherSim - If true, ignore open-meteo and cycle through
* ['sunny','cloudy','stormy','night'] automatically every * ['sunny','cloudy','stormy','night'] automatically every
* weatherSimInterval ms. Useful for A/B testing every * weatherSimInterval ms. Useful for A/B testing every
* theme without waiting on the API. * theme without waiting on the API.
* weatherSimInterval - Sim cycle period in ms (default 30000). * 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 = ~13 km at zoom 11; camera flies at y=2.2).
* Object with 'top' and 'bottom'. Default keeps the
* deck a bit below 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): * Time format tokens (subset):
* YYYY 4-digit year (2026) * YYYY 4-digit year (2026)
@@ -42,19 +75,96 @@ window.CONFIG = {
theme: 'ash', theme: 'ash',
language: 'en', language: 'en',
timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS', timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS',
debug: 1, debug: 0,
units: { units: {
temperature: 'C' temperature: 'C'
}, },
renderer: 'clouds3d', // 'vanta', 'clouds2d', or 'clouds3d' // Data attribution at the bottom of the info box (auto text based on
// the enabled layers). false = ausgeblendet.
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.
renderScale: 1,
location: { location: {
// Latitude and longitude for sun/moon calculations // Latitude and longitude for sun/moon calculations
// Default: Passau, Germany // Passau (vorher: Muenchen 48.1374/11.5755, Innsbruck 47.2692/11.4041)
lat: 48.5667, lat: 48.5667,
lon: 13.4319 lon: 13.4319
}, },
weatherSim: false, weatherSim: false,
weatherSimInterval: 30000, weatherSimInterval: 30000,
// Alles Downloads (Strassenkacheln, Hoehengitter, Wetter-JSON,
// Geo-Label) bleibt dauerhaft im IndexedDB — kein erneuter
// Download. expireDays (Tage, >0) reaktiviert periodisches
// Auffrischen der Strassenkacheln.
tiles: {
expireDays: null,
// true = Kachel-Raender + N/S + x/y-Koords in die Boden-Textur
// malen (Debug-Orientierung). Aus = saubere Karte.
debug: false
},
// Boden-Bildquelle: 'satellite' = ESRI World_Imagery (Luftbild, ohne
// Beschriftung), 'streets' = ESRI World_Street_Map (mit Ortsnamen).
// Beide dauerhaft gecacht (eigener Cache- Praefix pro Stil).
ground: {
style: 'satellite'
},
// Achterkurs um location (lat/lon), ueberschneidungspunkt genau auf
// der Koordinate. null/entfernen = gerade Flugbahn.
orbit: {
radiusKm: 60, // Lappenspitze: max. Abstand vom Zentrum
clockwise: true,
reorient: true, // Zufalls-Ausrichtung an jedem Kreuzungs-Durchflug
// Langsames vertikales Wogen innerhalb der Flugband 15-35 km:
// Mitte 25 km (~1.93 Welt-Einheiten), amp = halbes Band
// (10 km ~ 0.77); harte Klemme ueber/unter die Bandgrenzen.
// periodS Sekunden pro Zyklus, tilt = Reaktion der Kamera-Nase
// auf Steig-/Sinkrate. enabled: false = konstant 25 km.
altBob: { enabled: true, amp: 0.77, periodS: 90, tilt: 0.3 }
},
// Bodennebel: Distanz-Haze. distanceKm = Sichtweite bis vollständige
// Nebelsaettigung (false/0 = aus).
fog: {
distanceKm: 120
},
// 3D-Boden aus echten Hoehendaten (Copernicus DEM, terrarium-Kacheln).
// enabled: false = flache Ebene. exaggeration: vertikale Ueberhoehung
// (1 = real, unsichtbar; default 8). wireframe: true = Mesh-Kanten
// als orange Linien zusaetzlich einblenden (Debug).
// nightLights: NASA-VIIRS-Stadtlichter-Layer, der ab Sonnenuntergang
// ueber ~1 h in die Tageskarte geblendet wird (dunkler Boden,
// leuchtende Staedte). Einmaliger Fetch (~49 Kacheln), persistent
// gecacht. false = kein Lichtoverlay, nachts bleibt die normale
// Textur deutlich heller (nur sanft abgedimmt).
// carLights: fahrende Lichter auf Autobahnen (OSM/Overpass-Kurven,
// nachts additiv ueber dem Gelaende). JSON einmalig, persistent gecacht.
// cityLights: Siedlungslichter — OSM-Ortsknoten (city/town/village/
// hamlet) als leicht gelbliche Glow-Punkte, ab Daemmerung statisch;
// je groesser die Siedlung (place-Klasse + population-Tag), desto
// mehr und hellere Punkte. Eine Overpass-Abfrage, persistent gecacht.
// Daten (c) OpenStreetMap-Mitwirkende, ODbL.
terrain: {
enabled: true,
exaggeration: 10,
wireframe: false,
nightLights: false,
carLights: true,
cityLights: true
},
// Tagessimulation: true = 24h-Zyklus wird in cycleSeconds durchlaufen
// (Sonne steigt im Osten, Mittag im Sueden, Untergang im Westen).
// false = echte Sonnen-/Mondposition via SunCalc.
day: {
simulation: false,
cycleSeconds: 20
},
// Wolkendecke in Welt-Einheiten (1 unit ~ 13 km bei Zoom 11;
// Kamera fliegt auf y=2.2). Decke etwas unter Flughohe.
clouds: {
top: 0.9,
bottom: -0.3
},
}; };
// List of available themes. The key is the theme id (used in config.theme), // List of available themes. The key is the theme id (used in config.theme),
+18
View File
@@ -8,6 +8,13 @@ server {
try_files $uri $uri/ =404; try_files $uri $uri/ =404;
} }
# The background page runs as an iframe on other origins; a stray
# favicon request must not log 404s.
location = /favicon.ico {
return 204;
access_log off;
}
# CORS proxy for Carto basemaps (place names, OSM-derived). # CORS proxy for Carto basemaps (place names, OSM-derived).
# URL format: /carto/voyager/{z}/{x}/{y}.png # URL format: /carto/voyager/{z}/{x}/{y}.png
location ^~ /carto/ { location ^~ /carto/ {
@@ -37,6 +44,17 @@ server {
add_header Access-Control-Max-Age 86400 always; add_header Access-Control-Max-Age 86400 always;
} }
# CORS proxy for Mapzen/TILEZEN terrain-RGB "terrarium" elevation
# tiles (Copernicus DEM). URL format: /terrarium/{z}/{x}/{y}.png
location ^~ /terrarium/ {
resolver 1.1.1.1 8.8.8.8 ipv6=off valid=300s;
proxy_pass https://s3.amazonaws.com/elevation-tiles-prod/terrarium/;
proxy_ssl_server_name on;
proxy_set_header Host s3.amazonaws.com;
proxy_set_header User-Agent "Mozilla/5.0 (compatible; tile-proxy)";
add_header Access-Control-Allow-Origin "*" always;
}
# CORS preflight handler # CORS preflight handler
location ~* \.(png|jpg)$ { location ~* \.(png|jpg)$ {
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

+7 -17
View File
@@ -1,25 +1,15 @@
three.min.js - three.js (r128)
Source: https://github.com/mrdoob/three.js
License: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE)
Used under the terms of the MIT License.
suncalc.js - SunCalc (v1.9.0) suncalc.js - SunCalc (v1.9.0)
Source: https://github.com/mourner/suncalc Source: https://github.com/mourner/suncalc
Original author: Vladimir Agafonkin (mourner) Original author: Vladimir Agafonkin (mourner)
License: ISC (https://github.com/mourner/suncalc/blob/master/LICENSE) License: ISC (https://github.com/mourner/suncalc/blob/master/LICENSE)
Used under the terms of the ISC License. Used under the terms of the ISC License.
vanta.clouds.min.js - Vanta.js Clouds effect (v0.5.21) stars.json - star catalogue derived from the Yale Bright Star Catalogue
Source: https://github.com/tengbao/vanta (5th ed., public catalogue data). Generated for this project; see the
Author: Teng Bao real-sky notes in clouds3d.js.
License: MIT (https://github.com/tengbao/vanta/blob/master/LICENSE)
Used under the terms of the MIT License.
vanta.clouds2.min.js - Vanta.js Clouds2 effect (v0.5.21) clouds3d.js - original code for this project (same license as the
Source: https://github.com/tengbao/vanta repository root LICENSE).
Author: Teng Bao
License: MIT (https://github.com/tengbao/vanta/blob/master/LICENSE)
Used under the terms of the MIT License.
These libraries are vendored locally so the application can run fully offline These libraries/data files are vendored locally so the application can
without any external network dependencies. run fully offline without any external network dependencies.
View File
Whitespace-only changes.
+2745 -277
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
[[1.266,-0.503,6.29,5100],[1.334,-5.708,4.61,5000],[1.425,13.396,5.51,5500],[1.567,58.437,5.96,5500],[1.579,-49.075,5.7,5900],[1.61,64.196,5.59,12000],[1.653,29.021,6.13,5000],[1.709,-23.108,6.18,8250],[1.826,-17.386,6.19,8500],[1.934,-2.549,6.43,14000],[1.945,-22.509,5.94,9500],[2.015,-33.529,5.68,4850],[2.05,-2.448,6.07,4700],[2.097,29.091,2.06,14000],[2.072,-8.824,5.99,5200],[2.171,36.627,6.19,6300],[2.139,-17.578,6.06,3500],[2.218,25.463,6.23,5000],[2.334,79.715,6.01,8250],[2.295,59.15,2.27,7200],[2.26,18.212,5.53,5100],[2.26,-54.002,6.33,5600],[2.337,-27.988,5.42,7200],[2.353,-45.748,3.88,5000],[2.51,11.146,5.51,12000],[2.58,46.072,5.03,7200],[2.578,-5.249,5.84,4850],[2.509,-82.224,5.28,5200],[2.678,-12.58,5.85,5000],[2.816,-15.468,4.89,6450],[2.893,-27.8,5.41,4250],[2.933,-35.133,5.25,6900],[2.996,48.152,6.16,4400],[3.042,-17.938,5.25,4250],[3.309,15.184,2.83,26000],[3.35,26.987,6.3,6000],[3.378,41.035,5.72,7500],[3.425,-26.022,5.94,4250],[3.434,-26.285,6.31,4250],[3.51,33.206,6.25,9750],[3.651,20.207,4.8,3200],[3.615,-7.781,5.12,3050],[3.331,-84.994,5.77,3500],[3.66,-18.933,4.44,3050],[3.734,22.284,6.24,9750],[3.745,8.821,5.79,7500],[3.727,-9.57,5.75,12000],[3.779,31.536,6.45,4250],[3.794,27.283,6.35,9750],[3.743,-34.904,6.17,4550],[4.058,76.951,6.35,14000],[4.09,43.595,6.15,9500],[4.037,-31.446,5.67,4250],[3.98,-75.911,6.49,5200],[4.142,8.24,6.11,5200],[4.238,61.533,5.74,5200],[4.177,-20.211,6.47,14000],[4.288,47.947,5.89,16000],[4.273,38.682,4.61,9500],[4.429,51.433,6.14,30000],[4.386,-19.051,6.45,6900],[4.449,1.689,6.17,5000],[4.582,36.785,4.52,9500],[4.572,11.206,6.05,5000],[4.675,43.791,6.11,14000],[4.66,31.517,5.87,10000],[4.674,-8.053,6.46,6000],[4.677,-43.235,6.33,4700],[4.857,-8.824,3.56,4850],[4.923,40.73,6.33,4850],[5.022,48.865,6.52,10000],[5.018,-64.875,4.23,6150],[5.102,30.936,5.9,16000],[5.19,32.911,5.79,4250],[5.15,8.19,5.37,4550],[5.227,10.977,6.56,10000],[5.28,37.969,5.18,6750],[5.162,-69.625,5.51,12000],[5.38,-28.982,5.18,5500],[5.443,-20.058,5.12,2750],[5.606,13.482,6.23,4550],[5.369,-77.427,5.97,4550],[5.716,-12.209,6.39,5800],[5.803,-31.036,6.55,12000],[6.065,52.02,5.57,20000],[6.198,61.831,5.4,12000],[6.124,-2.219,6.07,4850],[6.277,53.047,5.74,12000],[6.351,1.94,5.77,5500],[6.438,-77.254,2.8,5800],[6.571,-42.306,2.39,5000],[6.551,-43.68,3.94,8250],[6.656,-0.05,6.19,5200],[6.811,-25.547,5.98,5200],[7.012,17.893,5.06,3050],[7.057,44.394,5.17,9500],[6.982,-33.007,4.81,2900],[7.053,16.445,6.06,4250],[7.084,10.19,6.04,6600],[7.088,-20.335,6.43,6000],[7.11,-39.915,5.43,3500],[7.236,36.9,6.26,5500],[7.18,-50.533,6.26,5000],[7.729,77.019,6.21,5000],[7.583,59.977,5.94,12000],[7.53,29.752,5.23,8250],[7.466,-14.864,6.14,7200],[7.454,-32.117,6.57,4400],[7.51,-3.957,5.72,3500],[7.595,-23.788,5.19,8750],[7.616,-40.939,6.19,7200],[7.609,-48.215,5.69,7200],[7.855,66.519,6.18,18000],[7.857,33.582,5.87,4850],[7.943,54.522,4.73,14000],[7.922,52.839,5.6,4700],[7.854,-48.804,4.77,10000],[7.886,-62.958,4.37,12000],[7.89,-62.966,4.54,9500],[8.33,70.982,6.42,10000],[8.25,62.932,4.16,28000],[8.148,20.294,5.38,5000],[8.099,6.956,5.67,12000],[8.205,28.28,6.3,5000],[8.293,54.895,5.93,5000],[8.182,-63.031,5.09,10000],[8.604,66.75,6.48,12000],[8.421,-29.558,5.55,4700],[8.347,-71.266,6.13,8250],[8.616,-52.373,5.57,7050],[8.73,13.371,6.4,5000],[8.812,-3.593,5.2,6300],[8.887,-0.506,5.93,6750],[9.035,54.169,5.08,16000],[8.978,13.207,6.41,6450],[9.114,60.326,5.79,9000],[8.921,-48.001,5.51,6600],[8.889,-54.822,6.06,5000],[9.083,27.255,6.5,14000],[9.012,-14.974,6.45,5100],[9.029,-22.842,6.06,8500],[9.194,44.489,5.13,4250],[9.243,53.897,3.66,26000],[9.22,33.719,4.36,20000],[9.197,15.232,5.89,26000],[9.28,24.014,6.47,4700],[9.338,35.399,5.48,5800],[9.947,82.494,6.4,6600],[9.336,-24.767,5.57,5200],[9.156,-65.125,6.42,4700],[9.377,3.135,6.39,4400],[9.325,-54.394,6.41,5000],[9.639,29.312,4.37,5400],[9.791,49.354,5.43,3950],[9.832,30.861,3.27,4550],[9.841,21.251,5.87,5000],[9.982,21.438,5.36,5000],[10.127,56.537,2.23,5000],[9.967,-44.797,6.01,4850],[10.119,-16.517,6.49,5500],[10.137,-23.804,6.14,5700],[10.177,-4.352,5.91,5000],[10.28,39.459,5.33,5200],[10.11,-59.454,5.89,5900],[10.514,66.148,5.83,5100],[10.4,24.629,6.04,8250],[10.516,50.513,4.8,26000],[10.332,-46.085,4.59,5200],[10.63,58.753,6.17,12000],[10.443,-56.502,5.7,8750],[10.867,47.025,4.94,8750],[10.679,-38.463,6.06,10000],[10.674,-60.263,5.98,4250],[10.618,-65.468,5.39,6600],[10.898,-17.987,2.04,5100],[11.11,47.864,5.67,20000],[10.959,-12.012,6.02,4700],[10.838,-57.463,4.36,10000],[11.412,74.988,5.66,9500],[11.181,48.284,4.54,20000],[11.047,-10.609,4.76,5000],[11.05,-38.422,5.9,4850],[11.322,55.222,5.42,9500],[11.185,-22.006,5.24,7350],[11.238,-42.677,5.94,7500],[11.135,-62.498,6.07,6750],[11.663,69.325,6.33,7050],[11.35,-4.629,6.15,3500],[11.25,-53.715,6.15,6450],[11.37,-12.881,6.15,6000],[11.563,55.305,6.52,9500],[11.545,44.861,6.05,12000],[11.424,-16.424,6.47,7350],[11.677,59.574,6.39,6000],[11.942,74.847,5.41,14000],[11.44,-47.552,5.8,5900],[11.549,-22.52Line truncated
-6
View File
@@ -1,6 +0,0 @@
/**
* @license
* Copyright 2010-2021 Three.js Authors
* SPDX-License-Identifier: MIT
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="128",n=100,i=300,r=301,s=302,a=303,o=304,l=306,c=307,h=1e3,u=1001,d=1002,p=1003,m=1004,f=1005,g=1006,v=1007,y=1008,x=1009,_=1012,w=1014,b=1015,M=1016,S=1020,T=1022,E=1023,A=1026,L=1027,R=33776,C=33777,P=33778,D=33779,I=35840,N=35841,B=35842,z=35843,F=37492,O=37496,H=2300,G=2301,U=2302,k=2400,V=2401,W=2402,j=2500,q=2501,X=3e3,Y=3001,Z=3007,J=3002,Q=3004,K=3005,$=3006,tt=7680,et=35044,nt=35048,it="300 es";class rt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e<i;e++)n[e].call(this,t);t.target=null}}}const st=[];for(let t=0;t<256;t++)st[t]=(t<16?"0":"")+t.toString(16);let at=1234567;const ot=Math.PI/180,lt=180/Math.PI;function ct(){const t=4294967295*Math.random()|0,e=4294967295*Math.random()|0,n=4294967295*Math.random()|0,i=4294967295*Math.random()|0;return(st[255&t]+st[t>>8&255]+st[t>>16&255]+st[t>>24&255]+"-"+st[255&e]+st[e>>8&255]+"-"+st[e>>16&15|64]+st[e>>24&255]+"-"+st[63&n|128]+st[n>>8&255]+"-"+st[n>>16&255]+st[n>>24&255]+st[255&i]+st[i>>8&255]+st[i>>16&255]+st[i>>24&255]).toUpperCase()}function ht(t,e,n){return Math.max(e,Math.min(n,t))}function ut(t,e){return(t%e+e)%e}function dt(t,e,n){return(1-n)*t+n*e}function pt(t){return 0==(t&t-1)&&0!==t}function mt(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function ft(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}var gt=Object.freeze({__proto__:null,DEG2RAD:ot,RAD2DEG:lt,generateUUID:ct,clamp:ht,euclideanModulo:ut,mapLinear:function(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)},inverseLerp:function(t,e,n){return t!==e?(n-t)/(e-t):0},lerp:dt,damp:function(t,e,n,i){return dt(t,e,1-Math.exp(-n*i))},pingpong:function(t,e=1){return e-Math.abs(ut(t,2*e)-e)},smoothstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)},smootherstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){return void 0!==t&&(at=t%2147483647),at=16807*at%2147483647,(at-1)/2147483646},degToRad:function(t){return t*ot},radToDeg:function(t){return t*lt},isPowerOfTwo:pt,ceilPowerOfTwo:mt,floorPowerOfTwo:ft,setQuaternionFromProperEuler:function(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}}});class vt{constructor(t=0,e=0){this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this)}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this)}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=tLine truncated
-1
View File
@@ -1 +0,0 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports._vantaEffect=t():e._vantaEffect=t()}("undefined"!=typeof self?self:this,(function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=6)}([function(e,t,n){"use strict";function i(e,t){for(let n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function o(){return"undefined"!=typeof navigator?/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)||window.innerWidth<600:null}n.d(t,"c",(function(){return i})),n.d(t,"e",(function(){return o})),n.d(t,"i",(function(){return s})),n.d(t,"h",(function(){return r})),n.d(t,"g",(function(){return a})),n.d(t,"f",(function(){return h})),n.d(t,"a",(function(){return c})),n.d(t,"b",(function(){return u})),n.d(t,"d",(function(){return l})),Number.prototype.clamp=function(e,t){return Math.min(Math.max(this,e),t)};const s=e=>e[Math.floor(Math.random()*e.length)];function r(e,t){return null==e&&(e=0),null==t&&(t=1),e+Math.random()*(t-e)}function a(e,t){return null==e&&(e=0),null==t&&(t=1),Math.floor(e+Math.random()*(t-e+1))}const h=e=>document.querySelector(e),c=e=>"number"==typeof e?"#"+("00000"+e.toString(16)).slice(-6):e,u=(e,t=1)=>{const n=c(e),i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(n),o=i?{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16)}:null;return"rgba("+o.r+","+o.g+","+o.b+","+t+")"},l=e=>.299*e.r+.587*e.g+.114*e.b},function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var i=n(0);const o="object"==typeof window;let s=o&&window.THREE||{};o&&!window.VANTA&&(window.VANTA={});const r=o&&window.VANTA||{};r.register=(e,t)=>r[e]=e=>new t(e),r.version="0.5.21";const a=function(){return Array.prototype.unshift.call(arguments,"[VANTA]"),console.error.apply(this,arguments)};r.VantaBase=class{constructor(e={}){if(!o)return!1;r.current=this,this.windowMouseMoveWrapper=this.windowMouseMoveWrapper.bind(this),this.windowTouchWrapper=this.windowTouchWrapper.bind(this),this.windowGyroWrapper=this.windowGyroWrapper.bind(this),this.resize=this.resize.bind(this),this.animationLoop=this.animationLoop.bind(this),this.restart=this.restart.bind(this);const t="function"==typeof this.getDefaultOptions?this.getDefaultOptions():this.defaultOptions;if(this.options=Object(i.c)({mouseControls:!0,touchControls:!0,gyroControls:!1,minHeight:200,minWidth:200,scale:1,scaleMobile:1},t),(e instanceof HTMLElement||"string"==typeof e)&&(e={el:e}),Object(i.c)(this.options,e),this.options.THREE&&(s=this.options.THREE),this.el=this.options.el,null==this.el)a('Instance needs "el" param!');else if(!(this.options.el instanceof HTMLElement)){const e=this.el;if(this.el=Object(i.f)(e),!this.el)return void a("Cannot find element",e)}this.prepareEl(),this.initThree(),this.setSize();try{this.init()}catch(e){return a("Init error",e),this.renderer&&this.renderer.domElement&&this.el.removeChild(this.renderer.domElement),void(this.options.backgroundColor&&(console.log("[VANTA] Falling back to backgroundColor"),this.el.style.background=Object(i.a)(this.options.backgroundColor)))}this.initMouse(),this.resize(),this.animationLoop();const n=window.addEventListener;n("resize",this.resize),window.requestAnimationFrame(this.resize),this.options.mouseControls&&(n("scroll",this.windowMouseMoveWrapper),n("mousemove",this.windowMouseMoveWrapper)),this.options.touchControls&&(n("touchstart",this.windowTouchWrapper),n("touchmove",this.windowTouchWrapper)),this.options.gyroControls&&n("deviceorientation",this.windowGyroWrapper)}setOptions(e={}){Object(i.c)(this.options,e),this.triggerMouseMove()}prepareEl(){let e,t;if("undefined"!=typeof Node&&Node.TEXT_NODE)for(e=0;e<this.el.childNodes.length;e++){const t=this.el.childNodes[e];if(t.nodeType===Node.TEXT_NODE){const e=document.createElement("span");e.textContent=t.textContent,t.parentElement.insertBefore(e,t),t.remove()}}for(e=0;e<this.el.children.length;e++)t=this.el.children[e],"static"===getComputedStyle(t).position&&(t.style.position="relative"),"auto"===getComputedStyle(t).zIndex&&(t.style.zIndex=1);"Line truncated
-1
View File
@@ -1 +0,0 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports._vantaEffect=t():e._vantaEffect=t()}("undefined"!=typeof self?self:this,(function(){return function(e){var t={};function i(o){if(t[o])return t[o].exports;var s=t[o]={i:o,l:!1,exports:{}};return e[o].call(s.exports,s,s.exports,i),s.l=!0,s.exports}return i.m=e,i.c=t,i.d=function(e,t,o){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)i.d(o,s,function(t){return e[t]}.bind(null,s));return o},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=7)}([function(e,t,i){"use strict";function o(e,t){for(let i in t)t.hasOwnProperty(i)&&(e[i]=t[i]);return e}function s(){return"undefined"!=typeof navigator?/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)||window.innerWidth<600:null}i.d(t,"c",(function(){return o})),i.d(t,"e",(function(){return s})),i.d(t,"i",(function(){return n})),i.d(t,"h",(function(){return r})),i.d(t,"g",(function(){return h})),i.d(t,"f",(function(){return a})),i.d(t,"a",(function(){return u})),i.d(t,"b",(function(){return c})),i.d(t,"d",(function(){return l})),Number.prototype.clamp=function(e,t){return Math.min(Math.max(this,e),t)};const n=e=>e[Math.floor(Math.random()*e.length)];function r(e,t){return null==e&&(e=0),null==t&&(t=1),e+Math.random()*(t-e)}function h(e,t){return null==e&&(e=0),null==t&&(t=1),Math.floor(e+Math.random()*(t-e+1))}const a=e=>document.querySelector(e),u=e=>"number"==typeof e?"#"+("00000"+e.toString(16)).slice(-6):e,c=(e,t=1)=>{const i=u(e),o=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(i),s=o?{r:parseInt(o[1],16),g:parseInt(o[2],16),b:parseInt(o[3],16)}:null;return"rgba("+s.r+","+s.g+","+s.b+","+t+")"},l=e=>.299*e.r+.587*e.g+.114*e.b},function(e,t,i){"use strict";i.d(t,"a",(function(){return r}));var o=i(0);const s="object"==typeof window;let n=s&&window.THREE||{};s&&!window.VANTA&&(window.VANTA={});const r=s&&window.VANTA||{};r.register=(e,t)=>r[e]=e=>new t(e),r.version="0.5.21";const h=function(){return Array.prototype.unshift.call(arguments,"[VANTA]"),console.error.apply(this,arguments)};r.VantaBase=class{constructor(e={}){if(!s)return!1;r.current=this,this.windowMouseMoveWrapper=this.windowMouseMoveWrapper.bind(this),this.windowTouchWrapper=this.windowTouchWrapper.bind(this),this.windowGyroWrapper=this.windowGyroWrapper.bind(this),this.resize=this.resize.bind(this),this.animationLoop=this.animationLoop.bind(this),this.restart=this.restart.bind(this);const t="function"==typeof this.getDefaultOptions?this.getDefaultOptions():this.defaultOptions;if(this.options=Object(o.c)({mouseControls:!0,touchControls:!0,gyroControls:!1,minHeight:200,minWidth:200,scale:1,scaleMobile:1},t),(e instanceof HTMLElement||"string"==typeof e)&&(e={el:e}),Object(o.c)(this.options,e),this.options.THREE&&(n=this.options.THREE),this.el=this.options.el,null==this.el)h('Instance needs "el" param!');else if(!(this.options.el instanceof HTMLElement)){const e=this.el;if(this.el=Object(o.f)(e),!this.el)return void h("Cannot find element",e)}this.prepareEl(),this.initThree(),this.setSize();try{this.init()}catch(e){return h("Init error",e),this.renderer&&this.renderer.domElement&&this.el.removeChild(this.renderer.domElement),void(this.options.backgroundColor&&(console.log("[VANTA] Falling back to backgroundColor"),this.el.style.background=Object(o.a)(this.options.backgroundColor)))}this.initMouse(),this.resize(),this.animationLoop();const i=window.addEventListener;i("resize",this.resize),window.requestAnimationFrame(this.resize),this.options.mouseControls&&(i("scroll",this.windowMouseMoveWrapper),i("mousemove",this.windowMouseMoveWrapper)),this.options.touchControls&&(i("touchstart",this.windowTouchWrapper),i("touchmove",this.windowTouchWrapper)),this.options.gyroControls&&i("deviceorientation",this.windowGyroWrapper)}setOptions(e={}){Object(o.c)(this.options,e),this.triggerMouseMove()}prepareEl(){let e,t;if("undefined"!=typeof Node&&Node.TEXT_NODE)for(e=0;e<this.el.childNodes.length;e++){const t=this.el.childNodes[e];if(t.nodeType===Node.TEXT_NODE){const e=document.createElement("span");e.textContent=t.textContent,t.parentElement.insertBefore(e,t),t.remove()}}for(e=0;e<this.el.children.length;e++)t=this.el.children[e],"static"===getComputedStyle(t).position&&(t.style.position="relative"),"auto"===getComputedStyle(t).zIndex&&(t.style.zIndex=1);"Line truncated