diff --git a/src/app.js b/src/app.js
index da08b9b..db4f777 100644
--- a/src/app.js
+++ b/src/app.js
@@ -2459,7 +2459,7 @@
const pedBtn = document.getElementById("pedigree-btn");
const pedRefresh = document.getElementById("pedigree-refresh");
- const PED_OPEN_DEPTH = 4; // generations shown expanded by default; deeper collapse
+ const PED_OPEN_DEPTH = 3; // show 3 generations expanded by default; deeper collapses
let pedPollTimer = null;
let pedNodes = {}; // latest ancestry map, for the progress count
@@ -2646,12 +2646,18 @@
}
// The tree is ahnentafel-indexed: the dog is position 1, its sire 2n and dam
- // 2n+1. We build recursively (sire above dam) and collapse below PED_OPEN_DEPTH.
+ // 2n+1. We render it top-down like a family tree — the dog on top, parents
+ // branching below — as a nested
/- so CSS can draw the connectors.
+ // Built recursively (sire left, dam right) and collapsed below PED_OPEN_DEPTH.
function renderTree(nodes) {
pedNodes = nodes;
pedTree.textContent = "";
const root = buildPedNode(nodes, 1, 0);
- if (root) pedTree.append(root);
+ if (!root) return;
+ const ul = document.createElement("ul");
+ ul.className = "ped-tree-h";
+ ul.append(root);
+ pedTree.append(ul);
}
function buildPedNode(nodes, pos, depth) {
@@ -2660,8 +2666,7 @@
const hasDam = !!nodes[String(pos * 2 + 1)];
if (!n && !hasSire && !hasDam) return null;
- const wrap = document.createElement("div");
- wrap.className = "ped-node";
+ const li = document.createElement("li");
const card = document.createElement("div");
card.className = "ped-card";
@@ -2682,32 +2687,30 @@
r.textContent = n.reg;
card.append(r);
}
+ li.append(card);
if (hasSire || hasDam) {
- const kids = document.createElement("div");
- kids.className = "ped-children";
+ const kids = document.createElement("ul");
const s = buildPedNode(nodes, pos * 2, depth + 1);
const d = buildPedNode(nodes, pos * 2 + 1, depth + 1);
if (s) { s.classList.add("ped-sire"); kids.append(s); }
if (d) { d.classList.add("ped-dam"); kids.append(d); }
const collapsed = depth >= PED_OPEN_DEPTH;
- if (collapsed) wrap.classList.add("collapsed");
+ if (collapsed) li.classList.add("collapsed");
const toggle = document.createElement("button");
toggle.type = "button";
toggle.className = "ped-toggle";
toggle.setAttribute("aria-label", "Toggle ancestors");
toggle.textContent = collapsed ? "+" : "−";
toggle.addEventListener("click", () => {
- const nowCollapsed = wrap.classList.toggle("collapsed");
+ const nowCollapsed = li.classList.toggle("collapsed");
toggle.textContent = nowCollapsed ? "+" : "−";
});
card.prepend(toggle);
- wrap.append(card, kids);
- } else {
- wrap.append(card);
+ li.append(kids);
}
- return wrap;
+ return li;
}
// ---------- changelog dialog ----------
diff --git a/src/changelog.json b/src/changelog.json
index f466f8c..1629b7e 100644
--- a/src/changelog.json
+++ b/src/changelog.json
@@ -1,4 +1,5 @@
[
+ { "date": "2026-07-26", "text": "The pedigree now reads top-down like a family tree — your dog on top with its sire and dam branching below — showing three generations at a glance, with each dog expandable to trace the line further back" },
{ "date": "2026-07-26", "text": "The pedigree ID set in Settings now syncs reliably to your other devices — it's no longer dropped when two devices' clocks disagree" },
{ "date": "2026-07-26", "text": "New 🌳 Pedigree page: add your dog's SKK chip or registration number in Settings to unlock it, then explore its ancestry as a tree — the first generations show at once and the line fills in further back as it's traced from SKK Hunddata. It's cached, so it reopens instantly and works offline" },
{ "date": "2026-07-24", "text": "The age counter reads \"16 weeks (3 months and 3 weeks) old\" so weeks and months line up; past 4 months it drops the weeks and shows just months (e.g. \"5 months and 2 weeks old\")" },
diff --git a/src/style.css b/src/style.css
index 3ebbbae..01933fc 100644
--- a/src/style.css
+++ b/src/style.css
@@ -1123,59 +1123,111 @@ section.collapsed > :not(h2) { display: none; }
.ped-subject-name { font-size: 1.15rem; font-weight: 700; }
.ped-subject-meta { font-size: 0.85rem; color: var(--muted); margin-top: 2px; }
-/* the tree */
+/* Top-down family tree: the dog on top, parents branching below, connected by
+ lines drawn with each
- 's ::before/::after (the classic CSS tree). Wider
+ than the screen once expanded, so the container scrolls horizontally. */
.pedigree-tree {
overflow-x: auto;
- padding-bottom: 24px;
+ padding: 8px 0 28px;
}
-.ped-node { position: relative; }
+.ped-tree-h, .ped-tree-h ul {
+ display: flex;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+.ped-tree-h {
+ /* "safe" centers when the tree fits and falls back to start-aligned (no
+ clipped/unreachable left edge) once it's wider than the screen. */
+ justify-content: safe center;
+ min-width: max-content;
+ padding: 4px 16px 12px;
+}
+.ped-tree-h ul {
+ justify-content: center;
+ position: relative;
+ padding-top: 22px; /* room for the connector from the parent above */
+}
+.ped-tree-h li {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 22px 6px 0;
+}
+/* Elbow from each child up to the horizontal bar shared by its siblings. */
+.ped-tree-h li::before,
+.ped-tree-h li::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ width: 50%;
+ height: 22px;
+ border-top: 2px solid var(--border);
+}
+.ped-tree-h li::before { right: 50%; }
+.ped-tree-h li::after { left: 50%; border-left: 2px solid var(--border); }
+/* Vertical drop from a parent card down to its children's bar. */
+.ped-tree-h ul::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 50%;
+ height: 22px;
+ border-left: 2px solid var(--border);
+}
+/* A lone parent connects with a straight line, no elbow. */
+.ped-tree-h li:only-child::before,
+.ped-tree-h li:only-child::after { display: none; }
+/* Trim the outer half-lines at the ends of a sibling row. */
+.ped-tree-h li:first-child::before,
+.ped-tree-h li:last-child::after { border: 0 none; }
+.ped-tree-h li:last-child::before { border-right: 2px solid var(--border); }
+/* The dog sits on top with no connector above it. */
+.ped-tree-h > li { padding-top: 0; }
+.ped-tree-h > li::before,
+.ped-tree-h > li::after { display: none; }
+/* Collapsed: hide the ancestry below this dog (and its connectors go with it). */
+.ped-tree-h li.collapsed > ul { display: none; }
+
.ped-card {
position: relative;
- display: inline-block;
- padding: 6px 24px 6px 28px;
- margin: 3px 0;
+ width: 140px;
+ box-sizing: border-box;
+ padding: 7px 18px 7px 20px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--surface);
box-shadow: var(--shadow);
+ text-align: center;
}
-.ped-name { font-weight: 600; font-size: 0.95rem; }
+.ped-name { font-weight: 600; font-size: 0.85rem; line-height: 1.2; }
.ped-name.ped-unknown { color: var(--muted); font-weight: 500; font-style: italic; }
-.ped-titles { font-size: 0.72rem; color: var(--accent); margin-top: 1px; }
+.ped-titles { font-size: 0.66rem; color: var(--accent); margin-top: 2px; line-height: 1.2; }
.ped-reg {
- font-size: 0.72rem;
+ font-size: 0.66rem;
color: var(--muted);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
margin-top: 1px;
}
-
-/* lineage spine: children indented under the card */
-.ped-children {
- margin-left: 16px;
- padding-left: 14px;
- border-left: 2px solid var(--border);
-}
-.ped-node.collapsed > .ped-children { display: none; }
-
-/* sire ♂ (blue) above dam ♀ (purple) markers on the parent-role cards */
-.ped-sire > .ped-card { border-left: 3px solid var(--sleep); }
-.ped-dam > .ped-card { border-left: 3px solid var(--training); }
+/* sire ♂ (blue) / dam ♀ (purple) accents on the parent cards */
+.ped-sire > .ped-card { border-top: 3px solid var(--sleep); }
+.ped-dam > .ped-card { border-top: 3px solid var(--training); }
.ped-sire > .ped-card::after,
.ped-dam > .ped-card::after {
position: absolute;
- right: 7px;
- top: 6px;
- font-size: 0.75rem;
+ right: 6px;
+ top: 5px;
+ font-size: 0.72rem;
}
.ped-sire > .ped-card::after { content: "♂"; color: var(--sleep); }
.ped-dam > .ped-card::after { content: "♀"; color: var(--training); }
-/* expand/collapse toggle */
+/* expand/collapse toggle (top-left of the card) */
.ped-toggle {
position: absolute;
- left: 6px;
- top: 50%;
- transform: translateY(-50%);
+ left: 5px;
+ top: 5px;
width: 18px; height: 18px;
padding: 0;
font-size: 0.9rem;