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

+310 -110
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<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="i18n.js"></script>
<script>
@@ -18,7 +18,7 @@
<script src="vendor/suncalc.js"></script>
<style>
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 {
position: fixed;
bottom: 2vh;
@@ -291,6 +291,14 @@
color: var(--icon);
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 {
margin-bottom: 0.6vh;
}
@@ -338,9 +346,9 @@
</style>
</head>
<body>
<div id="vanta" style="background: #1c1b1f;"></div>
<div id="scene"></div>
<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 class="debug-controls">
<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">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-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">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>
@@ -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="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"/>
<!-- 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)"/>
</svg>
<div id="digital-clock">00:00:00</div>
@@ -502,20 +517,14 @@
<div id="weather-divider" class="weather-divider"></div>
<div id="weather-current" class="weather-current"></div>
<div id="weather-forecast" class="weather-forecast"></div>
<div id="credits" class="credits"></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/clouds2d.js"></script>
<script>
window.__clouds2dStarted = true;
window.__cloudsStarted = true;
try {
window.__noiseDataUri = 'data:image/png;base64,PLACEHOLDER';
var noiseDataUri = 'data:image/png;base64,PLACEHOLDER';
// Parse URL hash params: #key=value&key=value...
// Supported: #renderer=clouds2d|clouds3d|vanta, #flight=N|NE|E|SE|S|SW|W|NW,
// #fetch=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
function _parseHashParams(hash) {
var out = {};
hash = (hash || '').replace(/^#/, '');
@@ -530,10 +539,6 @@ function _parseHashParams(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
// has cos/sin (corridor rotation matrix) and dx/dz (camera motion unit vec).
var FLIGHT_DIRS = {
@@ -552,48 +557,20 @@ function _pickDir(key) {
}
window.__flightDir = _pickDir('flight');
window.__fetchDir = _pickDir('fetch');
var useVanta = (renderer !== 'clouds2d' && renderer !== 'clouds3d');
var effect;
if (useVanta && typeof VANTA !== 'undefined') {
effect = (VANTA.CLOUDS2 || VANTA.CLOUDS)({
el: "#vanta",
mouseControls: false,
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);
}
// ---- WebGL scene (clouds3d.js) ------------------------------------------
var sceneEl = document.getElementById('scene');
function sceneRender() {
if (sceneEl && typeof window.clouds3dRender === 'function') {
window.clouds3dRender(sceneEl);
}
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) {
return [(h >> 16) & 255, (h >> 8) & 255, h & 255];
@@ -612,10 +589,26 @@ if (useVanta && typeof VANTA !== 'undefined') {
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 PASSAU_LON = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lon) || 13.4319;
var SITE_LAT = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lat) || 48.5667;
var SITE_LON = (window.CONFIG && window.CONFIG.location && window.CONFIG.location.lon) || 13.4319;
var realDayProgress = 0;
@@ -839,9 +832,13 @@ window.sunPalettes = [
}
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 sunPos = SunCalc.getPosition(now, PASSAU_LAT, PASSAU_LON);
var sunPos = SunCalc.getPosition(now, SITE_LAT, SITE_LON);
var moonIllum = SunCalc.getMoonIllumination(now);
// The day palette covers sunAlt down to sunPalettes[0].alt
@@ -918,6 +915,7 @@ window.sunPalettes = [
var hourHand = document.getElementById('hour-hand');
var minuteHand = document.getElementById('minute-hand');
var secondHand = document.getElementById('second-hand');
var compassNeedle = document.getElementById('compass-needle');
var hourMarks = document.getElementById('hour-marks');
var rpgSunArc = document.getElementById('sun-arc');
var rpgMoonArc = document.getElementById('moon-arc');
@@ -1018,9 +1016,7 @@ window.sunPalettes = [
result += date.getFullYear();
i += 1;
} else if (ch === 'M' && format.substr(i, 4) === 'MMMM') {
var tMonthFull = t(monthKeys[date.getMonth()]);
console.log('MMMM token', date.getMonth(), '->', tMonthFull, 'lang=', window.CONFIG && window.CONFIG.language);
result += tMonthFull;
result += t(monthKeys[date.getMonth()]);
i += 4;
} else if (ch === 'M' && format.substr(i, 3) === 'MMM') {
result += t(monthKeys[date.getMonth()].substring(0, 3));
@@ -1279,7 +1275,10 @@ window.sunPalettes = [
// Calculate real day progress based on sun position
// 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 sunset = sunTimes.sunset;
if (now < sunrise) {
@@ -1290,7 +1289,8 @@ window.sunPalettes = [
} else {
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
var h = now.getHours();
@@ -1308,9 +1308,9 @@ window.sunPalettes = [
secondHand.setAttribute('transform', 'rotate(' + secondAngle + ' 140 140)');
// Sun times
var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON);
var sunriseNext = getNextEvent(sunTimes.sunrise, now, PASSAU_LAT, PASSAU_LON, 'sunrise');
var sunsetNext = getNextEvent(sunTimes.sunset, now, PASSAU_LAT, PASSAU_LON, 'sunset');
var sunTimes = SunCalc.getTimes(now, SITE_LAT, SITE_LON);
var sunriseNext = getNextEvent(sunTimes.sunrise, now, SITE_LAT, SITE_LON, 'sunrise');
var sunsetNext = getNextEvent(sunTimes.sunset, now, SITE_LAT, SITE_LON, 'sunset');
document.getElementById('sunrise-value').textContent = formatTimeHM(sunriseNext.date);
document.getElementById('sunrise-when').textContent = sunriseNext.label ? '· ' + sunriseNext.label : '';
@@ -1323,16 +1323,16 @@ window.sunPalettes = [
if (sunZenith <= now) {
var tomorrow = new Date(now);
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);
}
document.getElementById('sun-zenith-value').textContent = formatTimeHM(sunZenith);
document.getElementById('sun-zenith-when').textContent = '· ' + sunZenithWhen;
// Moon times
var moonTimes = SunCalc.getMoonTimes(now, PASSAU_LAT, PASSAU_LON);
var moonriseNext = getNextEvent(moonTimes.rise, now, PASSAU_LAT, PASSAU_LON, 'moonrise');
var moonsetNext = getNextEvent(moonTimes.set, now, PASSAU_LAT, PASSAU_LON, 'moonset');
var moonTimes = SunCalc.getMoonTimes(now, SITE_LAT, SITE_LON);
var moonriseNext = getNextEvent(moonTimes.rise, now, SITE_LAT, SITE_LON, 'moonrise');
var moonsetNext = getNextEvent(moonTimes.set, now, SITE_LAT, SITE_LON, 'moonset');
document.getElementById('moonrise-value').textContent = formatTimeHM(moonriseNext.date);
document.getElementById('moonrise-when').textContent = moonriseNext.label ? '· ' + moonriseNext.label : '';
@@ -1388,7 +1388,7 @@ window.sunPalettes = [
sunSetArrow.setAttribute('transform', 'translate(' + ssP.x + ', ' + ssP.y + ')');
// 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);
sunZenithMarker.setAttribute('transform', 'translate(' + szP.x + ', ' + szP.y + ')');
@@ -1494,7 +1494,7 @@ window.weatherThemes = {
// ---- Weather simulation -----------------------------------------
// 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.
var simWeathers = ['sunny', 'cloudy', 'stormy', 'night'];
var simIndex = 0;
@@ -1565,6 +1565,14 @@ window.weatherThemes = {
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) {
prevThemeColors = targetThemeColors;
currentThemeSpeed = targetThemeColors.speed;
@@ -1572,6 +1580,29 @@ window.weatherThemes = {
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) {
var themeColors = getEffectiveThemeColors();
if (!themeColors) return c;
@@ -1579,8 +1610,9 @@ window.weatherThemes = {
// At night, weather only tints slightly - we don't want a
// 'cloudy' sky colour washing over deep night sky and
// making it look bright.
var _sunPos = SunCalc.getPosition(new Date(), PASSAU_LAT, PASSAU_LON);
var nightFactor = Math.max(0, Math.min(1, -_sunPos.altitude / 0.4));
var _sunAlt = DAY_SIM ? (window.__currentSunAlt || 0)
: 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
// palette visible underneath, weather only adds tint.
var mix = 0.4 * (1 - nightFactor * 0.7);
@@ -1702,7 +1734,8 @@ window.weatherThemes = {
if (css && cssFile) css.href = cssFile;
setTimeout(function() {
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);
window.CONFIG.theme = theme;
}
@@ -1713,18 +1746,19 @@ window.weatherThemes = {
if (typeof fetchWeather === 'function') fetchWeather();
}
function updateDebug(t, palette, vantaColors, themeName, realDayProgress) {
function updateDebug(t, palette, sceneColors, themeName, realDayProgress) {
var now = new Date();
var hh = now.getHours(), mm = now.getMinutes(), ss = now.getSeconds();
document.getElementById('dbg-time').textContent =
(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-weather').textContent =
currentWeatherCode + ' (' + (window.weatherThemes[themeName] ? themeName : '?') + ')';
currentWeatherCode + ' (' + (window.weatherThemes[themeName] ? themeName : '?') + ')'
+ (window.__weatherStale ? ' cache' : '');
// RPG angles
var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON);
var moonTimes = SunCalc.getMoonTimes(now, PASSAU_LAT, PASSAU_LON);
var sunTimes = SunCalc.getTimes(now, SITE_LAT, SITE_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-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) + '°';
@@ -1755,12 +1789,12 @@ window.weatherThemes = {
}
}
if (vantaColors) {
if (sceneColors) {
var vKeys = ['sky', 'cloud', 'light', 'bg'];
var vKeyMap = {sky: 'sky', cloud: 'cloud', light: 'sunLight', bg: 'bg'};
for (var j = 0; j < vKeys.length; j++) {
var vk = vKeys[j];
var vc = vantaColors[vk];
var vc = sceneColors[vk];
var kk = vKeyMap[vk];
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);
@@ -1794,7 +1828,12 @@ window.weatherThemes = {
tableHtml += '<span class="theme-table-speed">' + th.speed + '</span>';
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
var colorKeys = [
@@ -1813,40 +1852,136 @@ window.weatherThemes = {
'<span class="theme-table-speed">' + cv + '</span>' +
'</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() {
var t = (Date.now() - startTime) / CYCLE % 1;
var c = getColor(t);
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.__currentWeatherName = currentWeatherThemeName;
window.__currentDayProgress = realDayProgress;
window.__currentThemeSpeed = currentThemeSpeed;
// Sun altitude in radians. Negative = below horizon (night).
var _sunPos = SunCalc.getPosition(new Date(), PASSAU_LAT, PASSAU_LON);
window.__currentSunAlt = _sunPos.altitude;
effect.setOptions({
skyColor: c.sky,
cloudColor: c.cloud,
lightColor: c.light,
backgroundColor: c.bg,
backgroundAlpha: 0,
speed: currentThemeSpeed
});
document.body.style.backgroundColor = '#' + ('00000' + c.sky.toString(16)).slice(-6);
updateDebug(t, null, c, currentWeatherThemeName, realDayProgress);
if (DAY_SIM) {
__simT = t;
window.__currentDayProgress = t;
// virtual weekday: starts at the real one and advances with
// every simulated day, so weekends pass by in the 20s cycle
if (typeof window.__simDow !== 'number') {
window.__simDow = new Date().getDay();
window.__simLastT = t;
} else if (t < window.__simLastT) {
window.__simDow = (window.__simDow + 1) % 7;
}
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);
}
var __lastWeatherHour = null;
function tick() {
try {
updateClockAndEvents();
} catch (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);
}
@@ -1883,6 +2018,16 @@ window.weatherThemes = {
// set their own place name in CONFIG.location.label).
function reverseGeocode(lat, lon) {
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=' +
lat + '&lon=' + lon + '&zoom=10&addressdetails=1';
fetch(url, {
@@ -1897,6 +2042,9 @@ window.weatherThemes = {
if (window.CONFIG && window.CONFIG.location) {
window.CONFIG.location.label = name;
}
if (window.BackgroundCache) {
window.BackgroundCache.put('meta/geolabel', { value: name });
}
}
})
.catch(function() { /* ignore errors */ });
@@ -2002,12 +2150,29 @@ window.weatherThemes = {
.then(function(response) { return response.json(); })
.then(function(data) {
weatherData = data;
window.__weatherStale = false;
if (window.BackgroundCache) {
window.BackgroundCache.put('meta/weather', { json: data });
}
currentWeatherCode = data.current.weather_code;
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
updateWeatherDisplay();
})
.catch(function(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() {
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 hourly = weatherData.hourly;
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
var currentUnits = (window.CONFIG && window.CONFIG.units) || 'metric';
var tempDisplay = currentUnits === 'imperial'
? Math.round(current.temperature_2m * 9 / 5 + 32) + '\u00b0F'
: Math.round(current.temperature_2m) + '\u00b0C';
? Math.round(curTemp * 9 / 5 + 32) + '\u00b0F'
: Math.round(curTemp) + '\u00b0C';
var windDisplay = currentUnits === 'imperial'
? Math.round(current.wind_speed_10m * 0.621371) + ' mph'
: 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">' +
((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">' +
t('Humidity') + ' ' + Math.round(current.relative_humidity_2m) + '% · ' + t('Wind') + ' ' + windDisplay + '</div></div>';
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 = '';
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 dayLabel = d === 0 ? t('today') : t('tomorrow');
if (d > 1) {
var dayLabel = d === dayOffset ? t('today')
: (d === dayOffset + 1 ? t('tomorrow') : '');
if (!dayLabel) {
var weekdayKey = ['Su.', 'Mo.', 'Tu.', 'We.', 'Th.', 'Fr.', 'Sa.'][dayDate.getDay()];
dayLabel = t(weekdayKey) + ' ' +
(dayDate.getDate() < 10 ? '0' : '') + dayDate.getDate() + '.' +
@@ -2123,7 +2323,7 @@ window.weatherThemes = {
setInterval(fetchWeather, 15 * 60 * 1000);
}
} catch (e) {
window.__clouds2dError = e.message || String(e);
window.__cloudsError = e.message || String(e);
console.error('Background init error:', e);
}
</script>