diff --git a/src/app.js b/src/app.js
index d243ee6..9d0be5e 100644
--- a/src/app.js
+++ b/src/app.js
@@ -941,12 +941,12 @@
return { yMax, steps: Math.max(1, Math.round(yMax / step)) };
}
- // Sleep-specific axis: always 2-hour granularity, capped at 24h/day,
- // for a more readable picture of typical 10–18 h puppy sleep.
+ // Sleep-specific axis: 1-hour granularity, capped at 24h/day. Shared by the
+ // sleep bars and the trend chart; both thin out labels when steps get dense.
function niceAxisSleepHours(rawMax) {
if (!(rawMax > 0)) return { yMax: 2, steps: 2 };
- const m = Math.min(24, Math.max(2, Math.ceil(rawMax / 2) * 2));
- return { yMax: m, steps: m / 2 };
+ const m = Math.min(24, Math.max(2, Math.ceil(rawMax)));
+ return { yMax: m, steps: m };
}
function escapeText(s) {
@@ -980,13 +980,18 @@
const gap = days.length > 14 ? 2 : 4;
const barW = (innerW - (days.length - 1) * gap) / days.length;
+ // Every gridline gets a label unless that would crowd the 9px text
+ // (this chart's plot area is shorter than the trend chart's).
+ const labelEvery = ySteps > 12 ? 2 : 1;
const parts = [];
for (let i = 0; i <= ySteps; i++) {
const y = MT + innerH * (1 - i / ySteps);
const v = Math.round(yMax * i / ySteps * 10) / 10;
const vText = v % 1 === 0 ? v : v.toFixed(1);
parts.push(``);
- parts.push(`${vText}h`);
+ if (i % labelEvery === 0) {
+ parts.push(`${vText}h`);
+ }
}
const selYmd = ymd(selectedDay());
@@ -1341,14 +1346,6 @@
return { today, yesterday, avg, avgDays, projected };
}
- // Trend-specific y-axis: 1-hour gridlines (vs the bar charts' 2-hour steps)
- // so nearby curves separate visually; still capped at 24h.
- function niceAxisTrendHours(rawMax) {
- if (!(rawMax > 0)) return { yMax: 2, steps: 2 };
- const m = Math.min(24, Math.max(2, Math.ceil(rawMax)));
- return { yMax: m, steps: m };
- }
-
function drawSleepTrendChart(curves) {
const svg = document.getElementById("chart-sleep-trend");
if (!svg) return;
@@ -1366,7 +1363,7 @@
].filter(s => s.pts && s.pts.length > 1);
const rawMax = Math.max(...series.flatMap(s => s.pts.map(p => p.y)));
- const { yMax, steps: ySteps } = niceAxisTrendHours(rawMax);
+ const { yMax, steps: ySteps } = niceAxisSleepHours(rawMax);
const xOf = (hour) => ML + (hour / 24) * innerW;
const yOf = (v) => MT + innerH * (1 - v / yMax);
diff --git a/src/changelog.json b/src/changelog.json
index 7936d35..fd5817d 100644
--- a/src/changelog.json
+++ b/src/changelog.json
@@ -1,4 +1,5 @@
[
+ { "date": "2026-07-15", "text": "The Sleep chart in the last-N-days card now has 1-hour gridlines too" },
{ "date": "2026-07-15", "text": "The Sleep trend chart is taller with 1-hour gridlines, so nearby lines are easier to tell apart" },
{ "date": "2026-07-15", "text": "The Sleep trend legend shows the hours for each line — Today, Yesterday and the average, next to the projection" },
{ "date": "2026-07-15", "text": "The Sleep trend chart projects where today will land by midnight — a dashed tail continues today's line following the average day's rhythm" },