clouds3d-only cleanup: drop Vanta/three.js/clouds2d, real-sky fixes, settlement lights, dynamic location anchor
This commit is contained in:
1 parent
e4170bee62
commit
fbac24773a
17 files changed
+3217
-3411
No files matched your search
+310
-110
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user