feat: tik op het regenblok voor de Buienradar-animatie
Het regenblok linksonder opent een overlay met de geanimeerde regenradar van Buienradar (550x512, tijd in het beeld), naast de regencijfers die we al hebben: vandaag, de 3 vorige dagen en het maandtotaal. Sluit na 20 seconden of bij een tik, net als het uuroverzicht. Het beeld loopt via onze eigen /api/radar.php in plaats van een <img> rechtstreeks naar Buienradar: de spec wil alles lokaal geserveerd, de iPad praat zo nooit met een derde partij, we cachen 4 minuten (de radar ververst elke 5) en de servicewerker kan het laatste beeld offline bewaren. Bij een storing komt het laatst opgehaalde beeld terug; pas als er niets is, een nette melding in plaats van een leeg vlak. Bronvermelding staat in de overlay, zoals Buienradar vraagt. Verder: de overlay-machinerie (20s-timer, balk, sluiten) staat nu een keer in plaats van per overlay, Http kan optioneel redirects volgen (de beeld-API leidt door naar een CDN), en alle /api/-paden zijn in de servicewerker network-first met een cachesleutel zonder querystring. Servicewerker naar v7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+72
-12
@@ -14,7 +14,8 @@ const FETCH_TIMEOUT_MS = 10000;
|
||||
const STALE_MINUTES = 30;
|
||||
const RELOAD_HOUR = 4;
|
||||
const FALLBACK_TZ = 'Europe/Berlin';
|
||||
const HOURLY_OVERLAY_MS = 20000;
|
||||
const OVERLAY_MS = 20000; // hoe lang een overlay blijft staan zonder tik
|
||||
const RADAR_BUCKET_MS = 240000; // hergebruikvenster voor het radarbeeld, gelijk aan de servercache
|
||||
const HOURLY_WIDTH = 1045; // = stage-breedte 1133 min 2x 44px padding, zelfde marge als de rest van het scherm
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
@@ -120,7 +121,8 @@ function renderForecast(days) {
|
||||
|
||||
/* ---------- uuroverzicht per dag ---------- */
|
||||
|
||||
let hourlyOverlayTimer = null;
|
||||
let overlayTimer = null;
|
||||
let openOverlayId = null;
|
||||
|
||||
function renderHourlyCharts(hourly) {
|
||||
const temps = hourly.map((h) => h.temp);
|
||||
@@ -147,25 +149,81 @@ function openHourOverlay(date) {
|
||||
const day = (state.data?.forecast || []).find((d) => d.date === date);
|
||||
if (!day) return;
|
||||
|
||||
clearTimeout(hourlyOverlayTimer);
|
||||
setText('ho-date', formatFullDate(day.date, state.tz));
|
||||
renderHourlyCharts(day.hourly || []);
|
||||
showOverlay('hour-overlay', 'ho-bar-fill');
|
||||
}
|
||||
|
||||
const fill = $('ho-bar-fill');
|
||||
/* ---------- regenradar ---------- */
|
||||
|
||||
function renderRainDetails(rain) {
|
||||
setText('ro-today', f1(rain.today));
|
||||
$('ro-days').innerHTML = rain.previous_days
|
||||
.map((p) => `<p><span>${dayAbbr(p.date, state.tz)}</span><b class="num">${fmm(p.mm)}</b></p>`)
|
||||
.join('');
|
||||
setText('ro-month', `${monthName(new Date(), state.tz)} ${f1(rain.month_total)} mm`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zet het radarbeeld klaar. De bucket in de URL verandert elke RADAR_BUCKET_MS, zodat de browser
|
||||
* binnen dat venster het al geladen beeld hergebruikt (tik = meteen zichtbaar) en daarna vanzelf
|
||||
* een verser beeld ophaalt. Er wordt niets op de achtergrond geladen zolang niemand kijkt.
|
||||
*/
|
||||
function loadRadarImage() {
|
||||
const img = $('ro-img');
|
||||
const status = $('ro-status');
|
||||
const src = `api/radar.php?t=${Math.floor(Date.now() / RADAR_BUCKET_MS)}`;
|
||||
if (img.getAttribute('src') === src && img.complete && img.naturalWidth > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
status.textContent = 'Radar laden…';
|
||||
status.hidden = false;
|
||||
img.hidden = true;
|
||||
img.onload = () => {
|
||||
status.hidden = true;
|
||||
img.hidden = false;
|
||||
};
|
||||
img.onerror = () => {
|
||||
status.textContent = 'Radar niet beschikbaar';
|
||||
status.hidden = false;
|
||||
img.hidden = true;
|
||||
};
|
||||
img.setAttribute('src', src);
|
||||
}
|
||||
|
||||
function openRadarOverlay() {
|
||||
if (!state.data) return;
|
||||
renderRainDetails(state.data.rain);
|
||||
loadRadarImage();
|
||||
showOverlay('radar-overlay', 'ro-bar-fill');
|
||||
}
|
||||
|
||||
/* ---------- gedeelde overlay-machinerie ---------- */
|
||||
|
||||
/** Toont een overlay, start de 20s-balk opnieuw en zet de sluittimer. */
|
||||
function showOverlay(id, barFillId) {
|
||||
closeOverlay();
|
||||
|
||||
const fill = $(barFillId);
|
||||
fill.style.transition = 'none';
|
||||
fill.style.transform = 'scaleX(1)';
|
||||
fill.getBoundingClientRect(); // forceer reflow zodat de volgende transitie opnieuw start
|
||||
fill.style.transition = `transform ${HOURLY_OVERLAY_MS}ms linear`;
|
||||
fill.style.transition = `transform ${OVERLAY_MS}ms linear`;
|
||||
fill.style.transform = 'scaleX(0)';
|
||||
|
||||
$('hour-overlay').hidden = false;
|
||||
hourlyOverlayTimer = setTimeout(closeHourOverlay, HOURLY_OVERLAY_MS);
|
||||
$(id).hidden = false;
|
||||
openOverlayId = id;
|
||||
overlayTimer = setTimeout(closeOverlay, OVERLAY_MS);
|
||||
}
|
||||
|
||||
function closeHourOverlay() {
|
||||
clearTimeout(hourlyOverlayTimer);
|
||||
hourlyOverlayTimer = null;
|
||||
$('hour-overlay').hidden = true;
|
||||
function closeOverlay() {
|
||||
clearTimeout(overlayTimer);
|
||||
overlayTimer = null;
|
||||
if (openOverlayId !== null) {
|
||||
$(openOverlayId).hidden = true;
|
||||
openOverlayId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function renderBanner() {
|
||||
@@ -319,7 +377,9 @@ $('forecast').addEventListener('click', (e) => {
|
||||
const day = e.target.closest('.a-day');
|
||||
if (day) openHourOverlay(day.dataset.date);
|
||||
});
|
||||
$('hour-overlay').addEventListener('click', closeHourOverlay);
|
||||
$('hour-overlay').addEventListener('click', closeOverlay);
|
||||
$('radar-overlay').addEventListener('click', closeOverlay);
|
||||
$('rain-block').addEventListener('click', openRadarOverlay);
|
||||
|
||||
$('rooms').addEventListener('click', (e) => {
|
||||
const room = e.target.closest('.a-room');
|
||||
|
||||
Reference in New Issue
Block a user