diff --git a/src/routes/weather/week/[location]/DailyStripSticky.svelte b/src/routes/weather/week/[location]/DailyStripSticky.svelte index 2b75ea9..b35b5f8 100644 --- a/src/routes/weather/week/[location]/DailyStripSticky.svelte +++ b/src/routes/weather/week/[location]/DailyStripSticky.svelte @@ -262,7 +262,7 @@ diff --git a/src/routes/weather/week/[location]/HourlyTable.svelte b/src/routes/weather/week/[location]/HourlyTable.svelte index 3e4c9e5..6f01d72 100644 --- a/src/routes/weather/week/[location]/HourlyTable.svelte +++ b/src/routes/weather/week/[location]/HourlyTable.svelte @@ -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} > @@ -582,6 +602,14 @@ {/if} + + {#if nowPercent != null} +
+ {/if} {#if isTodaySelected && nowPercent != null} {formatTemp(temp)} @@ -677,6 +710,7 @@ {@const temp = hourly.apparent_temperature[cell.idx]}
{formatTemp(temp)} @@ -691,6 +725,7 @@ {@const temp = hourly.dew_point_2m?.[cell.idx]} {formatTemp(temp)} @@ -706,6 +741,7 @@ {@const windDir = hourly.winddirection_10m[cell.idx]} {#if windDir != null && !isNaN(windDir)} @@ -730,6 +766,7 @@ {@const v = hourly.wind_gusts_10m?.[cell.idx]} @@ -761,6 +799,7 @@ {@const cloud = hourly.cloud_cover[cell.idx]} @@ -793,6 +833,7 @@ {@const v = hourly.uv_index?.[cell.idx]} @@ -808,6 +849,7 @@ {@const v = hourly.visibility?.[cell.idx]} {v != null && !isNaN(v) ? (v / 1000).toFixed(0) : '-'} @@ -823,6 +865,7 @@ {@const prob = hourly.precipitation_probability[cell.idx]} {v != null && !isNaN(v) && v > 0 ? v.toFixed(1) : '-'} @@ -864,7 +908,8 @@ > {/if} - + {#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" > {/if} - -
{/if} @@ -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 {