layout shifts
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user