layout shifts

This commit is contained in:
Vincent van der Wal
2026-08-01 13:37:04 +02:00
parent 792da49cac
commit fe961a27ae
24 changed files with 427 additions and 210 deletions
@@ -48,6 +48,12 @@
);
}
/** Drop a single entry from the recent list (favourites are unaffected). */
function removeRecent(loc: GeoLocation) {
const key = locationKey(loc);
storedRecentLocations.update((list) => list.filter((l) => locationKey(l) !== key));
}
function toggleFavorite(loc: GeoLocation) {
const key = locationKey(loc);
storedFavoriteLocations.update((list) =>
@@ -126,7 +132,7 @@
})();
</script>
{#snippet locationRow(location: GeoLocation)}
{#snippet locationRow(location: GeoLocation, removable: boolean)}
{@const fav = favKeys.has(locationKey(location))}
<div
class="group flex items-center rounded-md border border-transparent transition-[background,border-color] duration-150 hover:border-border hover:bg-accent"
@@ -172,6 +178,18 @@
/>
</svg>
</button>
{#if removable}
<button
class="mr-1 flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/50 hover:bg-background hover:text-destructive"
onclick={() => removeRecent(location)}
aria-label="Remove {location.name} from recent locations"
title="Remove from recent"
>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
<path stroke-linecap="round" d="M6 6l12 12M18 6L6 18" />
</svg>
</button>
{/if}
</div>
{/snippet}
@@ -262,7 +280,7 @@
</div>
<div class="space-y-0.5">
{#each $storedFavoriteLocations as loc (locationKey(loc))}
{@render locationRow(loc)}
{@render locationRow(loc, false)}
{/each}
</div>
{/if}
@@ -276,7 +294,7 @@
</div>
<div class="space-y-0.5">
{#each recentToShow as loc (locationKey(loc))}
{@render locationRow(loc)}
{@render locationRow(loc, true)}
{/each}
</div>
{/if}
@@ -305,7 +323,7 @@
{:else if results.results && results.results.length > 0}
<div class="space-y-0.5">
{#each results.results as location, i (i)}
{@render locationRow(location)}
{@render locationRow(location, false)}
{/each}
</div>
{:else if results.results}
+6 -2
View File
@@ -1,6 +1,8 @@
<script lang="ts">
import { resolve } from '$app/paths';
import LogoMark from './logo-mark.svelte';
// A hand-picked set of the prerendered city pages (see
// routes/weather/locations/city-names100.json) — instant navigation targets
// that double as SEO entry points.
@@ -37,9 +39,11 @@
<!-- Brand -->
<div class="max-w-xs">
<div class="flex items-center gap-2">
<span class="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-lg"
></span
<span
class="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground"
>
<LogoMark />
</span>
<span class="text-lg font-bold tracking-tight">Drizz.li</span>
</div>
<p class="mt-3 text-sm leading-relaxed text-muted-foreground">
+25 -14
View File
@@ -74,16 +74,26 @@
<header
class="topbar flex h-14 shrink-0 items-center gap-3 border-b border-topbar-border bg-topbar px-3 md:px-4"
>
<!-- Mobile menu toggle -->
<button
class="-ms-1 flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground md:hidden"
onclick={onMenuToggle}
aria-label="Toggle menu"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.75">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<!-- Mobile menu toggle. On phones this side and the settings side both take
an equal share of the leftover width, which lands the search box dead
centre; on md+ they collapse and the spacer below does the work. -->
<div class="flex flex-1 items-center md:flex-none">
<button
class="-ms-1 flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground md:hidden"
onclick={onMenuToggle}
aria-label="Toggle menu"
>
<svg
class="h-5 w-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="1.75"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<!-- Current location display -->
{#if location}
@@ -109,8 +119,8 @@
</div>
{/if}
<!-- Spacer -->
<div class="flex-1"></div>
<!-- Spacer (md+ only: on phones the equal side columns centre the search) -->
<div class="hidden flex-1 md:block"></div>
<!-- Location search: primary way to switch places, so keep it loud -->
<div class="w-full max-w-sm md:max-w-md">
@@ -123,8 +133,9 @@
</div>
<!-- Phones only have room for one control, so units, theme and supporter
status collapse into a single settings menu below md. -->
<div class="md:hidden">
status collapse into a single settings menu below md. This side mirrors
the menu-button column so the search lands dead centre. -->
<div class="flex flex-1 justify-end md:hidden">
<SettingsMenu />
</div>
@@ -0,0 +1,21 @@
<script lang="ts">
// umbrella-with-rain mark (matches the favicon); shared by the sidebar home
// link and the footer so the brand reads the same in both places
interface Props {
class?: string;
}
let { class: className = 'h-5 w-5' }: Props = $props();
</script>
<svg class={className} fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.75">
<!-- rain falls from above onto the canopy -->
<path stroke-linecap="round" d="M4.5 3v.01M19.5 3v.01M8.5 1.5v.01M15.5 2.5v.01" />
<path stroke-linecap="round" d="M12 4.5V6" />
<path
fill="currentColor"
stroke="none"
d="M4 14a8 8 0 0 1 16 0c-.66-1-1.99-1-2.66 0-.67-1-2-1-2.67 0-.67-1-2-1-2.67 0-.67-1-2-1-2.67 0C8.66 13 7.33 13 6.66 14 6 13 4.66 13 4 14Z"
/>
<path stroke-linecap="round" d="M12 14v5a1.9 1.9 0 0 1-3.8 0" />
</svg>
@@ -2,6 +2,8 @@
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import LogoMark from './logo-mark.svelte';
interface Props {
collapsed?: boolean;
onToggle?: () => void;
@@ -78,24 +80,7 @@
<div
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground"
>
<!-- umbrella-with-rain logo mark (matches favicon) -->
<svg
class="h-5 w-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="1.75"
>
<!-- rain falls from above onto the canopy -->
<path stroke-linecap="round" d="M4.5 3v.01M19.5 3v.01M8.5 1.5v.01M15.5 2.5v.01" />
<path stroke-linecap="round" d="M12 4.5V6" />
<path
fill="currentColor"
stroke="none"
d="M4 14a8 8 0 0 1 16 0c-.66-1-1.99-1-2.66 0-.67-1-2-1-2.67 0-.67-1-2-1-2.67 0-.67-1-2-1-2.67 0C8.66 13 7.33 13 6.66 14 6 13 4.66 13 4 14Z"
/>
<path stroke-linecap="round" d="M12 14v5a1.9 1.9 0 0 1-3.8 0" />
</svg>
<LogoMark />
</div>
{#if !collapsed}
<span class="text-sm font-bold tracking-tight whitespace-nowrap text-sidebar-foreground">