diff --git a/gaps.mjs b/gaps.mjs
new file mode 100644
index 0000000..2240484
--- /dev/null
+++ b/gaps.mjs
@@ -0,0 +1,25 @@
+import { chromium } from 'playwright';
+const browser = await chromium.launch();
+const out = {};
+for (const [label, vp, mobile] of [['mobile', { width: 390, height: 844 }, true], ['desktop', { width: 1280, height: 950 }, false]]) {
+ const ctx = await browser.newContext({ viewport: vp, isMobile: mobile, hasTouch: mobile });
+ const page = await ctx.newPage();
+ await page.goto('http://localhost:5185/en/weather/week/berlin/', { waitUntil: 'load', timeout: 120000 });
+ await page.waitForTimeout(14000);
+ await page.locator('canvas').first().scrollIntoViewIfNeeded();
+ await page.waitForTimeout(1500);
+ out[label] = await page.evaluate(() => {
+ const c = document.querySelector('.strip-cell').getBoundingClientRect();
+ const icon = document.querySelector('.strip-cell .day-icon').getBoundingClientRect();
+ const temps = document.querySelector('.strip-cell .temp-max').getBoundingClientRect();
+ return {
+ cell: { w: Math.round(c.width), h: Math.round(c.height) },
+ iconOffCentre: +(icon.top + icon.height / 2 - (c.top + c.height / 2)).toFixed(1),
+ gapBelowTemps: +(c.bottom - temps.bottom).toFixed(1)
+ };
+ });
+ await page.screenshot({ path: `/tmp/gap-${label}.png`, clip: { x: 0, y: 0, width: vp.width, height: 200 } });
+ await ctx.close();
+}
+console.log(JSON.stringify(out, null, 1));
+await browser.close();
diff --git a/src/lib/charts/CanvasChart.svelte b/src/lib/charts/CanvasChart.svelte
index d746fb3..3ec6a20 100644
--- a/src/lib/charts/CanvasChart.svelte
+++ b/src/lib/charts/CanvasChart.svelte
@@ -388,7 +388,18 @@
// Minimal gutters on narrow screens so the plot uses nearly the full width
// (just enough to keep the axis tick labels legible).
- let isNarrow = $derived(width > 0 && width < 520);
+ // Compact gutters (small axis padding, short icon rows) apply to phones AND
+ // tablets: below `lg` the page is edge-to-edge, so wide desktop-style axis
+ // margins would waste most of the width. Desktop keeps its roomier metrics.
+ let belowDesktop = $state(false);
+ onMount(() => {
+ const mq = window.matchMedia('(max-width: 1023px)');
+ const apply = () => (belowDesktop = mq.matches);
+ apply();
+ mq.addEventListener('change', apply);
+ return () => mq.removeEventListener('change', apply);
+ });
+ let isNarrow = $derived(belowDesktop || (width > 0 && width < 520));
let padLeft = $derived(isNarrow ? 26 : 60);
// Reserve the right gutter when this chart (or a sibling, via reserveRightAxis)
// has a right axis, so a stacked row of charts share the same plot width. On
diff --git a/src/lib/components/navigation/weather-nav.svelte b/src/lib/components/navigation/weather-nav.svelte
index 44bfc3a..d09f374 100644
--- a/src/lib/components/navigation/weather-nav.svelte
+++ b/src/lib/components/navigation/weather-nav.svelte
@@ -1,6 +1,10 @@
-
+
+
{formatZoned(selectedDay, timezone, 'EEEE')}
diff --git a/src/routes/weather/week/[location]/HourlyTable.svelte b/src/routes/weather/week/[location]/HourlyTable.svelte
index a7c385b..abaef82 100644
--- a/src/routes/weather/week/[location]/HourlyTable.svelte
+++ b/src/routes/weather/week/[location]/HourlyTable.svelte
@@ -458,7 +458,9 @@
1h needs far more room than 3h (24 vs 8 columns) -->
@@ -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;
}
}
diff --git a/src/routes/weather/week/[location]/MeteogramCharts.svelte b/src/routes/weather/week/[location]/MeteogramCharts.svelte
index 71df6cd..abeac7e 100644
--- a/src/routes/weather/week/[location]/MeteogramCharts.svelte
+++ b/src/routes/weather/week/[location]/MeteogramCharts.svelte
@@ -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 @@
{panel.titleShort}
-
+
diff --git a/tiles.mjs b/tiles.mjs
new file mode 100644
index 0000000..830b577
--- /dev/null
+++ b/tiles.mjs
@@ -0,0 +1,28 @@
+import { chromium } from 'playwright';
+const browser = await chromium.launch();
+const out = {};
+for (const [label, vp] of [['desktop', { width: 1280, height: 950 }], ['tablet', { width: 900, height: 800 }]]) {
+ const ctx = await browser.newContext({ viewport: vp });
+ const page = await ctx.newPage();
+ await page.goto('http://localhost:5185/en/weather/week/berlin/', { waitUntil: 'load', timeout: 120000 });
+ await page.waitForTimeout(14000);
+ await page.locator('canvas').first().scrollIntoViewIfNeeded();
+ await page.waitForTimeout(1500);
+ out[label] = await page.evaluate(() => {
+ const c = document.querySelector('.strip-cell').getBoundingClientRect();
+ const icon = document.querySelector('.strip-cell .day-icon').getBoundingClientRect();
+ const temps = document.querySelector('.strip-cell .temp-max').getBoundingClientRect();
+ const canvas = document.querySelector('canvas');
+ const cw = canvas ? canvas.getBoundingClientRect() : null;
+ return {
+ cell: { w: Math.round(c.width), h: Math.round(c.height) },
+ iconOffCentre: +(icon.top + icon.height / 2 - (c.top + c.height / 2)).toFixed(1),
+ gapBelowTemps: +(c.bottom - temps.bottom).toFixed(1),
+ canvasWidth: cw ? Math.round(cw.width) : null
+ };
+ });
+ await page.screenshot({ path: `/tmp/tiles-${label}.png`, clip: { x: 0, y: 0, width: vp.width, height: 220 } });
+ await ctx.close();
+}
+console.log(JSON.stringify(out, null, 1));
+await browser.close();