Keep the header and weight rows tidy when the age is long

The age counter can read "16 weeks (3 months and 3 weeks) old", which broke
two tight layouts: the header title never truncated, so a long name (or the
wide status pill) collided with the action buttons and the age wrapped to
three lines; and each weight-log row embedded the full age, wrapping to two
lines.

Add compact age formatters and use them where space is tight: formatAgeShort
("16 wk · 3 mo 3 wk") in the header, formatAgeWeeks ("16 wk") in weight rows;
the verbose form stays on the roomy weight-chart caption. Make the header
robust — the title flexes and the name ellipsis-truncates so the buttons are
never pushed, while the age wraps rather than truncating so it's never cut
off. Weight rows keep the date/age on one line with the value pinned right.
This commit is contained in:
Alexander Heldt
2026-08-01 18:11:03 +00:00
parent 69e312175b
commit 1ed325b834
3 changed files with 42 additions and 3 deletions
+20 -2
View File
@@ -291,6 +291,24 @@
return `${wk} (${monthPhrase}) old`; return `${wk} (${monthPhrase}) old`;
} }
// Compact one-line age for space-tight spots (header): keeps the weeks + months
// breakdown but abbreviated — "16 wk · 3 mo 3 wk", "5 mo 2 wk", "3 wk".
function formatAgeShort(birthday, at) {
const a = ageParts(birthday, at);
if (!a) return "";
const wk = `${a.weeks} wk`;
if (a.months < 1) return wk;
const mo = `${a.months} mo${a.remWeeks > 0 ? ` ${a.remWeeks} wk` : ""}`;
if (a.months >= 4) return mo;
return `${wk} · ${mo}`;
}
// Weeks-only age for dense lists (weight rows): "16 wk".
function formatAgeWeeks(birthday, at) {
const a = ageParts(birthday, at);
return a ? `${a.weeks} wk` : "";
}
// Rough age-based daily sleep goal (hours), for the trend chart's goal band: // Rough age-based daily sleep goal (hours), for the trend chart's goal band:
// 08 weeks 2022h, 816 weeks 1820h, then 1618h to 6 months and 1416h to // 08 weeks 2022h, 816 weeks 1820h, then 1618h to 6 months and 1416h to
// 12 months. No birthday (or an adult dog) → no goal. // 12 months. No birthday (or an adult dog) → no goal.
@@ -1669,7 +1687,7 @@
li.className = "ww weight-ww"; li.className = "ww weight-ww";
const date = document.createElement("span"); const date = document.createElement("span");
date.className = "ww-range"; date.className = "ww-range";
const age = formatAge(birthday, w.at); const age = formatAgeWeeks(birthday, w.at);
const dateStr = new Date(w.at).toLocaleDateString(undefined, { month: "short", day: "numeric" }); const dateStr = new Date(w.at).toLocaleDateString(undefined, { month: "short", day: "numeric" });
date.textContent = age ? `${dateStr} · ${age}` : dateStr; date.textContent = age ? `${dateStr} · ${age}` : dateStr;
const val = document.createElement("span"); const val = document.createElement("span");
@@ -1888,7 +1906,7 @@
const ageEl = document.getElementById("puppy-age"); const ageEl = document.getElementById("puppy-age");
title.textContent = cfg.name ? `🐶 ${cfg.name}` : "🐶 Puppy Tracker"; title.textContent = cfg.name ? `🐶 ${cfg.name}` : "🐶 Puppy Tracker";
document.title = cfg.name ? `${cfg.name} · Puppy Tracker` : "Puppy Tracker"; document.title = cfg.name ? `${cfg.name} · Puppy Tracker` : "Puppy Tracker";
const ageText = formatAge(cfg.birthday); const ageText = formatAgeShort(cfg.birthday);
ageEl.textContent = ageText; ageEl.textContent = ageText;
ageEl.hidden = !ageText; ageEl.hidden = !ageText;
// Use the puppy's name in the sleep-timeline heading rather than assuming a // Use the puppy's name in the sleep-timeline heading rather than assuming a
+1
View File
@@ -1,4 +1,5 @@
[ [
{ "date": "2026-08-01", "text": "Tidied the header on long names and ages — the name now truncates instead of shoving the buttons, and the age reads as a compact \"16 wk · 3 mo 3 wk\"; weight-log rows are a single line again (\"Aug 1 · 16 wk\")" },
{ "date": "2026-07-26", "text": "Added a fan-chart view of the pedigree (toggle it in the header): your dog at the centre with each generation fanning outward as a ring, so many generations fit at once without the tree sprawling sideways — tap a wedge for that dog, and repeated ancestors keep their colour" }, { "date": "2026-07-26", "text": "Added a fan-chart view of the pedigree (toggle it in the header): your dog at the centre with each generation fanning outward as a ring, so many generations fit at once without the tree sprawling sideways — tap a wedge for that dog, and repeated ancestors keep their colour" },
{ "date": "2026-07-26", "text": "Added a Collapse all / Expand all toggle to the pedigree, to fold the whole tree down to your dog or open every branch at once" }, { "date": "2026-07-26", "text": "Added a Collapse all / Expand all toggle to the pedigree, to fold the whole tree down to your dog or open every branch at once" },
{ "date": "2026-07-26", "text": "The pedigree is now zoomable — use the +/ buttons, ⌘/Ctrl-scroll, or pinch on a phone — to fit a wide tree on screen or zoom in for detail; your zoom level is remembered" }, { "date": "2026-07-26", "text": "The pedigree is now zoomable — use the +/ buttons, ⌘/Ctrl-scroll, or pinch on a phone — to fit a wide tree on screen or zoom in for detail; your zoom level is remembered" },
+21 -1
View File
@@ -78,13 +78,24 @@ h1 {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
flex: 1 1 auto;
min-width: 0; min-width: 0;
} }
/* Truncate the name so a long one never runs into the action buttons. Scoped to
the header title so the auth-screen heading is unaffected. */
#app-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.puppy-age { .puppy-age {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--muted); color: var(--muted);
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
/* One line in the common case; on a very narrow screen it wraps rather than
truncating, so the age is never cut off. The name (#app-title) is what
truncates to keep the buttons clear. */
} }
.header-actions { .header-actions {
@@ -644,7 +655,16 @@ dialog menu {
.weight-summary .stat-value.up { color: var(--gain); } .weight-summary .stat-value.up { color: var(--gain); }
.weight-summary .stat-value.down { color: var(--danger); } .weight-summary .stat-value.down { color: var(--danger); }
.ww.weight-ww { cursor: pointer; } .ww.weight-ww { cursor: pointer; }
.ww.weight-ww .ww-dur { text-align: right; } /* Keep each weight row to one line: the date/age column shrinks and ellipses,
the weight value stays a fixed size, right-aligned and always visible. */
.ww.weight-ww .ww-range {
min-width: 0;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ww.weight-ww .ww-dur { flex: 0 0 auto; text-align: right; }
.legend { .legend {
display: flex; display: flex;