Bring back the big timer; the bar pill takes over on scroll

The big asleep/awake card returns below the frozen bar, and the bar
pill becomes its twin: both tick together, but the pill stays
visibility-hidden — slot reserved, so the bar never shifts — while the
card is on screen, appearing only once the card scrolls out of sight
(rAF-throttled scroll check against the bar's bottom edge).
This commit is contained in:
Alexander Heldt
2026-07-13 21:31:29 +00:00
parent 8981214e55
commit cdd701f0b4
4 changed files with 90 additions and 8 deletions
+47 -4
View File
@@ -673,29 +673,59 @@
let bigClockState = null; // "asleep" | "awake" | null let bigClockState = null; // "asleep" | "awake" | null
let bigClockSince = 0; let bigClockSince = 0;
// The counter renders twice: the big card at the top of the page and the
// compact pill in the frozen day bar. The pill only *shows* once the big
// card is scrolled out of sight (see updateBarClockMode); while the card is
// visible the pill is invisible but keeps its slot so the bar never shifts.
function updateBarClockMode() {
const pill = document.getElementById("bar-clock");
if (!bigClockState || pill.hidden) return;
const card = document.getElementById("big-clock");
const bar = document.querySelector(".day-bar");
const cardVisible =
card.getBoundingClientRect().bottom > bar.getBoundingClientRect().bottom;
pill.classList.toggle("standby", cardVisible);
}
function renderBigClock(events) { function renderBigClock(events) {
const card = document.getElementById("big-clock");
const label = document.getElementById("bc-label");
const time = document.getElementById("bc-time");
const since = document.getElementById("bc-since");
const pill = document.getElementById("bar-clock"); const pill = document.getElementById("bar-clock");
const icon = document.getElementById("bar-clock-icon"); const icon = document.getElementById("bar-clock-icon");
const time = document.getElementById("bar-clock-time"); const ptime = document.getElementById("bar-clock-time");
const { state, since: ts } = currentSleepState(events); const { state, since: ts } = currentSleepState(events);
bigClockState = state; bigClockState = state;
bigClockSince = ts; bigClockSince = ts;
if (!state) { if (!state) {
card.hidden = true;
pill.hidden = true; pill.hidden = true;
return; return;
} }
card.hidden = false;
pill.hidden = false; pill.hidden = false;
card.classList.toggle("asleep", state === "asleep");
card.classList.toggle("awake", state === "awake");
pill.classList.toggle("asleep", state === "asleep"); pill.classList.toggle("asleep", state === "asleep");
pill.classList.toggle("awake", state === "awake"); pill.classList.toggle("awake", state === "awake");
label.textContent = state === "asleep" ? "Asleep for" : "Awake for";
const counter = formatCounter(Date.now() - ts);
time.textContent = counter;
ptime.textContent = counter;
since.textContent = `since ${formatTime(ts)}`;
icon.textContent = state === "asleep" ? "😴" : "☀️"; icon.textContent = state === "asleep" ? "😴" : "☀️";
pill.title = `${state === "asleep" ? "Asleep" : "Awake"} since ${formatTime(ts)}`; pill.title = `${state === "asleep" ? "Asleep" : "Awake"} since ${formatTime(ts)}`;
time.textContent = formatCounter(Date.now() - ts); updateBarClockMode();
} }
function tickBigClock() { function tickBigClock() {
if (!bigClockState) return; if (!bigClockState) return;
const time = document.getElementById("bar-clock-time"); const counter = formatCounter(Date.now() - bigClockSince);
if (time) time.textContent = formatCounter(Date.now() - bigClockSince); const time = document.getElementById("bc-time");
const ptime = document.getElementById("bar-clock-time");
if (time) time.textContent = counter;
if (ptime) ptime.textContent = counter;
} }
function renderWindowList(listId, emptyId, windows, ongoingLabel, extraClass) { function renderWindowList(listId, emptyId, windows, ongoingLabel, extraClass) {
@@ -2309,6 +2339,19 @@
b.addEventListener("click", () => setChartDays(Number(b.dataset.days))); b.addEventListener("click", () => setChartDays(Number(b.dataset.days)));
}); });
// Swap the timer pill in/out of the frozen bar as the big card scrolls
// past. rAF-throttled: scroll events fire far more often than we can paint.
{
let queued = false;
const onScroll = () => {
if (queued) return;
queued = true;
requestAnimationFrame(() => { queued = false; updateBarClockMode(); });
};
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll, { passive: true });
}
dayPicker.value = ymd(new Date()); dayPicker.value = ymd(new Date());
dayPicker.addEventListener("change", render); dayPicker.addEventListener("change", render);
+1
View File
@@ -1,4 +1,5 @@
[ [
{ "date": "2026-07-13", "text": "The big timer is back at the top — scroll past it and it hops into the frozen bar instead" },
{ "date": "2026-07-13", "text": "The top bar fits on one row: timer, day arrows, date picker and Today" }, { "date": "2026-07-13", "text": "The top bar fits on one row: timer, day arrows, date picker and Today" },
{ "date": "2026-07-13", "text": "Pick how many days the charts cover — 7, 14 or 30 (default 7)" }, { "date": "2026-07-13", "text": "Pick how many days the charts cover — 7, 14 or 30 (default 7)" },
{ "date": "2026-07-13", "text": "Much finer y-axis on the food chart" }, { "date": "2026-07-13", "text": "Much finer y-axis on the food chart" },
+9 -2
View File
@@ -78,8 +78,9 @@
<main> <main>
<section class="day-bar"> <section class="day-bar">
<!-- Compact asleep/awake counter (the old big clock); hidden until a <!-- Compact twin of the big timer below: invisible (but keeping its
sleep event exists. Hover/long-press shows "since" via title. --> slot) while the big card is on screen, shown once it scrolls
away. Hidden entirely until a sleep event exists. -->
<div id="bar-clock" class="bar-clock" hidden> <div id="bar-clock" class="bar-clock" hidden>
<span id="bar-clock-icon" aria-hidden="true"></span> <span id="bar-clock-icon" aria-hidden="true"></span>
<span id="bar-clock-time"></span> <span id="bar-clock-time"></span>
@@ -90,6 +91,12 @@
<button type="button" id="day-today" class="ghost">Today</button> <button type="button" id="day-today" class="ghost">Today</button>
</section> </section>
<section id="big-clock" class="big-clock" hidden>
<div class="bc-label" id="bc-label"></div>
<div class="bc-time" id="bc-time">0:00</div>
<div class="bc-since" id="bc-since"></div>
</section>
<section class="quick-actions"> <section class="quick-actions">
<h2>Log event</h2> <h2>Log event</h2>
<div class="grid"> <div class="grid">
+31
View File
@@ -179,9 +179,40 @@ body::before {
flex-shrink: 0; flex-shrink: 0;
} }
.bar-clock[hidden] { display: none; } .bar-clock[hidden] { display: none; }
/* Big timer still on screen: keep the pill's slot but show nothing. */
.bar-clock.standby { visibility: hidden; }
.bar-clock.asleep { background: color-mix(in srgb, var(--sleep) 18%, var(--surface)); color: var(--sleep); } .bar-clock.asleep { background: color-mix(in srgb, var(--sleep) 18%, var(--surface)); color: var(--sleep); }
.bar-clock.awake { background: color-mix(in srgb, var(--accent) 18%, var(--surface)); color: var(--accent); } .bar-clock.awake { background: color-mix(in srgb, var(--accent) 18%, var(--surface)); color: var(--accent); }
.big-clock {
text-align: center;
padding: 24px 16px;
}
.big-clock .bc-label {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
margin-bottom: 6px;
}
.big-clock .bc-time {
font-size: 3.25rem;
font-weight: 700;
font-variant-numeric: tabular-nums;
line-height: 1.05;
letter-spacing: -0.01em;
}
.big-clock .bc-since {
margin-top: 6px;
font-size: 0.8rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.big-clock.asleep { background: linear-gradient(180deg, var(--surface), color-mix(in srgb, var(--sleep) 10%, var(--surface))); }
.big-clock.asleep .bc-time { color: var(--sleep); }
.big-clock.awake { background: linear-gradient(180deg, var(--surface), color-mix(in srgb, var(--accent) 10%, var(--surface))); }
.big-clock.awake .bc-time { color: var(--accent); }
.grid { .grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));