Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
122897c732 | ||
|
|
31e04fb9a1 |
+4
-48
@@ -664,19 +664,6 @@
|
|||||||
.map(w => ({ start: w.start, end: w.end, ongoing: w.ongoing && today }));
|
.map(w => ({ start: w.start, end: w.end, ongoing: w.ongoing && today }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rough age-based walking guideline (the widely used "five-minute rule"):
|
|
||||||
// about 5 minutes per month of age per walk, twice a day, until the puppy is
|
|
||||||
// grown. Returns null without a birthday or once it's a year old, the same
|
|
||||||
// way sleepTargetFor bows out.
|
|
||||||
function walkTargetFor(birthday) {
|
|
||||||
const a = ageParts(birthday);
|
|
||||||
if (!a || a.months >= 12) return null;
|
|
||||||
// Under a month of counted age the rule has nothing to say yet; treat it
|
|
||||||
// as one "month" so the advice stays a short outing rather than zero.
|
|
||||||
const months = Math.max(1, a.months);
|
|
||||||
return { perWalk: months * 5, walks: 2, total: months * 10 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- rendering ----------
|
// ---------- rendering ----------
|
||||||
const dayPicker = document.getElementById("day-picker");
|
const dayPicker = document.getElementById("day-picker");
|
||||||
const eventList = document.getElementById("event-list");
|
const eventList = document.getElementById("event-list");
|
||||||
@@ -1762,7 +1749,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawWalkTrendChart(curves, target) {
|
function drawWalkTrendChart(curves) {
|
||||||
const svg = document.getElementById("chart-walk-trend");
|
const svg = document.getElementById("chart-walk-trend");
|
||||||
if (!svg) return;
|
if (!svg) return;
|
||||||
const W = 320, H = 180;
|
const W = 320, H = 180;
|
||||||
@@ -1777,12 +1764,7 @@
|
|||||||
{ pts: curves.today, cls: "wtrend-today", label: curves.dayLabel },
|
{ pts: curves.today, cls: "wtrend-today", label: curves.dayLabel },
|
||||||
].filter(s => s.pts && s.pts.length > 1);
|
].filter(s => s.pts && s.pts.length > 1);
|
||||||
|
|
||||||
// The axis has to reach the goal even on a day that fell well short of it,
|
const rawMax = Math.max(...series.flatMap(s => s.pts.map(p => p.y)));
|
||||||
// or the line it is measured against would sit off the top of the chart.
|
|
||||||
const rawMax = Math.max(
|
|
||||||
target ? target.total : 0,
|
|
||||||
...series.flatMap(s => s.pts.map(p => p.y)),
|
|
||||||
);
|
|
||||||
const { yMax, steps } = niceAxisLinear(rawMax);
|
const { yMax, steps } = niceAxisLinear(rawMax);
|
||||||
// Minutes are linear all the way down, so no split axis: the sleep trend
|
// Minutes are linear all the way down, so no split axis: the sleep trend
|
||||||
// stretches its top because the interesting hours cluster near a 16h goal,
|
// stretches its top because the interesting hours cluster near a 16h goal,
|
||||||
@@ -1804,16 +1786,6 @@
|
|||||||
parts.push(`<text x="${x.toFixed(1)}" y="${H - MB + 14}" text-anchor="${anchor}">${pad2(hr)}</text>`);
|
parts.push(`<text x="${x.toFixed(1)}" y="${H - MB + 14}" text-anchor="${anchor}">${pad2(hr)}</text>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The five-minute rule as one line rather than a band: it is a target to
|
|
||||||
// reach, with no upper bound a band would imply.
|
|
||||||
if (target) {
|
|
||||||
const y = yOf(Math.min(target.total, yMax));
|
|
||||||
parts.push(
|
|
||||||
`<line class="wtrend-goal" x1="${ML}" y1="${y.toFixed(1)}" x2="${W - MR}" y2="${y.toFixed(1)}">` +
|
|
||||||
`<title>${escapeText(`Goal ~${target.total} min (${target.perWalk} min × ${target.walks})`)}</title></line>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const s of series) {
|
for (const s of series) {
|
||||||
const d = s.pts
|
const d = s.pts
|
||||||
.map((p, i) => `${i === 0 ? "M" : "L"}${xOf(p.x).toFixed(1)} ${yOf(p.y).toFixed(1)}`)
|
.map((p, i) => `${i === 0 ? "M" : "L"}${xOf(p.x).toFixed(1)} ${yOf(p.y).toFixed(1)}`)
|
||||||
@@ -1830,22 +1802,11 @@
|
|||||||
|
|
||||||
function renderWalkTrend(events) {
|
function renderWalkTrend(events) {
|
||||||
const curves = walkTrendCurves(events);
|
const curves = walkTrendCurves(events);
|
||||||
const target = walkTargetFor(loadConfig().birthday);
|
drawWalkTrendChart(curves);
|
||||||
drawWalkTrendChart(curves, target);
|
|
||||||
|
|
||||||
const chip = (id) => document.getElementById(id);
|
const chip = (id) => document.getElementById(id);
|
||||||
const mins = (pts) => `${Math.round(pts[pts.length - 1].y)} min`;
|
const mins = (pts) => `${Math.round(pts[pts.length - 1].y)} min`;
|
||||||
// With no projection to carry it, the ✓ goes on today's own chip: the goal
|
chip("legend-wtrend-today-text").textContent = `${curves.dayLabel} ${mins(curves.today)}`;
|
||||||
// is a total for the day, so today's line is what meets it or doesn't.
|
|
||||||
let mark = "";
|
|
||||||
if (target) {
|
|
||||||
const done = curves.today[curves.today.length - 1].y >= target.total;
|
|
||||||
mark = done ? " ✓" : "";
|
|
||||||
chip("legend-wtrend-today-text").parentElement.title = done
|
|
||||||
? "Past the walking goal for this age"
|
|
||||||
: `Goal is about ${target.total} min a day at this age`;
|
|
||||||
}
|
|
||||||
chip("legend-wtrend-today-text").textContent = `${curves.dayLabel} ${mins(curves.today)}${mark}`;
|
|
||||||
|
|
||||||
const yLegend = chip("legend-wtrend-yesterday");
|
const yLegend = chip("legend-wtrend-yesterday");
|
||||||
yLegend.hidden = !curves.yesterday;
|
yLegend.hidden = !curves.yesterday;
|
||||||
@@ -1857,11 +1818,6 @@
|
|||||||
if (curves.avg) {
|
if (curves.avg) {
|
||||||
chip("legend-wtrend-avg-text").textContent = `${curves.avgDays}-day avg ${mins(curves.avg)}`;
|
chip("legend-wtrend-avg-text").textContent = `${curves.avgDays}-day avg ${mins(curves.avg)}`;
|
||||||
}
|
}
|
||||||
const gLegend = chip("legend-wtrend-goal");
|
|
||||||
gLegend.hidden = !target;
|
|
||||||
if (target) {
|
|
||||||
chip("legend-wtrend-goal-text").textContent = `Goal ~${target.total} min`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Which heatmap block is focused, remembered across re-renders so a background
|
// Which heatmap block is focused, remembered across re-renders so a background
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
[
|
[
|
||||||
|
{ "date": "2026-09-01", "text": "Removed the walking goal from the Walk trend — the dashed target line, its legend chip and the ✓ that marked a day as met. It came from the “five-minute rule” (five minutes per month of age, twice a day), which is a widely repeated rule of thumb rather than veterinary guidance, and the app was stating it more confidently than it deserved. The chart is now just a record of what you walked, against yesterday and the average" },
|
||||||
|
{ "date": "2026-09-01", "text": "The 7d / 14d / 30d buttons have moved out of the Sleep panel onto their own “Charts cover” row, just under the Log event buttons. They always set the window for every chart on the page — training, timing, sleep, walks, pees/poos/meals — but sitting inside the Sleep panel made them look like a sleep setting" },
|
||||||
{ "date": "2026-09-01", "text": "Dropped the “Darker = more sessions that day” caption under the training consistency grid. Tapping a cell still opens that day" },
|
{ "date": "2026-09-01", "text": "Dropped the “Darker = more sessions that day” caption under the training consistency grid. Tapping a cell still opens that day" },
|
||||||
{ "date": "2026-09-01", "text": "Dropped two hint lines: the “Based on N pee gaps…” sentence under the Timing charts, and the “Rule of thumb at this age…” one in the Walks panel. The charts above them already say it. The age-based walking goal is still there, drawn as the goal line on the Walk trend" },
|
{ "date": "2026-09-01", "text": "Dropped two hint lines: the “Based on N pee gaps…” sentence under the Timing charts, and the “Rule of thumb at this age…” one in the Walks panel. The charts above them already say it. The age-based walking goal is still there, drawn as the goal line on the Walk trend" },
|
||||||
{ "date": "2026-09-01", "text": "On the dark theme the awake timers are a warm near-white now — both “Awake for” at the top and the pill in the day bar — instead of yellow text on a yellow-tinted pill. The light theme keeps its dark gold, where white would disappear into a near-white pill" },
|
{ "date": "2026-09-01", "text": "On the dark theme the awake timers are a warm near-white now — both “Awake for” at the top and the pill in the day bar — instead of yellow text on a yellow-tinted pill. The light theme keeps its dark gold, where white would disappear into a near-white pill" },
|
||||||
|
|||||||
+14
-11
@@ -134,6 +134,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Page-level, not a panel's: every panel below that covers more than
|
||||||
|
one day reads this, so it sits on its own row above them all rather
|
||||||
|
than inside one of them, where it read as that panel's own control
|
||||||
|
(see renderChartWindow). -->
|
||||||
|
<div class="chart-window">
|
||||||
|
<span class="chart-window-label">Charts cover</span>
|
||||||
|
<div class="chart-days-picker" role="group" aria-label="How many days the charts cover">
|
||||||
|
<button type="button" class="ghost" data-days="7">7d</button>
|
||||||
|
<button type="button" class="ghost" data-days="14">14d</button>
|
||||||
|
<button type="button" class="ghost" data-days="30">30d</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="training" data-panel="training">
|
<section class="training" data-panel="training">
|
||||||
<h2>Training</h2>
|
<h2>Training</h2>
|
||||||
<ul id="training-list" class="training-list"></ul>
|
<ul id="training-list" class="training-list"></ul>
|
||||||
@@ -229,11 +242,6 @@
|
|||||||
|
|
||||||
<section class="patterns" data-panel="sleep-daily">
|
<section class="patterns" data-panel="sleep-daily">
|
||||||
<h2>Sleep <span class="muted-note" data-chart-days-label>(last 7 days)</span></h2>
|
<h2>Sleep <span class="muted-note" data-chart-days-label>(last 7 days)</span></h2>
|
||||||
<div class="chart-days-picker" role="group" aria-label="How many days the charts cover">
|
|
||||||
<button type="button" class="ghost" data-days="7">7d</button>
|
|
||||||
<button type="button" class="ghost" data-days="14">14d</button>
|
|
||||||
<button type="button" class="ghost" data-days="30">30d</button>
|
|
||||||
</div>
|
|
||||||
<div class="chart">
|
<div class="chart">
|
||||||
<div class="chart-title">Hours per day</div>
|
<div class="chart-title">Hours per day</div>
|
||||||
<svg id="chart-sleep" class="chart-svg" viewBox="0 0 320 160" role="img" aria-label="Sleep hours per day"></svg>
|
<svg id="chart-sleep" class="chart-svg" viewBox="0 0 320 160" role="img" aria-label="Sleep hours per day"></svg>
|
||||||
@@ -305,20 +313,15 @@
|
|||||||
|
|
||||||
<section class="patterns" data-panel="walk-trend" hidden>
|
<section class="patterns" data-panel="walk-trend" hidden>
|
||||||
<h2>Walk trend</h2>
|
<h2>Walk trend</h2>
|
||||||
<svg id="chart-walk-trend" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Cumulative minutes walked through the selected day, the day before it, the recent average, and the age-based daily goal"></svg>
|
<svg id="chart-walk-trend" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Cumulative minutes walked through the selected day, the day before it, and the recent average"></svg>
|
||||||
<div class="legend">
|
<div class="legend">
|
||||||
<span class="lg wtrend-today"><span class="sw"></span><span id="legend-wtrend-today-text">Today</span></span>
|
<span class="lg wtrend-today"><span class="sw"></span><span id="legend-wtrend-today-text">Today</span></span>
|
||||||
<span class="lg wtrend-yesterday" id="legend-wtrend-yesterday"><span class="sw"></span><span id="legend-wtrend-yesterday-text">Yesterday</span></span>
|
<span class="lg wtrend-yesterday" id="legend-wtrend-yesterday"><span class="sw"></span><span id="legend-wtrend-yesterday-text">Yesterday</span></span>
|
||||||
<span class="lg wtrend-avg" id="legend-wtrend-avg"><span class="sw"></span><span id="legend-wtrend-avg-text">7-day avg</span></span>
|
<span class="lg wtrend-avg" id="legend-wtrend-avg"><span class="sw"></span><span id="legend-wtrend-avg-text">7-day avg</span></span>
|
||||||
<span class="lg wtrend-goal" id="legend-wtrend-goal" hidden><span class="sw"></span><span id="legend-wtrend-goal-text">Goal</span></span>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="muted-note">Minutes walked so far at each point of the day, against yesterday and the average over the picked window. The line climbs only while a walk is on, so every step is one walk.</p>
|
<p class="muted-note">Minutes walked so far at each point of the day, against yesterday and the average over the picked window. The line climbs only while a walk is on, so every step is one walk.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- The day-window picker lives in this panel but governs every
|
|
||||||
day-window chart on the page — the training grid above, both sleep
|
|
||||||
patterns below, the counts panel and the walk chart — so changing it
|
|
||||||
here changes all of them (see renderChartWindow). -->
|
|
||||||
<section class="weight" data-panel="weight">
|
<section class="weight" data-panel="weight">
|
||||||
<h2>Weight</h2>
|
<h2>Weight</h2>
|
||||||
<div class="weight-summary">
|
<div class="weight-summary">
|
||||||
|
|||||||
+18
-7
@@ -721,11 +721,29 @@ dialog menu {
|
|||||||
.time-row input { flex: 1 1 120px; min-width: 0; }
|
.time-row input { flex: 1 1 120px; min-width: 0; }
|
||||||
.time-row button { padding: 8px 12px; }
|
.time-row button { padding: 8px 12px; }
|
||||||
|
|
||||||
|
/* The chart window is a page-level control rather than a panel's, so its row
|
||||||
|
skips the card chrome the sections have: no surface, no shadow, just a label
|
||||||
|
and the buttons sitting on the page background between two cards. */
|
||||||
|
.chart-window {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.chart-window-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
.chart-days-picker {
|
.chart-days-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
margin: -4px 0 14px;
|
margin: -4px 0 14px;
|
||||||
}
|
}
|
||||||
|
/* No heading above it out here, so nothing to pull up under or clear beneath. */
|
||||||
|
.chart-window .chart-days-picker { margin: 0; }
|
||||||
.chart-days-picker button {
|
.chart-days-picker button {
|
||||||
padding: 5px 12px;
|
padding: 5px 12px;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@@ -1155,16 +1173,9 @@ input.switch:checked::after { transform: translateX(18px); }
|
|||||||
fill: none;
|
fill: none;
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
.chart-svg .wtrend-goal {
|
|
||||||
stroke: var(--walk);
|
|
||||||
stroke-width: 1.5;
|
|
||||||
stroke-dasharray: 2 3;
|
|
||||||
opacity: 0.55;
|
|
||||||
}
|
|
||||||
.lg.wtrend-today .sw { background: var(--walk); }
|
.lg.wtrend-today .sw { background: var(--walk); }
|
||||||
.lg.wtrend-yesterday .sw { background: var(--eat); }
|
.lg.wtrend-yesterday .sw { background: var(--eat); }
|
||||||
.lg.wtrend-avg .sw { background: var(--weight); }
|
.lg.wtrend-avg .sw { background: var(--weight); }
|
||||||
.lg.wtrend-goal .sw { background: color-mix(in srgb, var(--walk) 45%, var(--surface)); }
|
|
||||||
.lg[hidden] { display: none; }
|
.lg[hidden] { display: none; }
|
||||||
|
|
||||||
.chart-svg .hm-cell { stroke: none; }
|
.chart-svg .hm-cell { stroke: none; }
|
||||||
|
|||||||
Reference in New Issue
Block a user