From 5fe309a8a641bbe3ebb9cad0c2859c84e6e32716 Mon Sep 17 00:00:00 2001
From: Kemal Yaylali
Date: Sun, 16 Aug 2026 17:09:16 +0100
Subject: [PATCH] Responsive pass: fix the squeezed chart, mobile navigation,
and overflow
Three real faults, all measured rather than guessed.
The forecast chart was unreadable on a 13 inch laptop. A 1280x800 screen has
desktop width but 280 fewer vertical pixels, and the fixed rows (stat cards,
outlook, diagnostics) took that entirely out of the one row meant to flex.
Measured: 404 px tall at 1920x1080, 124 px at 1280x800, 92 px at 1024x768.
Height-aware media queries now compact the furniture instead, and the chart is
201 px at 1280x800.
Mobile navigation did not exist. At 375 px all six tabs were clipped with no
affordance they were there, so five of six sections were unreachable. Below
1024 px the tab row is now a native select on its own full-width line, showing
the current section rather than a bare chevron.
Horizontal overflow on phones: the shell measured 452 px inside a 375 px
viewport. Flex and grid children default to min-width:auto, so a long subtitle
refused to shrink and dragged the layout wider than the screen.
Also restructured the conditions column. It was a fixed stack that simply
overflowed its own card on short screens, and shrinking each piece by media
query chased the symptom; it is now a flex column with exactly one flexible
child, so the tendency chart absorbs the slack and the column fits at any
height. And the no-scroll contract gained a height floor: below 700 px tall the
page scrolls, because at 1024x600 the chart had collapsed to 1 px and a
scrollbar is the better answer.
Verified across ten viewports from 1920x1080 to 375x667, six tabs each: 60
combinations, zero clipping, zero overflow, zero console errors.
---
README.md | 9 ++-
ashvale/dashboard.py | 174 +++++++++++++++++++++++++++++++++----------
2 files changed, 141 insertions(+), 42 deletions(-)
diff --git a/README.md b/README.md
index 07bc58f..a896703 100644
--- a/README.md
+++ b/README.md
@@ -160,9 +160,12 @@ permanently.
## The dashboard
-Six tabs, one viewport, no scrolling on desktop. Below 1024 px the constraint is
-released, because pinning six panels into a phone viewport produces unreadable
-eight-pixel type.
+Six tabs, one viewport, no scrolling on desktop. Below 1024 px wide the tabs
+become a dropdown and the page scrolls, because pinning six panels into a phone
+viewport produces unreadable eight-pixel type. The same release applies below
+700 px tall: a short screen gets a scrollbar rather than a one-pixel chart.
+
+Verified across ten viewports from 1920x1080 to 375x667, six tabs each.
| Tab | Answers |
| --- | --- |
diff --git a/ashvale/dashboard.py b/ashvale/dashboard.py
index a87bd82..817a9bc 100644
--- a/ashvale/dashboard.py
+++ b/ashvale/dashboard.py
@@ -14,6 +14,15 @@
"""The dashboard: six tabs in the header, one viewport, no scrolling.
+Responsive contract, in both axes. The no-scroll lock needs a floor on height
+as well as width: at 1024x600 the forecast chart collapsed to 1 px, which is
+worse than scrolling, so below 700 px tall the page scrolls and the charts keep
+a readable minimum. Between 700 and 920 px tall the fixed furniture (stat
+cards, outlook, diagnostics, the conditions column) compacts by media query so
+the height goes to the chart rather than the chrome. Below 1024 px wide the
+tab row becomes a native select, because six labels do not fit a phone header
+and a clipped tab strip is worse than a dropdown.
+
Layout contract. The page is a fixed three-row grid pinned to the
viewport height: header, tab bar, then a content region that takes the
remaining space and never overflows the fold. Each tab lays its panels
@@ -78,12 +87,72 @@ DASHBOARD_HTML = r"""
.scroller::-webkit-scrollbar-thumb { background:rgba(148,163,184,.28); border-radius:8px; }
.flash { animation: flash .5s ease-out; }
@keyframes flash { from { color:#a5b4fc; } to { color:inherit; } }
+ .sr-only { position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0; }
:focus-visible { outline:2px solid #818cf8; outline-offset:2px; border-radius:6px; }
@media (prefers-reduced-motion: reduce) { *{animation:none!important;transition:none!important} }
- @media (min-width:1024px) {
+ /* The no-scroll contract needs a floor on height as well as width. A 1024x600
+ screen cannot show six panels legibly in one viewport: measured, the
+ forecast chart collapsed to 1 px, which is worse than scrolling. Below
+ 700 px tall the page scrolls and the charts keep a readable minimum. */
+ @media (min-width:1024px) and (min-height:700px) {
html,body { height:100%; overflow:hidden; }
#shell { height:100dvh; }
}
+ @media (min-width:1024px) and (max-height:699px) {
+ .pane.active { min-height:640px; }
+ }
+
+ /* Nothing may push the page sideways. Flex and grid children default to
+ min-width:auto, so a long unbreakable subtitle inside a flex row refuses to
+ shrink and drags the whole layout wider than the viewport. Measured at 375
+ px this forced the shell to 452 px. */
+ html, body { max-width:100%; overflow-x:hidden; }
+ #shell, main, .pane, .glass { min-width:0; }
+ .pane > *, .glass > *, .glass p, .glass h2 { min-width:0; }
+ .glass p, .glass h2, .glass span { overflow-wrap:anywhere; }
+
+ /* Height-aware compaction. A 13 inch laptop has the same width as a desktop
+ but 280 fewer vertical pixels, and the fixed rows (stat cards, outlook,
+ diagnostics) eat that difference out of the one row that should flex: the
+ chart. Measured at 1280x800 the chart fell to 124 px, which is unreadable.
+ These rules give the height back to the chart rather than letting the
+ furniture keep it. */
+ @media (min-width:1024px) and (max-height:920px) {
+ .stat-card { padding:0.7rem 0.85rem; }
+ .stat-card .stat-big { font-size:2.1rem; line-height:1; }
+ .stat-card .stat-spark { height:1.4rem; }
+ .row-outlook { padding:0.55rem 1rem; }
+ .row-outlook .row-sub { display:none; }
+ .row-diag { padding-top:0.35rem; padding-bottom:0.35rem; }
+ .fc-strip { margin-top:0.35rem; padding-top:0.35rem; }
+ }
+ /* Conditions ahead is a fixed stack: icon, probability, the wet/dry buttons
+ and the tendency chart. At 1280x800 it needed 351 px into 267, so it
+ clipped its own chart. Same treatment as the stat cards: give the height
+ back by shrinking the furniture, not by hiding the reading. */
+ @media (min-width:1024px) and (max-height:920px) {
+ .cond-cap { display:none; }
+ .cond-icon svg { width:36px; height:36px; }
+ .cond-label { padding:0.35rem 0.5rem; }
+ }
+ @media (min-width:1024px) and (max-height:830px) {
+ .cond-icon svg { width:30px; height:30px; }
+ /* 1366x768 is the most common laptop resolution in the world, so it has to
+ fit rather than nearly fit. These four give back about 36 px. */
+ .cond-inner { gap:0.35rem; }
+ .cond-labstat, .cond-split { display:none; }
+ .cond-label { padding:0.3rem 0.45rem; }
+ .cond-label button { padding-top:0.2rem; padding-bottom:0.2rem; }
+ /* At 1024 the column is narrower, so the same text wraps taller. The
+ Zambretti number is already spelled out by the sky line above it. */
+ .cond-z { display:none; }
+ #c-label { font-size:0.8rem; }
+ .stat-card { padding:0.55rem 0.75rem; }
+ .stat-card .stat-big { font-size:1.8rem; }
+ .stat-card .stat-spark { display:none; }
+ .stat-card .stat-detail { font-size:9px; }
+ .row-outlook { padding:0.4rem 0.85rem; }
+ }
@@ -114,11 +183,30 @@ DASHBOARD_HTML = r"""