alignment and now

This commit is contained in:
Vincent van der Wal
2026-08-02 09:03:35 +02:00
parent 3fda55ddd2
commit ae50999c1c
5 changed files with 75 additions and 9 deletions
View File
+8 -2
View File
@@ -130,6 +130,7 @@
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import { formatZoned, getZonedHour } from '$lib/utils/date';
import { useNow } from '$lib/utils/now.svelte';
import { CHART_COLORS } from './data';
@@ -224,6 +225,10 @@
class: className = ''
}: Props = $props();
// Minute-resolution clock shared with the rest of the app; read in draw() so
// the current-time marker stays put as time passes.
const clock = useNow();
// ─── Constants ──────────────────────────────────────────────────────────────
const PAD_BOTTOM = 34;
@@ -1386,9 +1391,10 @@
}
}
// Current time marker
// Current time marker. Reading the shared clock (rather than Date.now())
// makes the enclosing draw effect re-run every minute, so the line moves.
if (showNow) {
const now = Date.now() / 1000;
const now = clock.current.getTime() / 1000;
if (now >= viewStart && now <= viewEnd) {
const x = xPix(now);
ctx.strokeStyle = CHART_COLORS.currentTimeLine;
+56
View File
@@ -0,0 +1,56 @@
import { browser } from '$app/environment';
/**
* Shared "now" clock.
*
* One timer, aligned to the wall-clock minute, drives every current-time marker
* in the app (the hourly table's NOW line, the meteogram's time marker) so they
* stay accurate without a page reload.
*
* The timer only runs while at least one component is subscribed, and re-aligns
* after each tick so it can't drift or fire twice within a minute (e.g. after
* the tab has been suspended).
*/
let current = $state(new Date());
let subscribers = 0;
let timer: ReturnType<typeof setTimeout> | undefined;
const MINUTE_MS = 60_000;
function scheduleTick() {
timer = setTimeout(
() => {
current = new Date();
scheduleTick();
},
MINUTE_MS - (Date.now() % MINUTE_MS)
);
}
/**
* Reactive current time, refreshed on every minute boundary while the calling
* component is mounted. On the server it just reports render time.
*/
export function useNow(): { readonly current: Date } {
if (browser) {
$effect(() => {
if (subscribers++ === 0) {
current = new Date();
scheduleTick();
}
return () => {
if (--subscribers === 0) {
clearTimeout(timer);
timer = undefined;
}
};
});
}
return {
get current() {
return browser ? current : new Date();
}
};
}
@@ -489,8 +489,8 @@
/* full state only: gap above the icons, and the day / night icon offsets
that tighten the pair around the cell centre */
--icon-gap-full: 2px;
--day-icon-nudge: 2px;
--night-icon-nudge: 4px;
--day-icon-nudge: 3px;
--night-icon-nudge: 0px;
/* compact only: signed shift that lands the lone icon dead centre in the
square tile (measured against the tile's mid-line) */
--icon-lift: 1px;
@@ -534,7 +534,7 @@
--tmax-nudge: 0px;
--tmin-nudge: 8px;
--icon-gap-full: 6px;
--day-icon-nudge: 3px;
--day-icon-nudge: 4px;
--night-icon-nudge: 6px;
--pb-full: 10px;
--pb-min: 3px;
@@ -769,11 +769,11 @@
and melts away completely when compact so the day icon re-centers. */
.night-icon {
display: block;
width: calc(var(--icon) * 0.42 * var(--rel));
height: calc(var(--icon) * 0.42 * var(--rel));
width: calc(var(--icon) * 0.55 * var(--rel));
height: calc(var(--icon) * 0.55 * var(--rel));
opacity: var(--rel);
margin-left: calc(-4px * var(--rel));
margin-bottom: calc(6px * var(--rel));
margin-bottom: calc(-4px * var(--rel));
transform: translateX(calc(-1 * var(--night-icon-nudge) * var(--k)));
}
/* weekday centered when full, pushed to the edges when compact */
@@ -10,6 +10,7 @@
} from '$lib/stores/settings';
import { formatUtcOffset, formatZoned, getZonedHour, isSameDayInZone } from '$lib/utils/date';
import { useNow } from '$lib/utils/now.svelte';
import { groupHover, setGroupHover } from '$lib/charts';
import * as m from '$lib/paraglide/messages';
@@ -74,7 +75,10 @@
$storedVariablePrefs.table?.[key] ?? defaultVariablePrefs.table[key] ?? true
);
const today = new Date();
// Ticks every minute, so the NOW line/label and the highlighted current-hour
// column keep up with the clock instead of freezing at page load.
const clock = useNow();
let today = $derived(clock.current);
const tempUnit = $derived(getTempUnit(units));
const windUnit = $derived(getWindUnit(units));
const precipUnit = $derived(getPrecipUnit(units));