diff --git a/dist/index.html b/dist/index.html
index b43ae80..7e0404d 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -7773,61 +7773,18 @@ async function fetchLightningStrikes({ lat, lon, radiusKm = 150, limit = 200 } =
if (!data || !Array.isArray(data.strikes)) throw new Error("Lightning relay returned an invalid response");
return data.strikes;
}
-/**
-* Project a lat/lon onto the radar tile layer's pixel coordinates.
-*
-* Mirrors the slippy-map math in PrecipitationRadar: the layer is a grid of
-* TILE_SIZE tiles centered on the location's center tile (cx, cy in tile
-* space). A strike at world coordinates is converted to floating tile
-* coordinates at the given zoom, then to pixels relative to the tile layer's
-* origin (the tile two rows/cols above-left of the center tile, since the grid
-* is 5x5 and centered).
-*
-* @param {number} lat
-* @param {number} lon
-* @param {Object} opts
-* @param {number} opts.zoom
-* @param {number} opts.cx - center tile X (float)
-* @param {number} opts.cy - center tile Y (float)
-* @param {number} [opts.tileSize=256]
-* @returns {{ x: number, y: number }} Pixel coords within the tile layer.
-*/
-function projectStrikeToLayer(lat, lon, { zoom, cx, cy, tileSize = 256 }) {
- const fx = tileX(lon, zoom);
- const fy = tileY(lat, zoom);
- const tcx = Math.floor(cx);
- const tcy = Math.floor(cy);
- return {
- x: (fx - (tcx - 2)) * tileSize,
- y: (fy - (tcy - 2)) * tileSize
- };
-}
-function tileX(lon, z) {
- return (lon + 180) / 360 * Math.pow(2, z);
-}
-function tileY(lat, z) {
- const rad = lat * Math.PI / 180;
- return (1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) * Math.pow(2, z - 1);
-}
//#endregion
//#region src/components/PrecipitationRadar.svelte
-init_db();
-var root$4 = /* @__PURE__ */ from_html(``);
-var root_1$4 = /* @__PURE__ */ from_html(`
`);
-var root_2$4 = /* @__PURE__ */ from_html(`
`);
-var root_3$3 = /* @__PURE__ */ from_html(`
`, 1);
-var root_4$3 = /* @__PURE__ */ from_html(``);
-var root_5$3 = /* @__PURE__ */ from_html(``);
-var root_6$2 = /* @__PURE__ */ from_html(`
`, 1);
-var root_7$1 = /* @__PURE__ */ from_html(`⏳ Loading radar...
`);
-var root_8 = /* @__PURE__ */ from_html(`
`);
-var root_9 = /* @__PURE__ */ from_html(`⏳ Loading lightning...
`);
-var root_10 = /* @__PURE__ */ from_html(`
`);
-var root_11 = /* @__PURE__ */ from_html(`⚡ No recent strikes nearby
`);
-var root_12 = /* @__PURE__ */ from_html(``);
-var root_13 = /* @__PURE__ */ from_html(`No radar data available.
`);
-var root_14 = /* @__PURE__ */ from_html(` `, 1);
-var root_15 = /* @__PURE__ */ from_html(``);
+var root$4 = /* @__PURE__ */ from_html(`
`);
+var root_1$4 = /* @__PURE__ */ from_html(`
`);
+var root_2$4 = /* @__PURE__ */ from_html(`
`, 1);
+var root_3$3 = /* @__PURE__ */ from_html(`
`, 1);
+var root_4$3 = /* @__PURE__ */ from_html(`⏳ Loading radar...
`);
+var root_5$3 = /* @__PURE__ */ from_html(`
`);
+var root_6$2 = /* @__PURE__ */ from_html(``);
+var root_7$1 = /* @__PURE__ */ from_html(`No radar data available.
`);
+var root_8 = /* @__PURE__ */ from_html(` `, 1);
+var root_9 = /* @__PURE__ */ from_html(``);
function PrecipitationRadar($$anchor, $$props) {
push($$props, true);
let radarData = /* @__PURE__ */ state(null);
@@ -7851,7 +7808,6 @@ function PrecipitationRadar($$anchor, $$props) {
const TILE_SIZE = 256;
const RADAR_COLOR = 4;
const loc = /* @__PURE__ */ user_derived(() => app$1.selectedLocation);
- let showLightning = /* @__PURE__ */ state(proxy(app$1.settings.showLightning || false));
let strikes = /* @__PURE__ */ state(proxy([]));
let lightningError = /* @__PURE__ */ state("");
let lightningLoading = /* @__PURE__ */ state(false);
@@ -7883,7 +7839,7 @@ function PrecipitationRadar($$anchor, $$props) {
}
}
async function loadLightning() {
- if (!get(loc) || !get(showLightning)) return;
+ if (!get(loc)) return;
set(lightningLoading, true);
set(lightningError, "");
try {
@@ -7902,17 +7858,6 @@ function PrecipitationRadar($$anchor, $$props) {
set(lightningLoading, false);
}
}
- function toggleLightning() {
- set(showLightning, !get(showLightning));
- app$1.settings = {
- ...app$1.settings,
- showLightning: get(showLightning)
- };
- saveSettings({
- ...app$1.settings,
- alertThresholds: { ...app$1.settings.alertThresholds }
- }).catch(() => {});
- }
function startLightningTimer() {
stopLightningTimer();
lightningTimer = setInterval(loadLightning, LIGHTNING_REFRESH_MS);
@@ -7924,7 +7869,7 @@ function PrecipitationRadar($$anchor, $$props) {
}
}
user_effect(() => {
- if (get(showLightning) && get(loc)) {
+ if (get(loc)) {
loadLightning();
startLightningTimer();
return () => {
@@ -7936,15 +7881,6 @@ function PrecipitationRadar($$anchor, $$props) {
set(lightningError, "");
}
});
- const strikeMarkers = /* @__PURE__ */ user_derived(() => get(showLightning) ? get(strikes).map((s) => ({
- ...s,
- ...projectStrikeToLayer(s.lat, s.lon, {
- zoom: get(zoom),
- cx: get(cx),
- cy: get(cy),
- tileSize: TILE_SIZE
- })
- })) : []);
const pastFrames = /* @__PURE__ */ user_derived(() => get(radarData)?.radar?.past || []);
const nowcastFrames = /* @__PURE__ */ user_derived(() => get(radarData)?.radar?.nowcast || []);
const allFrames = /* @__PURE__ */ user_derived(() => [...get(pastFrames), ...get(nowcastFrames)]);
@@ -8071,58 +8007,41 @@ function PrecipitationRadar($$anchor, $$props) {
}
const isPast = /* @__PURE__ */ user_derived(() => get(currentIdx) < get(pastFrames).length);
const frameLabel = /* @__PURE__ */ user_derived(() => get(isPast) ? "Past" : "Forecast");
- var div = root_15();
- var div_1 = child(div);
- var node = sibling(child(div_1), 2);
+ var div = root_9();
+ var node = sibling(child(div), 2);
var consequent = ($$anchor) => {
- var button = root$4();
- let classes;
- template_effect(() => {
- classes = set_class(button, 1, "layer-toggle svelte-tjvfz7", null, classes, { on: get(showLightning) });
- set_attribute(button, "aria-checked", get(showLightning));
- });
- delegated("click", button, toggleLightning);
- append($$anchor, button);
- };
- if_block(node, ($$render) => {
- if (get(loc)) $$render(consequent);
- });
- reset(div_1);
- var node_1 = sibling(div_1, 2);
- var consequent_1 = ($$anchor) => {
- var div_2 = root_1$4();
- var span = child(div_2);
+ var div_1 = root$4();
+ var span = child(div_1);
var text = child(span);
reset(span);
- var button_1 = sibling(span, 2);
- reset(div_2);
+ var button = sibling(span, 2);
+ reset(div_1);
template_effect(() => set_text(text, `⚠️ ${get(error) ?? ""}`));
- delegated("click", button_1, loadRadar);
- append($$anchor, div_2);
+ delegated("click", button, loadRadar);
+ append($$anchor, div_1);
};
var alternate = ($$anchor) => {
- var fragment = root_14();
- var div_3 = first_child(fragment);
- let classes_1;
- var node_2 = child(div_3);
- var consequent_4 = ($$anchor) => {
- var fragment_1 = root_6$2();
- var div_4 = first_child(fragment_1);
- var node_3 = child(div_4);
- each(node_3, 17, () => get(tiles), (t) => t.col + "_" + t.row, ($$anchor, t) => {
- var fragment_2 = root_3$3();
+ var fragment = root_8();
+ var div_2 = first_child(fragment);
+ let classes;
+ var node_1 = child(div_2);
+ var consequent_2 = ($$anchor) => {
+ var fragment_1 = root_3$3();
+ var div_3 = first_child(fragment_1);
+ each(div_3, 21, () => get(tiles), (t) => t.col + "_" + t.row, ($$anchor, t) => {
+ var fragment_2 = root_2$4();
var img = first_child(fragment_2);
- var node_4 = sibling(img, 2);
- var consequent_2 = ($$anchor) => {
- var img_1 = root_2$4();
+ var node_2 = sibling(img, 2);
+ var consequent_1 = ($$anchor) => {
+ var img_1 = root_1$4();
template_effect(($0) => {
set_attribute(img_1, "src", $0);
set_style(img_1, `grid-column: ${get(t).col + 3}; grid-row: ${get(t).row + 3}`);
}, [() => radarUrl(get(t).tx, get(t).ty)]);
append($$anchor, img_1);
};
- if_block(node_4, ($$render) => {
- if (get(frame)) $$render(consequent_2);
+ if_block(node_2, ($$render) => {
+ if (get(frame)) $$render(consequent_1);
});
template_effect(($0) => {
set_attribute(img, "src", $0);
@@ -8130,133 +8049,95 @@ function PrecipitationRadar($$anchor, $$props) {
}, [() => osmUrl(get(t).tx, get(t).ty)]);
append($$anchor, fragment_2);
});
- var node_5 = sibling(node_3, 2);
- var consequent_3 = ($$anchor) => {
- var div_5 = root_5$3();
- each(div_5, 21, () => get(strikeMarkers), (s) => s.time + "_" + s.lat + "_" + s.lon, ($$anchor, s) => {
- var span_1 = root_4$3();
- template_effect(($0) => {
- set_attribute(span_1, "title", $0);
- set_style(span_1, `left: ${get(s).x ?? ""}px; top: ${get(s).y ?? ""}px`);
- }, [() => "Strike " + (/* @__PURE__ */ new Date(get(s).time * 1e3)).toLocaleString()]);
- append($$anchor, span_1);
- });
- reset(div_5);
- append($$anchor, div_5);
- };
- if_block(node_5, ($$render) => {
- if (get(showLightning)) $$render(consequent_3);
- });
- reset(div_4);
- var div_6 = sibling(div_4, 2);
+ reset(div_3);
+ var div_4 = sibling(div_3, 2);
template_effect(() => {
- set_style(div_4, `transform: translate(${get(panX) + 128 - get(pxOff)}px, ${get(panY) + 128 - get(pyOff)}px)`);
- set_attribute(div_6, "title", get(loc)?.name || "");
- set_style(div_6, `transform: translate(calc(-50% + ${get(panX) ?? ""}px), calc(-50% + ${get(panY) ?? ""}px))`);
+ set_style(div_3, `transform: translate(${get(panX) + 128 - get(pxOff)}px, ${get(panY) + 128 - get(pyOff)}px)`);
+ set_attribute(div_4, "title", get(loc)?.name || "");
+ set_style(div_4, `transform: translate(calc(-50% + ${get(panX) ?? ""}px), calc(-50% + ${get(panY) ?? ""}px))`);
});
append($$anchor, fragment_1);
};
- if_block(node_2, ($$render) => {
- if (get(loc)) $$render(consequent_4);
+ if_block(node_1, ($$render) => {
+ if (get(loc)) $$render(consequent_2);
});
- var node_6 = sibling(node_2, 2);
- var consequent_5 = ($$anchor) => {
- append($$anchor, root_7$1());
+ var node_3 = sibling(node_1, 2);
+ var consequent_3 = ($$anchor) => {
+ append($$anchor, root_4$3());
};
- if_block(node_6, ($$render) => {
- if (get(loading)) $$render(consequent_5);
+ if_block(node_3, ($$render) => {
+ if (get(loading)) $$render(consequent_3);
});
- var node_7 = sibling(node_6, 2);
- var consequent_6 = ($$anchor) => {
- var div_8 = root_8();
- var span_2 = child(div_8);
- var text_1 = child(span_2);
- reset(span_2);
- var button_2 = sibling(span_2, 2);
- reset(div_8);
+ var node_4 = sibling(node_3, 2);
+ var consequent_4 = ($$anchor) => {
+ var div_6 = root_5$3();
+ var span_1 = child(div_6);
+ var text_1 = child(span_1);
+ reset(span_1);
+ var button_1 = sibling(span_1, 2);
+ reset(div_6);
template_effect(() => set_text(text_1, `⚠️ ${get(error) ?? ""}`));
- delegated("click", button_2, loadRadar);
- append($$anchor, div_8);
+ delegated("click", button_1, loadRadar);
+ append($$anchor, div_6);
};
- if_block(node_7, ($$render) => {
- if (get(error)) $$render(consequent_6);
+ if_block(node_4, ($$render) => {
+ if (get(error)) $$render(consequent_4);
});
- var node_8 = sibling(node_7, 2);
- var consequent_7 = ($$anchor) => {
- append($$anchor, root_9());
- };
- var consequent_8 = ($$anchor) => {
- var div_10 = root_10();
- var span_3 = child(div_10);
- var text_2 = child(span_3);
- reset(span_3);
- reset(div_10);
- template_effect(() => set_text(text_2, `⚡ ${get(lightningError) ?? ""} (relay not live yet)`));
- append($$anchor, div_10);
- };
- var consequent_9 = ($$anchor) => {
- append($$anchor, root_11());
- };
- if_block(node_8, ($$render) => {
- if (get(showLightning) && get(lightningLoading)) $$render(consequent_7);
- else if (get(showLightning) && get(lightningError)) $$render(consequent_8, 1);
- else if (get(showLightning) && get(strikes).length === 0 && !get(lightningLoading)) $$render(consequent_9, 2);
- });
- var div_12 = sibling(node_8, 2);
- var button_3 = child(div_12);
- var span_4 = sibling(button_3, 2);
- var text_3 = child(span_4, true);
- reset(span_4);
- var button_4 = sibling(span_4, 2);
- reset(div_12);
- reset(div_3);
- var node_9 = sibling(div_3, 2);
- var consequent_10 = ($$anchor) => {
- var div_13 = root_12();
- var div_14 = child(div_13);
- var button_5 = child(div_14);
+ var div_7 = sibling(node_4, 2);
+ var button_2 = child(div_7);
+ var span_2 = sibling(button_2, 2);
+ var text_2 = child(span_2, true);
+ reset(span_2);
+ var button_3 = sibling(span_2, 2);
+ reset(div_7);
+ reset(div_2);
+ var node_5 = sibling(div_2, 2);
+ var consequent_5 = ($$anchor) => {
+ var div_8 = root_6$2();
+ var div_9 = child(div_8);
+ var button_4 = child(div_9);
+ var button_5 = sibling(button_4, 2);
+ var text_3 = child(button_5, true);
+ reset(button_5);
var button_6 = sibling(button_5, 2);
- var text_4 = child(button_6, true);
- reset(button_6);
- var button_7 = sibling(button_6, 2);
- reset(div_14);
- var div_15 = sibling(div_14, 2);
- var input = child(div_15);
+ reset(div_9);
+ var div_10 = sibling(div_9, 2);
+ var input = child(div_10);
remove_input_defaults(input);
- reset(div_15);
- var div_16 = sibling(div_15, 2);
- var span_5 = child(div_16);
- var text_5 = child(span_5, true);
+ reset(div_10);
+ var div_11 = sibling(div_10, 2);
+ var span_3 = child(div_11);
+ var text_4 = child(span_3, true);
+ reset(span_3);
+ var span_4 = sibling(span_3, 2);
+ var text_5 = child(span_4, true);
+ reset(span_4);
+ var span_5 = sibling(span_4, 2);
+ let classes_1;
+ var text_6 = child(span_5, true);
reset(span_5);
var span_6 = sibling(span_5, 2);
- var text_6 = child(span_6, true);
+ var text_7 = child(span_6);
reset(span_6);
- var span_7 = sibling(span_6, 2);
- let classes_2;
- var text_7 = child(span_7, true);
- reset(span_7);
- var span_8 = sibling(span_7, 2);
- var text_8 = child(span_8);
- reset(span_8);
- reset(div_16);
- reset(div_13);
+ reset(div_11);
+ reset(div_8);
template_effect(($0, $1) => {
- set_attribute(button_6, "aria-label", get(playing) ? "Pause" : "Play");
- set_text(text_4, get(playing) ? "⏸" : "▶");
+ set_attribute(button_5, "aria-label", get(playing) ? "Pause" : "Play");
+ set_text(text_3, get(playing) ? "⏸" : "▶");
set_attribute(input, "max", get(totalFrames) - 1);
set_value(input, get(currentIdx));
- set_text(text_5, $0);
- set_text(text_6, $1);
- classes_2 = set_class(span_7, 1, "tl-badge svelte-tjvfz7", null, classes_2, {
+ set_text(text_4, $0);
+ set_text(text_5, $1);
+ classes_1 = set_class(span_5, 1, "tl-badge svelte-tjvfz7", null, classes_1, {
"tl-past": get(isPast),
"tl-future": !get(isPast)
});
- set_text(text_7, get(frameLabel));
- set_text(text_8, `${get(currentIdx) + 1}/${get(totalFrames) ?? ""}`);
+ set_text(text_6, get(frameLabel));
+ set_text(text_7, `${get(currentIdx) + 1}/${get(totalFrames) ?? ""}`);
}, [() => fmtTime(get(frame).time), () => fmtDate(get(frame).time)]);
- delegated("click", button_5, stepBack);
- delegated("click", button_6, togglePlay);
- delegated("click", button_7, stepForward);
+ delegated("click", button_4, stepBack);
+ delegated("click", button_5, togglePlay);
+ delegated("click", button_6, stepForward);
delegated("input", input, (e) => {
set(playing, false);
if (animRef) {
@@ -8265,35 +8146,35 @@ function PrecipitationRadar($$anchor, $$props) {
}
set(currentIdx, Number(e.target.value), true);
});
- append($$anchor, div_13);
+ append($$anchor, div_8);
};
- var consequent_11 = ($$anchor) => {
- var div_17 = root_13();
- var button_8 = sibling(child(div_17), 2);
- reset(div_17);
- delegated("click", button_8, loadRadar);
- append($$anchor, div_17);
+ var consequent_6 = ($$anchor) => {
+ var div_12 = root_7$1();
+ var button_7 = sibling(child(div_12), 2);
+ reset(div_12);
+ delegated("click", button_7, loadRadar);
+ append($$anchor, div_12);
};
- if_block(node_9, ($$render) => {
- if (get(frame)) $$render(consequent_10);
- else if (!get(loading)) $$render(consequent_11, 1);
+ if_block(node_5, ($$render) => {
+ if (get(frame)) $$render(consequent_5);
+ else if (!get(loading)) $$render(consequent_6, 1);
});
template_effect(() => {
- classes_1 = set_class(div_3, 1, "radar-viewport svelte-tjvfz7", null, classes_1, { dragging: get(dragging) });
- button_3.disabled = get(zoom) >= 11;
- set_text(text_3, get(zoom));
- button_4.disabled = get(zoom) <= 2;
+ classes = set_class(div_2, 1, "radar-viewport svelte-tjvfz7", null, classes, { dragging: get(dragging) });
+ button_2.disabled = get(zoom) >= 11;
+ set_text(text_2, get(zoom));
+ button_3.disabled = get(zoom) <= 2;
});
- delegated("pointerdown", div_3, onPointerDown);
- delegated("pointermove", div_3, onPointerMove);
- delegated("pointerup", div_3, onPointerUp);
- event("pointerleave", div_3, onPointerUp);
- delegated("click", button_3, zoomIn);
- delegated("click", button_4, zoomOut);
+ delegated("pointerdown", div_2, onPointerDown);
+ delegated("pointermove", div_2, onPointerMove);
+ delegated("pointerup", div_2, onPointerUp);
+ event("pointerleave", div_2, onPointerUp);
+ delegated("click", button_2, zoomIn);
+ delegated("click", button_3, zoomOut);
append($$anchor, fragment);
};
- if_block(node_1, ($$render) => {
- if (get(error) && !get(loc)) $$render(consequent_1);
+ if_block(node, ($$render) => {
+ if (get(error) && !get(loc)) $$render(consequent);
else $$render(alternate, -1);
});
reset(div);
@@ -9314,7 +9195,7 @@ delegate([
]);
//#endregion
//#region src/main.js
-console.info({ commit_hash: "97b676093a00eb7dc56bd07219df7b9446bc6e2e" });
+console.info({ commit_hash: "86acc5614316d179e884e15c8c697a00896da03e" });
registerServiceWorker();
mount(App, { target: document.getElementById("app") });
//#endregion
@@ -10562,31 +10443,6 @@ input::placeholder {
gap: 8px;
}
- .layer-toggle.svelte-tjvfz7 {
- display: inline-flex;
- align-items: center;
- gap: 5px;
- padding: 5px 11px;
- font-size: 0.78rem;
- font-weight: 600;
- border-radius: 16px;
- border: 1px solid var(--color-border);
- background: var(--color-surface);
- color: var(--color-text-muted);
- cursor: pointer;
- margin-bottom: 12px;
- transition: background var(--transition), color var(--transition), border-color var(--transition);
- }
- .layer-toggle.svelte-tjvfz7:hover {
- border-color: var(--color-primary);
- color: var(--color-text);
- }
- .layer-toggle.on.svelte-tjvfz7 {
- background: rgba(250, 204, 21, 0.18);
- border-color: #facc15;
- color: #fde047;
- }
-
.radar-status.svelte-tjvfz7,
.radar-status-err.svelte-tjvfz7 {
display: flex;
@@ -10690,40 +10546,6 @@ input::placeholder {
z-index: 1;
}
- /* ── Lightning strike layer ── */
- .lightning-layer.svelte-tjvfz7 {
- position: absolute;
- inset: 0;
- z-index: 2;
- pointer-events: none;
- }
-
- .lightning-strike.svelte-tjvfz7 {
- position: absolute;
- width: 8px;
- height: 8px;
- margin-left: -4px;
- margin-top: -4px;
- border-radius: 50%;
- background: #facc15;
- box-shadow: 0 0 6px 2px rgba(250, 204, 21, 0.85);
- pointer-events: auto;
- }
-
- .lightning-status.svelte-tjvfz7 {
- position: absolute;
- top: 12px;
- right: 12px;
- z-index: 10;
- padding: 5px 10px;
- background: rgba(10, 22, 40, 0.8);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-sm);
- pointer-events: none;
- }
- .lightning-status-err.svelte-tjvfz7 {
- border-color: rgba(239, 68, 68, 0.4);
- }
/* ── Location marker ── */
.location-marker.svelte-tjvfz7 {