Join the tab row to the frozen date row

The tab row was painted in the page ground while the day bar above it is a
card, and the two sat as separate bars with the day bar's rounded corners
cutting between them — so the frozen top of the screen read as two mismatched
strips rather than one thing.

It takes the same card now: surface colour, radius and shadow. Once the log
buttons have scrolled away and the two bars meet, they lose the seam and share
one frame — the day bar squares its bottom corners and gives up its shadow, the
tab row squares its top, and the pair casts a single shadow below. Scrolled back
to the top they are two cards again, which is right: the log buttons genuinely
sit between them there.

CSS has no way to ask whether a sticky element is currently stuck, so a class is
toggled from JS on the simplest available test — whether the two are touching.
It rides the rAF-throttled scroll handler that already existed for the timer
pill, so it adds no listener, and it is re-checked on a tab switch (a shorter
tab can leave the page too short to stay scrolled) and when the day bar changes
height.

The tab row's sticky offset is now a pixel less than the day bar's height. The
safe-area inset is free to be fractional while the measured height is a whole
number, and that shortfall would show as a hairline of page ground between the
two; the overlap it costs is invisible, since the day bar paints on top in the
same colour.
This commit is contained in:
Alexander Heldt
2026-09-07 20:14:24 +00:00
parent de1e18e394
commit 39343ff2a4
3 changed files with 54 additions and 8 deletions
+25 -2
View File
@@ -170,13 +170,36 @@ main {
fallback keeps the bar usable for the frame before that lands. */
.tabs {
position: sticky;
top: calc(env(safe-area-inset-top, 0px) + var(--day-bar-h, 52px));
/* One pixel under the day bar's height rather than exactly it: the inset is
free to be fractional while the measured height is a whole number, and a
sub-pixel shortfall would show as a hairline of page ground between the
two. The overlap is invisible — the day bar paints on top, same colour. */
top: calc(env(safe-area-inset-top, 0px) + var(--day-bar-h, 52px) - 1px);
z-index: 55; /* under the day bar, over the panels */
display: flex;
gap: 4px;
padding: 6px 4px;
margin: -8px 0 0;
background: var(--bg);
/* Same card as the day bar rather than the page ground, so the two read as
one thing once they are frozen together. */
background: var(--surface);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
/* At rest the two are separate cards with the log buttons between them. Scroll
the log buttons away and they meet, so the seam goes: the day bar squares its
bottom and gives up its shadow, the tab bar squares its top, and the pair
sits inside one rounded frame casting one shadow. JS adds .merged the moment
they touch — CSS has no way to ask whether a sticky element is stuck. */
.day-bar.merged {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
box-shadow: none;
}
.tabs.merged {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.tabs .tab {
flex: 1;