now improvements

This commit is contained in:
Vincent van der Wal
2026-08-02 09:53:39 +02:00
parent ae50999c1c
commit bde815c35a
2 changed files with 101 additions and 8 deletions
@@ -262,7 +262,7 @@
</span>
<span
class="rel-label overflow-hidden leading-[1.25] whitespace-nowrap {isToday
class="rel-label leading-[1.25] whitespace-nowrap {isToday
? 'font-semibold text-primary/80'
: 'text-muted-foreground'}"
>
@@ -328,6 +328,25 @@
: null
);
const NOW_LINE_W = 2;
// The line is painted as a background on the one cell it crosses (see the
// .now-cell rule) rather than as an overlay on top of the table, so it sits
// above each cell's colour tint but below its value and icon.
let nowCell = $derived.by((): { idx: number; offset: number } | null => {
if (nowLeftPx == null || cellData.length === 0) return null;
const colWidth = (tableWidth - headerColWidth) / cellData.length;
if (colWidth <= NOW_LINE_W) return null;
const col = Math.floor((nowLeftPx - headerColWidth) / colWidth);
if (col < 0 || col >= cellData.length) return null;
// keep the full line width inside the cell, which clips its overflow
const offset = nowLeftPx - headerColWidth - col * colWidth - NOW_LINE_W / 2;
return {
idx: cellData[col].idx,
offset: Math.max(0, Math.min(colWidth - NOW_LINE_W, offset))
};
});
// ─── Auto-scroll the (overflowing) table on day / interval change ────────────
// • Day change → today lands on the current-hour cell, other days on 06:00.
// • Interval change (3h ↔ 1h) → keep whatever cell is currently on the left.
@@ -501,6 +520,7 @@
class="relative {is3h
? 'min-w-[400px] md:min-w-[560px]'
: 'min-w-[780px] md:min-w-[1100px]'}"
style="--now-x:{nowCell?.offset ?? 0}px"
bind:clientWidth={tableWidth}
>
<table class="w-full table-fixed border-collapse whitespace-nowrap">
@@ -582,6 +602,14 @@
</span>
</div>
{/if}
<!-- Current-time line across the time row. Placed before the hour
labels so it paints under them, like it does in the rows below. -->
{#if nowPercent != null}
<div
class="pointer-events-none absolute inset-y-0 w-0.5 -translate-x-1/2 bg-red-500/75"
style="left:{nowPercent}%"
></div>
{/if}
<!-- "Now" label, aligned with the sunrise/sunset labels along the bottom -->
{#if isTodaySelected && nowPercent != null}
<span
@@ -597,6 +625,7 @@
{@const widthPct = 100 / cellData.length}
<span
class:col-empty={!cell.hasData}
class:now-label={cell.idx === nowCell?.idx}
class="absolute top-0 flex items-start pl-1 font-bold {is3h
? 'pt-2 text-sm'
: 'pt-2.5'}
@@ -638,6 +667,7 @@
{@const wCode = hourly.weather_code[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 leading-0"
class:cell-empty={!finite(wCode)}
class:icon-day={cell.isDaytime}
@@ -662,8 +692,11 @@
{@const style = getTempStyle(temp, String(units.temperature_unit))}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 font-bold {is3h ? 'text-lg' : 'text-[15px]'}"
style={finite(temp) ? `background-color:${style.bg};color:${style.fg}` : ''}
style={finite(temp)
? `background-color:${style.bg};color:${style.fg};--now-halo:${style.bg}`
: ''}
>
{formatTemp(temp)}
</td>
@@ -677,6 +710,7 @@
{@const temp = hourly.apparent_temperature[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 text-muted-foreground {is3h ? 'text-[13px]' : 'text-[11px]'}"
>
{formatTemp(temp)}
@@ -691,6 +725,7 @@
{@const temp = hourly.dew_point_2m?.[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 text-muted-foreground {is3h ? 'text-[13px]' : 'text-[11px]'}"
>
{formatTemp(temp)}
@@ -706,6 +741,7 @@
{@const windDir = hourly.winddirection_10m[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="relative cell h-12 align-middle leading-none"
>
{#if windDir != null && !isNaN(windDir)}
@@ -730,6 +766,7 @@
{@const v = hourly.wind_gusts_10m?.[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 font-semibold text-foreground/80 {is3h
? 'text-sm'
: 'text-xs'}"
@@ -746,6 +783,7 @@
{@const hum = hourly.relative_humidity_2m[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12"
style={finite(hum) ? `background:${getHumidityBg(hum)}` : ''}
>
@@ -761,6 +799,7 @@
{@const cloud = hourly.cloud_cover[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12"
style={finite(cloud)
? `background:rgba(140,160,180,${getCloudOpacity(cloud)})`
@@ -778,6 +817,7 @@
{@const v = hourly.pressure_msl?.[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 {is3h ? 'text-sm' : 'text-xs'}"
style="background:{getPressureBg(v ?? null)}"
>
@@ -793,6 +833,7 @@
{@const v = hourly.uv_index?.[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 font-semibold {is3h ? 'text-sm' : 'text-xs'}"
style="background:{getUvBg(v ?? null)}"
>
@@ -808,6 +849,7 @@
{@const v = hourly.visibility?.[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 {is3h ? 'text-sm' : 'text-xs'}"
>
{v != null && !isNaN(v) ? (v / 1000).toFixed(0) : '-'}
@@ -823,6 +865,7 @@
{@const prob = hourly.precipitation_probability[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell precip-cell h-12"
style="background:{getPrecipProbBg(prob ?? 0)}"
title={formatPrecipTooltip(precip, prob)}
@@ -844,6 +887,7 @@
{@const v = hourly.snowfall?.[cell.idx]}
<td
class:col-empty={!cell.hasData}
class:now-cell={cell.idx === nowCell?.idx}
class="cell h-12 {is3h ? 'text-sm' : 'text-xs'}"
>
{v != null && !isNaN(v) && v > 0 ? v.toFixed(1) : '-'}
@@ -864,7 +908,8 @@
></div>
{/if}
<!-- "Now" column highlight + exact-time line -->
<!-- "Now" column highlight. The current-time line itself is painted per
cell (.now-cell) so it stays under the values and icons. -->
{#if nowLeftPx != null}
{@const colWidth = (tableWidth - headerColWidth) / cellData.length}
{@const nowIdx = cellData.findIndex((c) => c.isNow)}
@@ -874,11 +919,6 @@
style="left:{headerColWidth + nowIdx * colWidth}px;width:{colWidth}px"
></div>
{/if}
<!-- full-height current-time line (its "Now" label lives in the time row) -->
<div
class="pointer-events-none absolute inset-y-0 z-10 w-0.5 -translate-x-1/2 bg-red-500/75"
style="left:{nowLeftPx}px"
></div>
{/if}
</div>
</div>
@@ -933,6 +973,59 @@
background: none !important;
}
/* ── Current-time line ──────────────────────────────────────────
Values sit on top of the line, so they get a soft halo in the colour of
whatever is behind them: the card by default, or the cell's own colour on
rows that paint one (set inline as --now-halo). Just enough to keep the
line from cutting through a glyph. */
.now-cell,
.now-label {
--now-halo: var(--color-card);
text-shadow:
0 0 2px var(--now-halo),
0 0 4px var(--now-halo);
}
/* Same idea for the weather and wind-arrow icons, which text-shadow can't
reach. */
.now-cell svg {
filter: drop-shadow(0 0 2px var(--now-halo)) drop-shadow(0 0 3px var(--now-halo));
}
/* The line itself: a background layer on the single cell it crosses (--now-x
is its offset within that cell). A background paints above the cell's own
colour tint but below its text and icons, which an overlay element on top
of the table can't do. !important because several rows set their tint with
the `background` shorthand inline, which would otherwise reset this image. */
.now-cell {
--now-line-color: color-mix(in oklab, var(--color-red-500) 75%, transparent);
--now-line: linear-gradient(
to right,
transparent var(--now-x),
var(--now-line-color) var(--now-x),
var(--now-line-color) calc(var(--now-x) + 2px),
transparent calc(var(--now-x) + 2px)
);
background-image: var(--now-line) !important;
background-repeat: no-repeat !important;
}
/* The row separator is painted over cell backgrounds, so it would chop the
line into per-row segments. On these cells it is switched off (a cell
border wins the collapse against the row's, same width and style) and
redrawn as a background layer below the line instead. */
.row + .row .now-cell {
border-top: 1px solid transparent;
background-image:
var(--now-line),
linear-gradient(
to bottom,
color-mix(in oklab, var(--color-border) 70%, transparent) 1px,
transparent 1px
) !important;
background-repeat: no-repeat, no-repeat !important;
}
/* A whole column the model never reached - dimmed end to end, hour label
included, so the point where the forecast runs out is obvious. */
.col-empty {