more fluent design
This commit is contained in:
@@ -101,11 +101,11 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.chart-bleed {
|
.chart-bleed {
|
||||||
/* Bleed exactly into the page padding on mobile (main has p-5 =
|
/* Bleed exactly into the page padding on mobile (main has p-3 =
|
||||||
1.25rem) for edge-to-edge charts, and a bit past the content
|
0.75rem) for edge-to-edge charts, and a bit past the content
|
||||||
column on md+ (main has 2rem padding) for extra readability. */
|
column on md+ (main has 2rem padding) for extra readability. */
|
||||||
margin-left: -1.25rem;
|
margin-left: -0.75rem;
|
||||||
margin-right: -1.25rem;
|
margin-right: -0.75rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export interface ChartPanel {
|
|||||||
export const defaultChartLayout: ChartPanel[] = [
|
export const defaultChartLayout: ChartPanel[] = [
|
||||||
{ id: 'panel-1', variables: ['weather_icons', 'temperature', 'cloud_cover'] },
|
{ id: 'panel-1', variables: ['weather_icons', 'temperature', 'cloud_cover'] },
|
||||||
{ id: 'panel-2', variables: ['precipitation', 'precipitation_probability'] },
|
{ id: 'panel-2', variables: ['precipitation', 'precipitation_probability'] },
|
||||||
{ id: 'panel-3', variables: ['wind', 'humidity'] }
|
{ id: 'panel-3', variables: ['wind', 'wind_direction', 'humidity'] }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const storedChartLayout = persisted<ChartPanel[]>('chart_layout_v1', defaultChartLayout);
|
export const storedChartLayout = persisted<ChartPanel[]>('chart_layout_v1', defaultChartLayout);
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
<Header onMenuToggle={toggleMobileMenu} />
|
<Header onMenuToggle={toggleMobileMenu} />
|
||||||
|
|
||||||
<main
|
<main
|
||||||
class={fullBleed ? 'flex-1 overflow-hidden' : 'flex-1 overflow-y-auto p-5 md:px-8 md:py-6'}
|
class={fullBleed ? 'flex-1 overflow-hidden' : 'flex-1 overflow-y-auto p-3 md:px-8 md:py-6'}
|
||||||
>
|
>
|
||||||
{#if fullBleed}
|
{#if fullBleed}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -281,22 +281,68 @@
|
|||||||
|
|
||||||
<!-- ─── Page hero: location (matches the other forecast pages) ──────────────── -->
|
<!-- ─── Page hero: location (matches the other forecast pages) ──────────────── -->
|
||||||
|
|
||||||
<div class="mb-5 flex min-w-0 items-center gap-3">
|
<div class="mb-5 flex flex-wrap items-center justify-between gap-x-6 gap-y-3">
|
||||||
<img
|
<div class="flex min-w-0 items-center gap-3">
|
||||||
class="h-10 w-10 shrink-0 rounded-full shadow-sm ring-2 ring-border"
|
<img
|
||||||
src="/images/country-flags/{(location.country_code || 'united_nations').toLowerCase()}.svg"
|
class="h-10 w-10 shrink-0 rounded-full shadow-sm ring-2 ring-border"
|
||||||
alt={location.country ?? ''}
|
src="/images/country-flags/{(location.country_code || 'united_nations').toLowerCase()}.svg"
|
||||||
/>
|
alt={location.country ?? ''}
|
||||||
<div class="min-w-0">
|
/>
|
||||||
<h1 class="truncate text-2xl leading-tight font-bold tracking-tight md:text-3xl">
|
<div class="min-w-0">
|
||||||
{location.name}
|
<h1 class="truncate text-2xl leading-tight font-bold tracking-tight md:text-3xl">
|
||||||
</h1>
|
{location.name}
|
||||||
<p class="truncate text-sm text-muted-foreground">
|
</h1>
|
||||||
<span class="lg:hidden"
|
<p class="truncate text-sm text-muted-foreground">
|
||||||
>{#if location.admin1}{location.admin1},
|
<span class="lg:hidden"
|
||||||
{/if}{location.country ?? ''}<span class="mx-1 opacity-50">·</span></span
|
>{#if location.admin1}{location.admin1},
|
||||||
>Model comparison
|
{/if}{location.country ?? ''}<span class="mx-1 opacity-50">·</span></span
|
||||||
</p>
|
>Model comparison
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Range / zoom controls, aligned with the title like the other pages -->
|
||||||
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
|
<span class="hidden text-xs text-muted-foreground lg:inline">
|
||||||
|
drag or
|
||||||
|
<kbd class="rounded border border-border bg-muted px-1 py-0.5 font-sans text-[10px]">Ctrl</kbd
|
||||||
|
>
|
||||||
|
+ scroll to zoom
|
||||||
|
</span>
|
||||||
|
{#if zoomActive}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex cursor-pointer items-center gap-1.5 rounded-lg border border-primary/50 bg-primary/10 px-2.5 py-1 text-xs font-semibold text-primary transition-colors hover:bg-primary/15"
|
||||||
|
onclick={resetZoom}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="h-3.5 w-3.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v6h6M20 20v-6h-6" />
|
||||||
|
<path stroke-linecap="round" d="M20 10a8 8 0 0 0-14-4M4 14a8 8 0 0 0 14 4" />
|
||||||
|
</svg>
|
||||||
|
Reset zoom
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<div
|
||||||
|
class="inline-flex items-center rounded-lg bg-muted p-0.5 text-xs font-semibold"
|
||||||
|
role="group"
|
||||||
|
aria-label="Chart time range"
|
||||||
|
>
|
||||||
|
{#each rangePresets as preset (preset.label)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="cursor-pointer rounded-md px-2.5 py-1 whitespace-nowrap text-muted-foreground transition-colors hover:bg-background hover:text-foreground hover:shadow-sm"
|
||||||
|
onclick={preset.apply}
|
||||||
|
>
|
||||||
|
{preset.label}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -310,49 +356,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Range / zoom controls (same as the 7-day meteograms) -->
|
|
||||||
<div class="mb-3 flex flex-wrap items-center justify-end gap-3">
|
|
||||||
<span class="hidden text-xs text-muted-foreground md:inline">
|
|
||||||
drag or
|
|
||||||
<kbd class="rounded border border-border bg-muted px-1 py-0.5 font-sans text-[10px]">Ctrl</kbd>
|
|
||||||
+ scroll to zoom
|
|
||||||
</span>
|
|
||||||
{#if zoomActive}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex cursor-pointer items-center gap-1.5 rounded-lg border border-primary/50 bg-primary/10 px-2.5 py-1 text-xs font-semibold text-primary transition-colors hover:bg-primary/15"
|
|
||||||
onclick={resetZoom}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="h-3.5 w-3.5"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v6h6M20 20v-6h-6" />
|
|
||||||
<path stroke-linecap="round" d="M20 10a8 8 0 0 0-14-4M4 14a8 8 0 0 0 14 4" />
|
|
||||||
</svg>
|
|
||||||
Reset zoom
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
<div
|
|
||||||
class="inline-flex items-center rounded-lg bg-muted p-0.5 text-xs font-semibold"
|
|
||||||
role="group"
|
|
||||||
aria-label="Chart time range"
|
|
||||||
>
|
|
||||||
{#each rangePresets as preset (preset.label)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="cursor-pointer rounded-md px-2.5 py-1 whitespace-nowrap text-muted-foreground transition-colors hover:bg-background hover:text-foreground hover:shadow-sm"
|
|
||||||
onclick={preset.apply}
|
|
||||||
>
|
|
||||||
{preset.label}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- chart count derives from the selected variables (not the fetched data),
|
<!-- chart count derives from the selected variables (not the fetched data),
|
||||||
so the reserved height is right even before the response arrives -->
|
so the reserved height is right even before the response arrives -->
|
||||||
<ChartContainer
|
<ChartContainer
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div in:fade out:fade class="mb-6 min-h-[260px]">
|
<div transition:fade={{ duration: 200 }} class="mb-6 min-h-[260px]">
|
||||||
<!-- negative margin + matching padding: the scroll box gains room so a
|
<!-- negative margin + matching padding: the scroll box gains room so a
|
||||||
lifted/scaled/shadowed card is never clipped, while the first card still
|
lifted/scaled/shadowed card is never clipped, while the first card still
|
||||||
lines up with the page content edge -->
|
lines up with the page content edge -->
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
import { storedVariablePrefs } from '$lib/stores/settings';
|
import { storedVariablePrefs } from '$lib/stores/settings';
|
||||||
|
|
||||||
import { formatUtcOffset, formatZoned, getZonedHour, isSameDayInZone } from '$lib/utils/date';
|
import { formatUtcOffset, formatZoned, getZonedHour, isSameDayInZone } from '$lib/utils/date';
|
||||||
@@ -236,10 +238,11 @@
|
|||||||
{#if cellData.length > 0}
|
{#if cellData.length > 0}
|
||||||
{@const hourly = data.hourly}
|
{@const hourly = data.hourly}
|
||||||
{@const iconPx = is3h ? 38 : 33}
|
{@const iconPx = is3h ? 38 : 33}
|
||||||
<!-- Full-bleed to the viewport edges on mobile (main has p-5 = 1.25rem);
|
<!-- Full-bleed to the viewport edges on mobile (main has p-3 = 0.75rem);
|
||||||
a contained rounded card on md+ -->
|
a contained rounded card on md+ -->
|
||||||
<section
|
<section
|
||||||
class="-mx-5 overflow-hidden border-y border-border/70 bg-card shadow-sm md:mx-0 md:rounded-2xl md:border"
|
transition:fade={{ duration: 200 }}
|
||||||
|
class="-mx-3 overflow-hidden border-y border-border/70 bg-card shadow-sm md:mx-0 md:rounded-2xl md:border"
|
||||||
>
|
>
|
||||||
<!-- Card toolbar -->
|
<!-- Card toolbar -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -153,7 +153,7 @@
|
|||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="mt-8" in:fade={{ duration: 200 }}>
|
<section class="mt-8" transition:fade={{ duration: 200 }}>
|
||||||
<div class="mb-3 flex flex-wrap items-center justify-between gap-2">
|
<div class="mb-3 flex flex-wrap items-center justify-between gap-2">
|
||||||
<h3 class="text-lg font-bold">
|
<h3 class="text-lg font-bold">
|
||||||
Meteograms
|
Meteograms
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<!-- one full-bleed card on mobile / contained card on md+, graphs stacked
|
<!-- one full-bleed card on mobile / contained card on md+, graphs stacked
|
||||||
tightly so they read as one fluent meteogram -->
|
tightly so they read as one fluent meteogram -->
|
||||||
<div class="-mx-5 border-y border-border/70 bg-card shadow-sm md:mx-0 md:rounded-2xl md:border">
|
<div class="-mx-3 border-y border-border/70 bg-card shadow-sm md:mx-0 md:rounded-2xl md:border">
|
||||||
{#each renderPanels as panel, i (panel.id)}
|
{#each renderPanels as panel, i (panel.id)}
|
||||||
<div
|
<div
|
||||||
class="px-0 pt-2 pb-1 md:px-4 {i > 0 ? 'border-t border-border/50' : 'md:pt-4'} {i ===
|
class="px-0 pt-2 pb-1 md:px-4 {i > 0 ? 'border-t border-border/50' : 'md:pt-4'} {i ===
|
||||||
|
|||||||
@@ -201,8 +201,19 @@ export const CHART_VARIABLES: ChartVariableDef[] = [
|
|||||||
color: '#26a69a',
|
color: '#26a69a',
|
||||||
width: 2,
|
width: 2,
|
||||||
fill: true,
|
fill: true,
|
||||||
fillOpacity: 0.15,
|
fillOpacity: 0.15
|
||||||
windArrows: true
|
},
|
||||||
|
{
|
||||||
|
key: 'wind_direction',
|
||||||
|
label: 'Wind direction',
|
||||||
|
short: 'Dir',
|
||||||
|
field: 'winddirection_10m',
|
||||||
|
api: 'wind_direction_10m',
|
||||||
|
type: 'line',
|
||||||
|
kind: 'wind',
|
||||||
|
color: '#14b8a6',
|
||||||
|
windArrows: true,
|
||||||
|
marker: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'wind_gusts',
|
key: 'wind_gusts',
|
||||||
@@ -325,7 +336,7 @@ export function neededHourlyApiVars(
|
|||||||
if (!def) continue;
|
if (!def) continue;
|
||||||
s.add(apiNameOf(def));
|
s.add(apiNameOf(def));
|
||||||
if (def.pictograms) s.add('weather_code');
|
if (def.pictograms) s.add('weather_code');
|
||||||
if (def.key === 'wind') s.add('wind_direction_10m');
|
if (def.windArrows || def.key === 'wind') s.add('wind_direction_10m');
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...s];
|
return [...s];
|
||||||
|
|||||||
Reference in New Issue
Block a user