crossfade
This commit is contained in:
@@ -4,9 +4,8 @@
|
||||
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import { storedEnsembleModel, storedLocation, storedUnits } from '$lib/stores/settings';
|
||||
|
||||
import { reportPageReady } from '$lib/stores/page-transition.svelte';
|
||||
import { storedEnsembleModel, storedLocation, storedUnits } from '$lib/stores/settings';
|
||||
|
||||
import { syncSearchParams } from '$lib/utils/url-state';
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import { storedLocation, storedModel, storedUnits } from '$lib/stores/settings';
|
||||
|
||||
import { reportPageReady } from '$lib/stores/page-transition.svelte';
|
||||
import { storedLocation, storedModel, storedUnits } from '$lib/stores/settings';
|
||||
|
||||
import { formatZoned } from '$lib/utils/date';
|
||||
import { readList, syncSearchParams } from '$lib/utils/url-state';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
import { reportPageReady } from '$lib/stores/page-transition.svelte';
|
||||
import {
|
||||
storedChartLayout,
|
||||
storedLocation,
|
||||
@@ -10,8 +11,6 @@
|
||||
storedVariablePrefs
|
||||
} from '$lib/stores/settings';
|
||||
|
||||
import { reportPageReady } from '$lib/stores/page-transition.svelte';
|
||||
|
||||
import { ChartContainer } from '$lib/components/charts';
|
||||
|
||||
import PaywallGate from '$lib/paywall/PaywallGate.svelte';
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
import { storedLocation, storedUnits } from '$lib/stores/settings';
|
||||
|
||||
import { reportPageReady } from '$lib/stores/page-transition.svelte';
|
||||
import { storedLocation, storedUnits } from '$lib/stores/settings';
|
||||
|
||||
import { ChartContainer, ChartToolbar } from '$lib/components/charts';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import { reportPageReady } from '$lib/stores/page-transition.svelte';
|
||||
import {
|
||||
storedChartLayout,
|
||||
storedLocation,
|
||||
@@ -14,8 +15,6 @@
|
||||
storedVariablePrefs
|
||||
} from '$lib/stores/settings';
|
||||
|
||||
import { reportPageReady } from '$lib/stores/page-transition.svelte';
|
||||
|
||||
import { formatZoned } from '$lib/utils/date';
|
||||
import { daySwap, runDayTransition } from '$lib/utils/day-swap';
|
||||
import { buildLocationRoute } from '$lib/utils/location';
|
||||
@@ -66,6 +65,19 @@
|
||||
|
||||
let variableSidebarOpen = $state(false);
|
||||
|
||||
// Meteogram canvases are shorter on phones, where a 300px plot eats most of
|
||||
// the viewport. The placeholder below uses the same number, so the reserved
|
||||
// space still matches exactly.
|
||||
let narrowViewport = $state(false);
|
||||
onMount(() => {
|
||||
const mq = window.matchMedia('(max-width: 767px)');
|
||||
const apply = () => (narrowViewport = mq.matches);
|
||||
apply();
|
||||
mq.addEventListener('change', apply);
|
||||
return () => mq.removeEventListener('change', apply);
|
||||
});
|
||||
let chartHeight = $derived(narrowViewport ? 215 : 300);
|
||||
|
||||
// Number of meteogram panels: reserves the chart area height before data
|
||||
// arrives (no layout shift)
|
||||
let enabledChartCount = $derived($storedChartLayout.filter((p) => p.variables.length > 0).length);
|
||||
@@ -418,7 +430,13 @@
|
||||
|
||||
{#if fetchedHourly}
|
||||
<div class="day-region-charts" use:daySwap={selectedDayKey}>
|
||||
<MeteogramCharts data={fetchedHourly} {selectedDay} units={params} {loading} />
|
||||
<MeteogramCharts
|
||||
data={fetchedHourly}
|
||||
{selectedDay}
|
||||
units={params}
|
||||
{loading}
|
||||
{chartHeight}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- reserve the exact chart area height before the first fetch resolves,
|
||||
@@ -431,7 +449,7 @@
|
||||
<div class="h-7 w-24 animate-pulse rounded-lg bg-muted"></div>
|
||||
</div>
|
||||
</div>
|
||||
<ChartContainer loading chartCount={enabledChartCount || 1} chartHeight={300} />
|
||||
<ChartContainer loading chartCount={enabledChartCount || 1} {chartHeight} />
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -443,7 +443,7 @@
|
||||
--icon-full: 44px;
|
||||
/* the compact icon carries the square tile: sized so the content fills it
|
||||
and the leftover slack under the temps stays small */
|
||||
--icon-min: 27px;
|
||||
--icon-min: 31px;
|
||||
--pt-full: 7px;
|
||||
--pt-min: 2px;
|
||||
--gap-full: 8px;
|
||||
@@ -518,7 +518,7 @@
|
||||
--pb-full: 10px;
|
||||
--pb-min: 3px;
|
||||
--icon-lift: 0px;
|
||||
--collapse-extra: 60px;
|
||||
--collapse-extra: 0px;
|
||||
}
|
||||
.daystrip {
|
||||
/* breathing room between the full cards and the table */
|
||||
@@ -526,6 +526,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop only: the docked tiles get roughly 1.75x bigger, with the icon and
|
||||
type scaled to match so they stay in proportion. Phones and tablets keep
|
||||
the compact bar - there the screen width is the scarce resource. */
|
||||
@media (min-width: 1024px) {
|
||||
.sentinel,
|
||||
.daystrip {
|
||||
--cell-h-min: 86px;
|
||||
--icon-min: 47px;
|
||||
--dow-font-min: 11px;
|
||||
--tmax-font-min: 14px;
|
||||
--tmin-font-min: 12px;
|
||||
--tmax-padx-min: 6px;
|
||||
--pt-min: 5px;
|
||||
--pb-min: 6px;
|
||||
--icon-lift: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Invisible collapse band: the scroll distance over which the collapse
|
||||
plays. The negative margin removes it from layout so nothing shifts. */
|
||||
.sentinel {
|
||||
|
||||
@@ -99,7 +99,10 @@
|
||||
</script>
|
||||
|
||||
<section class="mt-6" aria-label={m.summary_heading()}>
|
||||
<div class="overflow-hidden rounded-2xl border border-border/70 bg-card shadow-sm">
|
||||
<!-- flush with the screen edges on phones, a contained card from md up -->
|
||||
<div
|
||||
class="-mx-3 overflow-hidden border-y border-border/70 bg-card shadow-sm md:mx-0 md:rounded-2xl md:border"
|
||||
>
|
||||
<div class="border-b border-border/70 bg-muted/40 px-4 py-2.5">
|
||||
<h3 class="text-base font-bold">
|
||||
{formatZoned(selectedDay, timezone, 'EEEE')}
|
||||
|
||||
@@ -458,7 +458,9 @@
|
||||
1h needs far more room than 3h (24 vs 8 columns) -->
|
||||
<div class="overflow-x-auto" bind:this={tableScrollEl} onscroll={onTableScroll}>
|
||||
<div
|
||||
class="relative {is3h ? 'min-w-[560px]' : 'min-w-[1100px]'}"
|
||||
class="relative {is3h
|
||||
? 'min-w-[400px] md:min-w-[560px]'
|
||||
: 'min-w-[780px] md:min-w-[1100px]'}"
|
||||
bind:clientWidth={tableWidth}
|
||||
>
|
||||
<table class="w-full table-fixed border-collapse whitespace-nowrap">
|
||||
@@ -885,13 +887,34 @@
|
||||
}
|
||||
|
||||
/* ── Responsive ─────────────────────────────────────────────── */
|
||||
/* Phones: the table is the tallest thing on the page, so every cell is taken
|
||||
down to roughly 0.7 of its desktop size - padding, type and the icons that
|
||||
actually set the row height. Desktop keeps its original metrics. */
|
||||
@media (max-width: 768px) {
|
||||
.hdr {
|
||||
padding: 3px 2px;
|
||||
font-size: 10px;
|
||||
padding: 2px 1px;
|
||||
font-size: 9px;
|
||||
}
|
||||
.cell {
|
||||
font-size: 11px;
|
||||
padding: 1px;
|
||||
font-size: 10px;
|
||||
}
|
||||
/* The row-header icon and the fixed cell height are what actually set the
|
||||
row height, so both come down explicitly. */
|
||||
.cell :global(svg),
|
||||
.hdr :global(svg) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.cell {
|
||||
height: 34px;
|
||||
}
|
||||
.hdr :global(span) {
|
||||
font-size: 9px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
.precip-bar {
|
||||
min-height: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,14 +25,15 @@
|
||||
selectedDay: Date;
|
||||
units: WeatherUnits;
|
||||
loading: boolean;
|
||||
/** Canvas height in px; the page shrinks it on phones. */
|
||||
chartHeight?: number;
|
||||
onResetZoom?: () => void;
|
||||
}
|
||||
|
||||
let { data, selectedDay, units, loading, onResetZoom }: Props = $props();
|
||||
let { data, selectedDay, units, loading, chartHeight = 300, onResetZoom }: Props = $props();
|
||||
|
||||
const CHART_GROUP = 'week-meteogram';
|
||||
const SECONDS_PER_DAY = 24 * 3600;
|
||||
const CHART_HEIGHT = 300;
|
||||
|
||||
let customizerOpen = $state(false);
|
||||
let downloadingPng = $state(false);
|
||||
@@ -351,13 +352,7 @@
|
||||
<span class="lg:hidden">{panel.titleShort}</span>
|
||||
</h4>
|
||||
</div>
|
||||
<ChartContainer
|
||||
{loading}
|
||||
chartCount={1}
|
||||
chartHeight={CHART_HEIGHT}
|
||||
minWidth={520}
|
||||
bleed={false}
|
||||
>
|
||||
<ChartContainer {loading} chartCount={1} {chartHeight} minWidth={520} bleed={false}>
|
||||
<CanvasChart
|
||||
bind:this={chartComponents[i]}
|
||||
timestamps={timestampsSec}
|
||||
@@ -379,7 +374,7 @@
|
||||
yMaxRight={panel.def.yMaxRight}
|
||||
showCredit={i === renderPanels.length - 1}
|
||||
showLegend
|
||||
height={CHART_HEIGHT}
|
||||
height={chartHeight}
|
||||
group={CHART_GROUP}
|
||||
/>
|
||||
</ChartContainer>
|
||||
|
||||
Reference in New Issue
Block a user