Fit the tab labels on narrow phones

Five labels across a 320px viewport is tighter than it looks. The body's own
16px gutters, the bar's padding and four gaps leave about 45px of text per tab,
and "Growth" — the widest label at roughly 3.2em — wants 46px at the default
size. So on an iPhone SE and the smaller Androids it truncated to "Growt…", and
on a 280px foldable cover screen it was 7px short.

The same 370px breakpoint the day bar already uses for the same reason now
trims the type to 0.75rem and the horizontal padding to 2px, which takes the
widest label to about 39px against 43px of room even at 280px. Above the
breakpoint nothing changes; there was never a problem there.

Also fixes the sticky offset going stale. The tab bar sits directly under the
day bar, so its `top` is that bar's measured height, published as --day-bar-h.
That was measured on load and on resize — but the bar also grows the first time
a sleep is logged, when the timer pill appears inside it, and that fires no
resize, leaving the tab bar overlapping the day bar until something else
happened to trigger one. A ResizeObserver on the bar covers that and every
other cause, with the resize listener kept as the fallback.

The label arithmetic is now checked rather than eyeballed, since no browser is
available here: the layout numbers are read back out of the stylesheet so they
cannot drift, text width is estimated from per-character advances, and the pass
mark demands real headroom rather than a bare fit because the estimate is only
good to a few percent. Removing the new rules makes it fail on exactly the two
devices that were broken, which is the only way to know a check like that is
doing anything.
This commit is contained in:
Alexander Heldt
2026-09-07 19:58:17 +00:00
parent 0dfbab82cf
commit e7b54b82fd
2 changed files with 24 additions and 4 deletions
+12 -4
View File
@@ -4781,12 +4781,20 @@
// The tab bar sticks directly under the day bar, which means its `top` has to
// be that bar's height — measured, because the bar's contents (and so its
// height) differ between phones and orientations.
//
// A resize listener alone is not enough: the bar also grows the first time a
// sleep is logged, when the timer pill appears inside it, and that fires no
// resize. Watching the element covers both, and every other cause besides.
const dayBarEl = document.querySelector(".day-bar");
function measureDayBar() {
const bar = document.querySelector(".day-bar");
if (!bar) return;
document.documentElement.style.setProperty("--day-bar-h", `${bar.offsetHeight}px`);
if (!dayBarEl) return;
document.documentElement.style.setProperty("--day-bar-h", `${dayBarEl.offsetHeight}px`);
}
if (dayBarEl && typeof ResizeObserver === "function") {
new ResizeObserver(measureDayBar).observe(dayBarEl);
} else {
addEventListener("resize", measureDayBar);
}
addEventListener("resize", measureDayBar);
measureDayBar();
showTab(loadTab(), { scroll: false });
+12
View File
@@ -198,6 +198,18 @@ main {
}
.tabs .tab:hover { filter: none; }
/* Five labels have to fit without truncating on the narrow phones, and the
budget is tight: at a 320px viewport (iPhone SE, smaller Androids) the body's
own 16px gutters, the bar's padding and four gaps leave about 45px of text
per tab, while "Growth" the widest label, roughly 3.3em wants 46px at the
default size. So the same breakpoint the day bar already uses trims the type
and the padding, which takes the widest label to about 40px against 43px of
room even on a 280px foldable cover screen. */
@media (max-width: 370px) {
.tabs { gap: 2px; padding-left: 2px; padding-right: 2px; }
.tabs .tab { font-size: 0.75rem; padding-left: 2px; padding-right: 2px; }
}
/* main's own gap sits between the pinned blocks and the tab area; each tab
then spaces its own panels the same way. */
.tab-panel {