Rebuild weather effects on WebGL2 with new variants and a live demo page
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.
This commit is contained in:
@@ -312,6 +312,8 @@
|
||||
window.PETE_TTS = {{.TTS}};
|
||||
</script>
|
||||
<script src="/static/js/prefs.js" defer></script>
|
||||
<script src="/static/js/weather-2d.js" defer></script>
|
||||
<script src="/static/js/weather-gl.js" defer></script>
|
||||
<script src="/static/js/weather.js" defer></script>
|
||||
<script src="/static/js/weather-forecast.js" defer></script>
|
||||
<script src="/static/js/search.js" defer></script>
|
||||
|
||||
@@ -3,14 +3,21 @@
|
||||
{{define "main"}}
|
||||
<div class="space-y-8">
|
||||
<section class="rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<h1 class="font-display text-3xl font-bold mb-1">Weather demo</h1>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mb-5">Pick a variant, intensity, and phase. The canvas reloads on each click.</p>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3 mb-1">
|
||||
<h1 class="font-display text-3xl font-bold">Weather demo</h1>
|
||||
<div class="flex items-center gap-2 text-xs font-semibold">
|
||||
<span data-demo-renderer class="rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1 uppercase tracking-wider text-[color:var(--ink)]/60">renderer: …</span>
|
||||
<span data-demo-fps class="rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1 tabular-nums text-[color:var(--ink)]/60">— fps</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mb-5">Pick a variant, intensity, and phase. Switching is instant, no reload.</p>
|
||||
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-[color:var(--ink)]/60">Variant</span>
|
||||
{{range $v := .Variants}}
|
||||
<a href="?variant={{$v}}&intensity={{$.Intensity}}&phase={{$.PhaseSel}}"
|
||||
data-demo-variant="{{$v}}"
|
||||
class="rounded-full px-3 py-1 transition border-2 border-[color:var(--ink)]/10
|
||||
{{if eq $.Variant $v}}bg-[color:var(--accent)] text-white font-semibold{{else}}hover:bg-[color:var(--ink)]/5{{end}}">{{$v}}</a>
|
||||
{{end}}
|
||||
@@ -19,6 +26,7 @@
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-[color:var(--ink)]/60">Intensity</span>
|
||||
{{range $i := .Intensities}}
|
||||
<a href="?variant={{$.Variant}}&intensity={{$i}}&phase={{$.PhaseSel}}"
|
||||
data-demo-intensity="{{$i}}"
|
||||
class="rounded-full px-3 py-1 transition border-2 border-[color:var(--ink)]/10
|
||||
{{if eq $.Intensity $i}}bg-[color:var(--accent)] text-white font-semibold{{else}}hover:bg-[color:var(--ink)]/5{{end}}">{{$i}}</a>
|
||||
{{end}}
|
||||
@@ -27,6 +35,7 @@
|
||||
<span class="w-20 shrink-0 font-semibold uppercase tracking-wider text-[color:var(--ink)]/60">Phase</span>
|
||||
{{range $p := .Phases}}
|
||||
<a href="?variant={{$.Variant}}&intensity={{$.Intensity}}&phase={{$p}}"
|
||||
data-demo-phase="{{$p}}"
|
||||
class="rounded-full px-3 py-1 transition border-2 border-[color:var(--ink)]/10
|
||||
{{if eq $.PhaseSel $p}}bg-[color:var(--accent)] text-white font-semibold{{else}}hover:bg-[color:var(--ink)]/5{{end}}">{{$p}}</a>
|
||||
{{end}}
|
||||
@@ -34,7 +43,8 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-5 text-xs text-[color:var(--ink)]/50">
|
||||
Current: <code class="font-semibold">{{.Weather.Season}} / {{.Variant}} / {{.Intensity}} / {{.PhaseSel}}</code>
|
||||
Current: <code data-demo-current class="font-semibold">{{.Variant}} / {{.Intensity}} / {{.PhaseSel}}</code>
|
||||
<span class="mx-1">·</span> Tip: aurora and clear night want the night phase; wind and haze are the new autumn gusts and Saharan calima.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -50,8 +60,76 @@
|
||||
</div>
|
||||
<div class="rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<p class="font-display">Card B</p>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-1">Move your window taller to give the particles more room.</p>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-1">Make your window taller to give the particles more room.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Instant switching: the pill links stay real URLs for no-JS visitors, but
|
||||
// with JS we swap the effect in place via PeteWeather.set and keep the URL
|
||||
// in sync with replaceState.
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var root = document.documentElement;
|
||||
var state = {
|
||||
variant: {{.Variant}},
|
||||
intensity: {{.Intensity}},
|
||||
phase: {{.PhaseSel}}
|
||||
};
|
||||
|
||||
function markActive(attr, value) {
|
||||
document.querySelectorAll("[" + attr + "]").forEach(function (el) {
|
||||
var on = el.getAttribute(attr) === value;
|
||||
el.classList.toggle("bg-[color:var(--accent)]", on);
|
||||
el.classList.toggle("text-white", on);
|
||||
el.classList.toggle("font-semibold", on);
|
||||
});
|
||||
}
|
||||
|
||||
function apply() {
|
||||
root.dataset.phase = state.phase;
|
||||
if (window.PeteWeather) window.PeteWeather.set(state.variant, state.intensity);
|
||||
markActive("data-demo-variant", state.variant);
|
||||
markActive("data-demo-intensity", state.intensity);
|
||||
markActive("data-demo-phase", state.phase);
|
||||
var cur = document.querySelector("[data-demo-current]");
|
||||
if (cur) cur.textContent = state.variant + " / " + state.intensity + " / " + state.phase;
|
||||
var label = document.querySelector("[data-phase-label]");
|
||||
if (label) label.textContent = state.phase;
|
||||
history.replaceState(null, "", "?variant=" + state.variant + "&intensity=" + state.intensity + "&phase=" + state.phase);
|
||||
}
|
||||
|
||||
document.addEventListener("click", function (e) {
|
||||
var a = e.target.closest && e.target.closest("[data-demo-variant],[data-demo-intensity],[data-demo-phase]");
|
||||
if (!a || !window.PeteWeather) return;
|
||||
e.preventDefault();
|
||||
if (a.hasAttribute("data-demo-variant")) state.variant = a.getAttribute("data-demo-variant");
|
||||
if (a.hasAttribute("data-demo-intensity")) state.intensity = a.getAttribute("data-demo-intensity");
|
||||
if (a.hasAttribute("data-demo-phase")) state.phase = a.getAttribute("data-demo-phase");
|
||||
apply();
|
||||
});
|
||||
|
||||
var rEl = document.querySelector("[data-demo-renderer]");
|
||||
if (rEl && window.PeteWeather && window.PeteWeather.renderer) {
|
||||
rEl.textContent = "renderer: " + window.PeteWeather.renderer();
|
||||
}
|
||||
|
||||
// Lightweight FPS meter, demo page only. Counts frames on its own rAF and
|
||||
// refreshes the pill once a second.
|
||||
var fpsEl = document.querySelector("[data-demo-fps]");
|
||||
if (fpsEl) {
|
||||
var frames = 0, lastStamp = performance.now();
|
||||
function tick(now) {
|
||||
frames++;
|
||||
if (now - lastStamp >= 1000) {
|
||||
fpsEl.textContent = Math.round(frames * 1000 / (now - lastStamp)) + " fps";
|
||||
frames = 0;
|
||||
lastStamp = now;
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user