move settings to one icon mobile

This commit is contained in:
Vincent van der Wal
2026-08-01 12:39:19 +02:00
parent 8ad02ea3f9
commit 5cc9a5428c
23 changed files with 1511 additions and 189 deletions
@@ -1,6 +1,8 @@
<script lang="ts">
import { onMount } from 'svelte';
import { resolve } from '$app/paths';
import { formatZoned, getRelativeDayLabel, isSameDayInZone } from '$lib/utils/date';
import { getTempStyle } from '../../utils/colors';
@@ -23,6 +25,11 @@
onExtend?: () => void;
canExtendPast?: boolean;
onExtendPast?: () => void;
/**
* Location segment for the hand-off links that replace the side buttons
* once their range is exhausted. Omit to leave the slots empty.
*/
locationRoute?: string;
}
let {
@@ -33,7 +40,8 @@
canExtend = false,
onExtend,
canExtendPast = false,
onExtendPast
onExtendPast,
locationRoute
}: Props = $props();
// ─── Scroll-driven collapse (full cards → compact strip) ────────────────────
@@ -82,10 +90,16 @@
let scrolledForRef: FetchedDaily | null = null;
$effect(() => {
const d = daily;
if (!d || !canExtendPast || !stripScrollEl || !daysWrapEl || scrolledForRef === d) return;
if (!d || !stripScrollEl || !daysWrapEl || scrolledForRef === d) return;
scrolledForRef = d;
const scroll = stripScrollEl;
const wrap = daysWrapEl;
if (!canExtendPast) {
// the past button has been replaced by the history link, which is a
// destination rather than a control: it stays in view instead of parked
scroll.scrollLeft = 0;
return;
}
// the delta form is self-correcting (a no-op once right), so apply after
// layout and once more after late-loading CSS/fonts settle — otherwise a
// sliver of the past button can stay visible on first paint
@@ -110,7 +124,7 @@
<div bind:this={sentinelEl} class="sentinel" aria-hidden="true"></div>
<div
class="daystrip sticky -top-3 z-30 -mx-3 lg:-top-6 lg:-mx-8"
class="daystrip sticky -top-3 z-30 -mx-3 lg:-top-6 lg:-mx-8 mt-1"
class:js-snap={needsSnapFallback}
class:compact
class:stuck
@@ -122,7 +136,7 @@
match the 15-days button on the other end -->
<button
type="button"
class="strip-side flex shrink-0 cursor-pointer flex-col items-center justify-center gap-0.5 rounded-xl border border-dashed border-primary/50 bg-primary/5 text-primary transition-colors hover:border-primary hover:bg-primary/10"
class="strip-side strip-park flex shrink-0 cursor-pointer flex-col items-center justify-center gap-0.5 rounded-xl border border-dashed border-primary/50 bg-primary/5 text-primary transition-colors hover:border-primary hover:bg-primary/10"
onclick={onExtendPast}
aria-label="Load the past 3 days"
title="Load the past 3 days"
@@ -141,6 +155,31 @@
</span>
<span class="text-[9px] leading-tight font-semibold">past</span>
</button>
{:else if locationRoute}
<!-- the past days are already in the strip: the only way further back
is the archive, so the button hands over to it -->
<a
href={resolve('/weather/historical/[location]', { location: locationRoute })}
class="strip-side flex shrink-0 cursor-pointer flex-col items-center justify-center gap-0.5 rounded-xl border border-dashed border-primary/50 bg-primary/5 text-primary transition-colors hover:border-primary hover:bg-primary/10"
aria-label="Open the historical weather archive"
title="Historical weather: any past date back to 1940"
>
<svg
class="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 2" />
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.5 9a9 9 0 1 0 2.2-3.6L3 8m0-4.5V8h4.5"
/>
</svg>
<span class="text-[9px] leading-tight font-semibold">history</span>
</a>
{/if}
<div class="strip-days flex" bind:this={daysWrapEl}>
@@ -322,6 +361,27 @@
</span>
<span class="text-[9px] leading-tight font-semibold">days</span>
</button>
{:else if locationRoute}
<!-- the strip is at the model's limit: anything further out is a
seasonal outlook, so the button hands over to it -->
<a
href={resolve('/weather/seasonal/[location]', { location: locationRoute })}
class="strip-side flex shrink-0 cursor-pointer flex-col items-center justify-center gap-0.5 rounded-xl border border-dashed border-primary/50 bg-primary/5 text-primary transition-colors hover:border-primary hover:bg-primary/10"
aria-label="Open the seasonal outlook"
title="Seasonal outlook: monthly trends for the months ahead"
>
<svg
class="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M3 17l6-6 4 4 7-7" />
<path stroke-linecap="round" stroke-linejoin="round" d="M16 8h5v5" />
</svg>
<span class="text-[9px] leading-tight font-semibold">seasonal</span>
</a>
{/if}
</div>
{/if}
@@ -514,7 +574,7 @@
(Browsers too old to register --strip-p simply switch instantly.) */
.daystrip.js-snap {
transition:
--strip-p 0.28s ease,
--strip-p 0.32s ease,
--stuck 0.15s ease;
}
.daystrip.js-snap.compact {
@@ -527,7 +587,7 @@
/* larger cards need a touch longer to feel smooth */
.daystrip.js-snap {
transition:
--strip-p 0.55s ease,
--strip-p 0.42s ease,
--stuck 0.15s ease;
}
}
@@ -549,13 +609,14 @@
}
/* Scroll containers clip at the PADDING edge, so a parked past button would
always leak a sliver across the row's left padding. Extending the days
group's left edge (only while the past button exists) moves max-scroll so
the button parks fully beyond the clip edge. */
.strip-side + .strip-days {
group's left edge (only while the parked past button exists) moves
max-scroll so the button parks fully beyond the clip edge. The history
link that replaces it is never parked, so it keeps the normal gap. */
.strip-park + .strip-days {
padding-left: calc(12px - var(--gap-min));
}
@media (min-width: 1024px) {
.strip-side + .strip-days {
.strip-park + .strip-days {
padding-left: calc(32px - var(--gap-min));
}
}