From cdd701f0b4f24463948823932c568c33a97eee6d Mon Sep 17 00:00:00 2001 From: Alexander Heldt Date: Mon, 13 Jul 2026 21:31:29 +0000 Subject: [PATCH] Bring back the big timer; the bar pill takes over on scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- src/app.js | 55 +++++++++++++++++++++++++++++++++++++++++----- src/changelog.json | 1 + src/index.html | 11 ++++++++-- src/style.css | 31 ++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/src/app.js b/src/app.js index dfeb191..c6f64c0 100644 --- a/src/app.js +++ b/src/app.js @@ -673,29 +673,59 @@ let bigClockState = null; // "asleep" | "awake" | null let bigClockSince = 0; - function renderBigClock(events) { + // 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"); - const icon = document.getElementById("bar-clock-icon"); - const time = document.getElementById("bar-clock-time"); + 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) { + 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 icon = document.getElementById("bar-clock-icon"); + const ptime = document.getElementById("bar-clock-time"); const { state, since: ts } = currentSleepState(events); bigClockState = state; bigClockSince = ts; if (!state) { + card.hidden = true; pill.hidden = true; return; } + card.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("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" ? "😴" : "☀️"; pill.title = `${state === "asleep" ? "Asleep" : "Awake"} since ${formatTime(ts)}`; - time.textContent = formatCounter(Date.now() - ts); + updateBarClockMode(); } function tickBigClock() { if (!bigClockState) return; - const time = document.getElementById("bar-clock-time"); - if (time) time.textContent = formatCounter(Date.now() - bigClockSince); + const counter = 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) { @@ -2309,6 +2339,19 @@ 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.addEventListener("change", render); diff --git a/src/changelog.json b/src/changelog.json index ea72cb9..89dc3c1 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -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": "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" }, diff --git a/src/index.html b/src/index.html index de8d71d..b1abf77 100644 --- a/src/index.html +++ b/src/index.html @@ -78,8 +78,9 @@
- +
+ +

Log event

diff --git a/src/style.css b/src/style.css index 1a40b70..82c6f2e 100644 --- a/src/style.css +++ b/src/style.css @@ -179,9 +179,40 @@ body::before { flex-shrink: 0; } .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.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 { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));