Replace the date picker with a month grid of our own
The day bar had run out of room: two timers and four day controls came to more than the bar's width on every phone, so it wrapped onto two rows whenever a walk was running. Shaving pixels off both groups was not enough — even the most compact form of it only fitted a 428px iPhone Plus. The way out was structural. The browser's date picker is a sheet that covers the screen, which is backwards here: the reason to change day is to see what the figures did on it, and a modal hides exactly the thing you opened it for. So the date button now opens a small panel under the bar instead — month name with arrows, locale-ordered weekday initials, six rows of days — with the overview still on screen and updating as you move through it. That also solved the width, because Today belongs inside the grid rather than beside it. The day controls drop from about 204px to 111px, which is enough for both timers on one row from 320px up; only a 280px foldable cover still wraps. The bar keeps its wrapping for that case and for large system font sizes. The hidden input stays as the value everything reads — selectedDay() and every caller are untouched, and only the input's own picker is no longer opened. Six rows always, so the panel does not change height from month to month. Future days are disabled, matching the bar's → being disabled on today. The week starts where Intl says it does for the reader's locale, falling back to Monday. Arrow keys walk the grid and pull the neighbouring month into view at the edges. The grid arithmetic is checked rather than eyeballed, both week starts across every month of a year, leap years and year boundaries — including February 2026, which begins on a Sunday and so needs six leading days from January under Monday weeks. That is the case a naive `1 - getDay()` gets wrong, and it would have been invisible until somebody happened to open that month.
This commit is contained in:
@@ -53,14 +53,20 @@ of them, because logging has to be one tap from wherever you are.
|
|||||||
|
|
||||||
- The day bar carries up to two timers, each of which logs the boundary that
|
- The day bar carries up to two timers, each of which logs the boundary that
|
||||||
ends what it is counting when tapped: the asleep/awake one, and — only while
|
ends what it is counting when tapped: the asleep/awake one, and — only while
|
||||||
a walk is running — the walk. Two of them no longer fit beside the day
|
a walk is running — the walk. They count in minutes; the big card on Today
|
||||||
controls on a narrow phone, so the bar wraps; the timers and the controls are
|
keeps the seconds, and shows whichever of the two is the more immediate — the
|
||||||
each a group, so it wraps between them rather than stranding "Today" on a
|
walk, when there is one, since you are necessarily awake on a walk.
|
||||||
line of its own. The bar's height changes when it does and the tab bar sticks
|
- **The day picker is a month grid of the app's own**, not the browser's. The
|
||||||
to that height, which is why `--day-bar-h` is kept current by a
|
native one is a sheet covering the screen, and the reason to change day is to
|
||||||
`ResizeObserver` rather than measured once. The big card on Today shows
|
see what the figures did on it — so this is a small panel under the bar, with
|
||||||
whichever of the two is the more immediate — the walk, when there is one,
|
the overview still visible and updating as you move. `←` and `→` stay in the
|
||||||
since you are necessarily awake on a walk.
|
bar for the common ±1 day; the grid handles jumps and carries *Today*, which
|
||||||
|
is what freed the width to fit two timers on one row. The hidden
|
||||||
|
`<input type="date">` remains the value everything reads; only its own picker
|
||||||
|
is no longer opened.
|
||||||
|
- The bar can still wrap, and does below about 300px. Its height changes when
|
||||||
|
it does and the tab bar sticks to that height, which is why `--day-bar-h` is
|
||||||
|
kept current by a `ResizeObserver` rather than measured once.
|
||||||
- Each tab is a `.tab-panel` wrapper around the existing sections. The
|
- Each tab is a `.tab-panel` wrapper around the existing sections. The
|
||||||
**wrapper** is what gets hidden, never the sections: `walk-timeline` and
|
**wrapper** is what gets hidden, never the sections: `walk-timeline` and
|
||||||
`walk-trend` carry their own `hidden`, set by `renderWalkPatterns` once a walk
|
`walk-trend` carry their own `hidden`, set by `renderWalkPatterns` once a walk
|
||||||
|
|||||||
+146
-16
@@ -629,6 +629,18 @@
|
|||||||
return { walking: latest.type === "walk-start", since: latest.at };
|
return { walking: latest.type === "walk-start", since: latest.at };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Minutes rather than seconds, for the frozen bar only. Two timers and the
|
||||||
|
// day controls only fit on one row if the timers are short, and a counter
|
||||||
|
// ticking every second at pill size is motion rather than information — the
|
||||||
|
// big card keeps its seconds, which is where you look if you want them.
|
||||||
|
function formatPillDuration(ms) {
|
||||||
|
if (ms < 0) ms = 0;
|
||||||
|
const totalMin = Math.floor(ms / 60000);
|
||||||
|
const h = Math.floor(totalMin / 60);
|
||||||
|
const m = totalMin % 60;
|
||||||
|
return h > 0 ? `${h}h${String(m).padStart(2, "0")}` : `${m}m`;
|
||||||
|
}
|
||||||
|
|
||||||
function formatCounter(ms) {
|
function formatCounter(ms) {
|
||||||
if (ms < 0) ms = 0;
|
if (ms < 0) ms = 0;
|
||||||
const totalSec = Math.floor(ms / 1000);
|
const totalSec = Math.floor(ms / 1000);
|
||||||
@@ -1048,8 +1060,7 @@
|
|||||||
pill.hidden = false;
|
pill.hidden = false;
|
||||||
pill.classList.toggle("asleep", state === "asleep");
|
pill.classList.toggle("asleep", state === "asleep");
|
||||||
pill.classList.toggle("awake", state === "awake");
|
pill.classList.toggle("awake", state === "awake");
|
||||||
const counter = formatCounter(Date.now() - ts);
|
ptime.textContent = formatPillDuration(Date.now() - ts);
|
||||||
ptime.textContent = counter;
|
|
||||||
icon.textContent = state === "asleep" ? "😴" : "☀️";
|
icon.textContent = state === "asleep" ? "😴" : "☀️";
|
||||||
const flip = state === "asleep" ? "Sleep end" : "Sleep start";
|
const flip = state === "asleep" ? "Sleep end" : "Sleep start";
|
||||||
pill.title = `${state === "asleep" ? "Asleep" : "Awake"} since ${formatTime(ts)} — tap to log ${flip.toLowerCase()}`;
|
pill.title = `${state === "asleep" ? "Asleep" : "Awake"} since ${formatTime(ts)} — tap to log ${flip.toLowerCase()}`;
|
||||||
@@ -1085,7 +1096,7 @@
|
|||||||
const wtime = document.getElementById("bar-walk-time");
|
const wtime = document.getElementById("bar-walk-time");
|
||||||
if (!walkSince) { pill.hidden = true; return; }
|
if (!walkSince) { pill.hidden = true; return; }
|
||||||
pill.hidden = false;
|
pill.hidden = false;
|
||||||
wtime.textContent = formatCounter(Date.now() - walkSince);
|
wtime.textContent = formatPillDuration(Date.now() - walkSince);
|
||||||
pill.title = `Walking since ${formatTime(walkSince)} — tap to log walk end`;
|
pill.title = `Walking since ${formatTime(walkSince)} — tap to log walk end`;
|
||||||
pill.setAttribute("aria-label", `Walking since ${formatTime(walkSince)}. Log walk end.`);
|
pill.setAttribute("aria-label", `Walking since ${formatTime(walkSince)}. Log walk end.`);
|
||||||
}
|
}
|
||||||
@@ -1093,11 +1104,11 @@
|
|||||||
function tickBigClock() {
|
function tickBigClock() {
|
||||||
if (bigClockState) {
|
if (bigClockState) {
|
||||||
const ptime = document.getElementById("bar-clock-time");
|
const ptime = document.getElementById("bar-clock-time");
|
||||||
if (ptime) ptime.textContent = formatCounter(Date.now() - bigClockSince);
|
if (ptime) ptime.textContent = formatPillDuration(Date.now() - bigClockSince);
|
||||||
}
|
}
|
||||||
if (walkSince) {
|
if (walkSince) {
|
||||||
const wtime = document.getElementById("bar-walk-time");
|
const wtime = document.getElementById("bar-walk-time");
|
||||||
if (wtime) wtime.textContent = formatCounter(Date.now() - walkSince);
|
if (wtime) wtime.textContent = formatPillDuration(Date.now() - walkSince);
|
||||||
}
|
}
|
||||||
// The card follows whichever of the two it is currently showing.
|
// The card follows whichever of the two it is currently showing.
|
||||||
if (bigClockState || walkSince) {
|
if (bigClockState || walkSince) {
|
||||||
@@ -2728,8 +2739,8 @@
|
|||||||
function renderDayBar() {
|
function renderDayBar() {
|
||||||
const day = selectedDay();
|
const day = selectedDay();
|
||||||
const isToday = ymd(day) === ymd(new Date());
|
const isToday = ymd(day) === ymd(new Date());
|
||||||
document.getElementById("day-next").disabled = isToday;
|
document.getElementById("day-next").disabled = isToday;
|
||||||
document.getElementById("day-today").disabled = isToday;
|
document.getElementById("cal-today").disabled = isToday;
|
||||||
// Year-less date on the picker's face — the year is implicit and the
|
// Year-less date on the picker's face — the year is implicit and the
|
||||||
// saved width keeps the bar on one row on small phones.
|
// saved width keeps the bar on one row on small phones.
|
||||||
document.getElementById("day-date-face").textContent =
|
document.getElementById("day-date-face").textContent =
|
||||||
@@ -4951,13 +4962,136 @@
|
|||||||
|
|
||||||
// The face opens the hidden input's native picker (iOS 16+ has showPicker;
|
// The face opens the hidden input's native picker (iOS 16+ has showPicker;
|
||||||
// focus() is the fallback and is what pops the picker on older iOS anyway).
|
// focus() is the fallback and is what pops the picker on older iOS anyway).
|
||||||
document.getElementById("day-date-face").addEventListener("click", () => {
|
// ---------- the month grid ----------
|
||||||
|
// The browser's own date picker is a sheet that covers the screen, which is
|
||||||
|
// exactly wrong here: the reason to change day is to see what the figures
|
||||||
|
// did on it, and a modal hides them. This one is a small panel under the
|
||||||
|
// bar, so the overview stays visible and updates as you move.
|
||||||
|
const dayCal = document.getElementById("day-cal");
|
||||||
|
const dayFace = document.getElementById("day-date-face");
|
||||||
|
const calMonthEl = document.getElementById("cal-month");
|
||||||
|
const calWeekdays = document.getElementById("cal-weekdays");
|
||||||
|
const calGrid = document.getElementById("cal-grid");
|
||||||
|
let calMonth = null; // first of the month on show
|
||||||
|
|
||||||
|
// Which weekday a week starts on, per the reader's locale — Monday in most of
|
||||||
|
// Europe, Sunday in the US. Intl knows; older engines don't, and the app's own
|
||||||
|
// day boundaries are local-midnight either way, so Monday is the fallback.
|
||||||
|
function firstDayOfWeek() {
|
||||||
try {
|
try {
|
||||||
if (typeof dayPicker.showPicker === "function") dayPicker.showPicker();
|
const loc = new Intl.Locale(navigator.language);
|
||||||
else dayPicker.focus();
|
const info = loc.weekInfo || (typeof loc.getWeekInfo === "function" ? loc.getWeekInfo() : null);
|
||||||
} catch {
|
if (info && info.firstDay) return info.firstDay % 7; // Intl 1..7 (Mon..Sun) → JS 1..0
|
||||||
dayPicker.focus();
|
} catch { /* not supported; fall through */ }
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCalendar() {
|
||||||
|
const start = firstDayOfWeek();
|
||||||
|
const selected = ymd(selectedDay());
|
||||||
|
const todayYmd = ymd(new Date());
|
||||||
|
calMonthEl.textContent = calMonth.toLocaleDateString(undefined, { month: "long", year: "numeric" });
|
||||||
|
|
||||||
|
// Weekday initials, taken from a real week so they follow the locale.
|
||||||
|
calWeekdays.innerHTML = "";
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const d = new Date(2026, 1, 1 + ((start + i - new Date(2026, 1, 1).getDay()) + 7) % 7);
|
||||||
|
const cell = document.createElement("span");
|
||||||
|
cell.textContent = d.toLocaleDateString(undefined, { weekday: "narrow" });
|
||||||
|
calWeekdays.appendChild(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always six rows, so the panel doesn't change height from month to month.
|
||||||
|
const gridStart = new Date(calMonth);
|
||||||
|
gridStart.setDate(1 - ((calMonth.getDay() - start + 7) % 7));
|
||||||
|
calGrid.innerHTML = "";
|
||||||
|
for (let i = 0; i < 42; i++) {
|
||||||
|
const d = new Date(gridStart);
|
||||||
|
d.setDate(gridStart.getDate() + i);
|
||||||
|
const key = ymd(d);
|
||||||
|
const btn = document.createElement("button");
|
||||||
|
btn.type = "button";
|
||||||
|
btn.className = "cal-day";
|
||||||
|
btn.textContent = d.getDate();
|
||||||
|
btn.dataset.day = key;
|
||||||
|
if (d.getMonth() !== calMonth.getMonth()) btn.classList.add("other-month");
|
||||||
|
if (key === todayYmd) btn.classList.add("is-today");
|
||||||
|
if (key === selected) btn.classList.add("is-selected");
|
||||||
|
btn.setAttribute("aria-selected", String(key === selected));
|
||||||
|
// There is nothing to show for a day that hasn't happened, and the bar's
|
||||||
|
// → is disabled on today for the same reason.
|
||||||
|
if (key > todayYmd) btn.disabled = true;
|
||||||
|
btn.setAttribute("aria-label",
|
||||||
|
d.toLocaleDateString(undefined, { weekday: "long", day: "numeric", month: "long", year: "numeric" }));
|
||||||
|
calGrid.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCalendar() {
|
||||||
|
calMonth = startOfDay(selectedDay());
|
||||||
|
calMonth.setDate(1);
|
||||||
|
renderCalendar();
|
||||||
|
dayCal.hidden = false;
|
||||||
|
dayFace.setAttribute("aria-expanded", "true");
|
||||||
|
const sel = calGrid.querySelector(".is-selected") || calGrid.querySelector(".cal-day:not([disabled])");
|
||||||
|
if (sel) sel.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCalendar({ refocus = true } = {}) {
|
||||||
|
if (dayCal.hidden) return;
|
||||||
|
dayCal.hidden = true;
|
||||||
|
dayFace.setAttribute("aria-expanded", "false");
|
||||||
|
if (refocus) dayFace.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickDay(key) {
|
||||||
|
dayPicker.value = key;
|
||||||
|
closeCalendar();
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
dayFace.addEventListener("click", () => {
|
||||||
|
if (dayCal.hidden) openCalendar(); else closeCalendar();
|
||||||
|
});
|
||||||
|
|
||||||
|
function shiftCalMonth(months) {
|
||||||
|
calMonth = new Date(calMonth.getFullYear(), calMonth.getMonth() + months, 1);
|
||||||
|
renderCalendar();
|
||||||
|
}
|
||||||
|
document.getElementById("cal-prev").addEventListener("click", () => shiftCalMonth(-1));
|
||||||
|
document.getElementById("cal-next").addEventListener("click", () => shiftCalMonth(1));
|
||||||
|
document.getElementById("cal-today").addEventListener("click", () => pickDay(ymd(new Date())));
|
||||||
|
|
||||||
|
calGrid.addEventListener("click", (e) => {
|
||||||
|
const btn = e.target.closest(".cal-day");
|
||||||
|
if (btn && !btn.disabled) pickDay(btn.dataset.day);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Arrow keys walk the grid, which is what makes it usable without a mouse;
|
||||||
|
// moving off the edge of the month brings the neighbouring one into view.
|
||||||
|
calGrid.addEventListener("keydown", (e) => {
|
||||||
|
const step = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[e.key];
|
||||||
|
if (!step) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const from = e.target.closest(".cal-day");
|
||||||
|
if (!from) return;
|
||||||
|
const [y, m, d] = from.dataset.day.split("-").map(Number);
|
||||||
|
const to = new Date(y, m - 1, d + step);
|
||||||
|
if (ymd(to) > ymd(new Date())) return; // nothing beyond today
|
||||||
|
if (to.getMonth() !== calMonth.getMonth() || to.getFullYear() !== calMonth.getFullYear()) {
|
||||||
|
calMonth = new Date(to.getFullYear(), to.getMonth(), 1);
|
||||||
|
}
|
||||||
|
renderCalendar();
|
||||||
|
const next = calGrid.querySelector(`[data-day="${ymd(to)}"]`);
|
||||||
|
if (next) next.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
dayCal.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Escape") { e.stopPropagation(); closeCalendar(); }
|
||||||
|
});
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (dayCal.hidden) return;
|
||||||
|
if (!dayCal.contains(e.target) && e.target !== dayFace) closeCalendar({ refocus: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
function shiftSelectedDay(days) {
|
function shiftSelectedDay(days) {
|
||||||
@@ -4968,10 +5102,6 @@
|
|||||||
}
|
}
|
||||||
document.getElementById("day-prev").addEventListener("click", () => shiftSelectedDay(-1));
|
document.getElementById("day-prev").addEventListener("click", () => shiftSelectedDay(-1));
|
||||||
document.getElementById("day-next").addEventListener("click", () => shiftSelectedDay(+1));
|
document.getElementById("day-next").addEventListener("click", () => shiftSelectedDay(+1));
|
||||||
document.getElementById("day-today").addEventListener("click", () => {
|
|
||||||
dayPicker.value = ymd(new Date());
|
|
||||||
render();
|
|
||||||
});
|
|
||||||
document.getElementById("exclude-day").addEventListener("click", () => {
|
document.getElementById("exclude-day").addEventListener("click", () => {
|
||||||
toggleExcludedDay(selectedDay()); // addEvent/deleteEvent re-render for us
|
toggleExcludedDay(selectedDay()); // addEvent/deleteEvent re-render for us
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
|
{ "date": "2026-09-08", "text": "The date now opens a small month grid of the app's own instead of the browser's date picker. The browser's one is a sheet that covers the screen, which is backwards when the reason to change day is to see what the numbers did on it — this one sits under the bar with the overview still visible and updating as you move. ← and → still step a day at a time; the grid is for jumping further, and the Today button now lives inside it. That is what made room for the walk timer and the sleep timer to sit on one row: the top bar no longer splits onto two rows on a phone, except on the very smallest. The two timers count in minutes now rather than seconds — the big card on Today still ticks in seconds, which is where you look if you want them" },
|
||||||
{ "date": "2026-09-08", "text": "A walk in progress now has a timer in the frozen bar at the top, next to the asleep/awake one, counting from when the walk started — so you can see how long you have been out from any tab without going to look. Tapping it ends the walk, the same way tapping the sleep timer logs the sleep boundary. On a narrow phone two timers no longer fit beside the date controls, so the bar splits onto two rows while a walk is on and goes back to one when it ends. The big timer card on Today shows the walk too while there is one — you are awake on a walk either way, so the walk is the more useful of the two" },
|
{ "date": "2026-09-08", "text": "A walk in progress now has a timer in the frozen bar at the top, next to the asleep/awake one, counting from when the walk started — so you can see how long you have been out from any tab without going to look. Tapping it ends the walk, the same way tapping the sleep timer logs the sleep boundary. On a narrow phone two timers no longer fit beside the date controls, so the bar splits onto two rows while a walk is on and goes back to one when it ends. The big timer card on Today shows the walk too while there is one — you are awake on a walk either way, so the walk is the more useful of the two" },
|
||||||
{ "date": "2026-09-07", "text": "Going back to the Today tab no longer jumps the viewport either — by tapping it or with the back button. The other tabs stopped jumping in the last change, but returning to Today is a history step, and the browser was restoring the scroll position from when you last left it" },
|
{ "date": "2026-09-07", "text": "Going back to the Today tab no longer jumps the viewport either — by tapping it or with the back button. The other tabs stopped jumping in the last change, but returning to Today is a history step, and the browser was restoring the scroll position from when you last left it" },
|
||||||
{ "date": "2026-09-07", "text": "The tab row now matches the frozen row above it that holds the date. It was a different shade, and the two sat as separate bars with the date row's rounded corners cutting between them; they share the same card colour now, and once you have scrolled the log buttons away they join into a single rounded block instead of two stacked ones" },
|
{ "date": "2026-09-07", "text": "The tab row now matches the frozen row above it that holds the date. It was a different shade, and the two sat as separate bars with the date row's rounded corners cutting between them; they share the same card colour now, and once you have scrolled the log buttons away they join into a single rounded block instead of two stacked ones" },
|
||||||
|
|||||||
+17
-2
@@ -135,11 +135,26 @@
|
|||||||
so the face button carries a compact year-less date and the real
|
so the face button carries a compact year-less date and the real
|
||||||
input stays (visually hidden) as the value + native picker. -->
|
input stays (visually hidden) as the value + native picker. -->
|
||||||
<span class="day-date">
|
<span class="day-date">
|
||||||
<button type="button" id="day-date-face" class="ghost"></button>
|
<!-- Opens the month grid below rather than the browser's own date
|
||||||
|
picker: that one is a full-screen sheet on a phone, and the
|
||||||
|
point of changing day here is watching the figures underneath
|
||||||
|
change with it. The input stays as the value and is never
|
||||||
|
shown — every read of the selected day still goes through it. -->
|
||||||
|
<button type="button" id="day-date-face" class="ghost"
|
||||||
|
aria-haspopup="dialog" aria-expanded="false"></button>
|
||||||
<input type="date" id="day-picker" tabindex="-1" aria-hidden="true" />
|
<input type="date" id="day-picker" tabindex="-1" aria-hidden="true" />
|
||||||
|
<div id="day-cal" class="day-cal" role="dialog" aria-label="Pick a day" hidden>
|
||||||
|
<div class="day-cal-head">
|
||||||
|
<button type="button" id="cal-prev" class="ghost icon-btn" aria-label="Previous month">‹</button>
|
||||||
|
<span id="cal-month" class="day-cal-month" aria-live="polite"></span>
|
||||||
|
<button type="button" id="cal-next" class="ghost icon-btn" aria-label="Next month">›</button>
|
||||||
|
</div>
|
||||||
|
<div id="cal-weekdays" class="day-cal-weekdays" aria-hidden="true"></div>
|
||||||
|
<div id="cal-grid" class="day-cal-grid" role="grid"></div>
|
||||||
|
<button type="button" id="cal-today" class="ghost day-cal-today">Today</button>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<button type="button" id="day-next" class="ghost" aria-label="Next day">→</button>
|
<button type="button" id="day-next" class="ghost" aria-label="Next day">→</button>
|
||||||
<button type="button" id="day-today" class="ghost">Today</button>
|
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
+70
-3
@@ -293,6 +293,70 @@ body::before {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
/* ---------- the month grid ---------- */
|
||||||
|
/* Anchored under the date button rather than filling the screen the way the
|
||||||
|
browser's own picker does: changing day is worth doing *because* of the
|
||||||
|
figures below, so they have to stay in view while you move. Deliberately
|
||||||
|
kept short — six rows of small cells — for the same reason. */
|
||||||
|
.day-cal {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 70; /* over the day bar itself, which is 60 */
|
||||||
|
width: 268px;
|
||||||
|
max-width: calc(100vw - 24px);
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.day-cal-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.day-cal-month {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.day-cal-weekdays,
|
||||||
|
.day-cal-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.day-cal-weekdays span {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
button.cal-day {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
button.cal-day.other-month { color: var(--muted); opacity: 0.5; }
|
||||||
|
button.cal-day:disabled { opacity: 0.25; cursor: default; }
|
||||||
|
button.cal-day:disabled:hover { filter: none; }
|
||||||
|
/* Today is outlined, the selected day is filled — so "where I am" and "where
|
||||||
|
now is" stay tellable apart when they are different days. */
|
||||||
|
button.cal-day.is-today { box-shadow: inset 0 0 0 1.5px var(--accent); }
|
||||||
|
button.cal-day.is-selected {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.day-cal-today { width: 100%; margin-top: 8px; padding: 7px 10px; font-size: 0.85rem; }
|
||||||
|
|
||||||
#day-date-face {
|
#day-date-face {
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -341,11 +405,14 @@ body::before {
|
|||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
/* Sized so two of these fit beside the day controls on one row. The big card
|
||||||
padding: 7px 10px;
|
is where a timer is meant to be read at size; this is a glance, which is
|
||||||
|
also why the pills count in minutes and the card keeps the seconds. */
|
||||||
|
gap: 4px;
|
||||||
|
padding: 7px 9px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1rem;
|
font-size: 0.9rem;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user