The background weather is now GPU-rendered: one instanced-quad draw call over a baked sprite atlas plus a fullscreen sky shader (fog, Saharan haze, aurora, sun rays, storm gloom and lightning flash). The old Canvas2D renderer stays as weather-2d.js and kicks in automatically when WebGL2 is missing; weather.js is now a thin controller that owns the toggle, prefs and the PeteWeather API. Effect upgrades: shared wind with gust pulses leans the whole scene together, rain gets depth, splash pops and velocity-aligned streaks, storms grow procedural branched lightning bolts, snow mixes soft motes with spinning six-arm crystals, clouds drift in two parallax layers, clear nights get a moon with maria, twinkling stars and the occasional shooting star, blossoms and leaves tumble with a faked 3D flip. New variants: haze (Saharan calima), wind (autumn gusts with streak lines), hail (bouncing stones with drizzle) and aurora. The /weather demo page switches variant, intensity and phase in place without a reload and shows the active renderer plus an FPS meter.
85 lines
3.2 KiB
JavaScript
85 lines
3.2 KiB
JavaScript
// Weather controller. Two drivers feed the background canvas:
|
|
//
|
|
// 1. The server picks a *seasonal* variant + intensity from the Lisbon-local
|
|
// date and writes them to <html data-weather / data-intensity>. This is the
|
|
// default when the visitor hasn't set a location.
|
|
// 2. weather-forecast.js, when a postal code is saved, fetches the real
|
|
// forecast and calls PeteWeather.set(variant, intensity) to override the
|
|
// background with live conditions.
|
|
//
|
|
// Rendering is delegated to an engine: the WebGL2 one (weather-gl.js) when the
|
|
// browser supports it — GPU sprites, shader fog/aurora, real lightning — with
|
|
// the original Canvas2D renderer (weather-2d.js) as the fallback. This file
|
|
// only owns the toggle button, the on/off preference and the public API.
|
|
(function () {
|
|
var root = document.documentElement;
|
|
var canvas = document.getElementById("pete-weather");
|
|
if (!canvas) return;
|
|
var reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
|
|
var engines = window.PeteWeatherEngines || {};
|
|
var engine = (engines.webgl2 && engines.webgl2(canvas)) || null;
|
|
if (!engine && engines.canvas2d) engine = engines.canvas2d(canvas);
|
|
if (!engine) return;
|
|
|
|
var STORAGE_KEY = "pete-weather-off";
|
|
var toggleBtn = document.querySelector("[data-weather-toggle]");
|
|
var star = document.querySelector("[data-weather-star]");
|
|
|
|
function userDisabled() {
|
|
try { return localStorage.getItem(STORAGE_KEY) === "1"; } catch (e) { return false; }
|
|
}
|
|
function setDisabled(v) {
|
|
try {
|
|
if (v) localStorage.setItem(STORAGE_KEY, "1");
|
|
else localStorage.removeItem(STORAGE_KEY);
|
|
} catch (e) {}
|
|
if (window.PetePrefs) window.PetePrefs.push();
|
|
}
|
|
function syncBtn(enabled) {
|
|
if (!toggleBtn) return;
|
|
toggleBtn.setAttribute("aria-pressed", enabled ? "true" : "false");
|
|
toggleBtn.title = enabled ? "Turn off weather animation" : "Turn on weather animation";
|
|
if (star) {
|
|
star.style.color = "var(--accent)";
|
|
star.style.opacity = enabled ? "1" : "0.3";
|
|
star.style.filter = enabled ? "none" : "grayscale(1)";
|
|
}
|
|
}
|
|
|
|
var enabled = !userDisabled() && !reducedMotion;
|
|
|
|
// Public hook so weather-forecast.js (and the /weather demo) can swap the
|
|
// seasonal default for other conditions. Passing a falsy variant clears the
|
|
// canvas.
|
|
window.PeteWeather = {
|
|
set: function (v, inten) {
|
|
root.dataset.weather = v || "";
|
|
if (inten) root.dataset.intensity = inten;
|
|
engine.set(v || null, inten || root.dataset.intensity || "heavy");
|
|
if (enabled && v) engine.start();
|
|
},
|
|
isEnabled: function () { return enabled; },
|
|
renderer: function () { return engine.name; }
|
|
};
|
|
|
|
syncBtn(enabled);
|
|
engine.set(root.dataset.weather || null, root.dataset.intensity || "heavy");
|
|
if (enabled && root.dataset.weather) engine.start();
|
|
|
|
if (toggleBtn) {
|
|
toggleBtn.addEventListener("click", function () {
|
|
enabled = !enabled;
|
|
setDisabled(!enabled);
|
|
syncBtn(enabled);
|
|
if (enabled) engine.start(); else engine.stop();
|
|
});
|
|
}
|
|
|
|
document.addEventListener("visibilitychange", function () {
|
|
if (!enabled) return;
|
|
if (document.hidden) engine.stop();
|
|
else engine.start();
|
|
});
|
|
})();
|