From 4b34fcdb2a0385f2f32bc9709af05c7d2e99cc5b Mon Sep 17 00:00:00 2001 From: s0lray Date: Thu, 26 Mar 2026 18:37:33 -0400 Subject: [PATCH 1/2] Zoom to fill viewport on mobile initial load (THE-119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On screens ≤768px, pre-compute the bounding box of the collapsed tree layout and apply a zoom transform so the root + top-level categories fill the viewport immediately, instead of starting tiny in the center. Also adds a reusable zoomToFill() utility for future reset-view use. Co-Authored-By: Paperclip --- public/js/arf.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/public/js/arf.js b/public/js/arf.js index 4ad1503..d83ae7b 100644 --- a/public/js/arf.js +++ b/public/js/arf.js @@ -83,6 +83,31 @@ d3.json("arf.json").then(function(json) { } root.children.forEach(collapse); + + // On mobile, pre-compute zoom so the collapsed tree fills the viewport. + if (window.innerWidth <= 768) { + // Run tree layout to get final node positions, then compute zoom from data. + tree(root); + root.descendants().forEach(function(d) { d.y = d.depth * 180; }); + var visibleNodes = root.descendants(); + var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; + visibleNodes.forEach(function(d) { + if (d.x < minX) minX = d.x; + if (d.x > maxX) maxX = d.x; + if (d.y < minY) minY = d.y; + if (d.y > maxY) maxY = d.y; + }); + var pad = 60; + var bw = (maxY - minY) || 1; + var bh = (maxX - minX) || 1; + var k = Math.min((svgW - pad * 2) / bw, (svgH - pad * 2) / bh, 3); + var cx = (minY + maxY) / 2; + var cy = (minX + maxX) / 2; + var tx = svgW / 2 - margin[3] - cx * k; + var ty = svgH / 2 - margin[0] - cy * k; + svgEl.call(zoom.transform, d3.zoomIdentity.translate(tx, ty).scale(k)); + } + update(root); initSearch(); }); @@ -235,6 +260,19 @@ function toggle(d) { } } +// Zoom the tree so visible nodes fill the SVG viewport (used on mobile init). +function zoomToFill() { + var bbox = vis.node().getBBox(); + if (!bbox.width || !bbox.height) return; + var pad = 40; + var scaleX = (svgW - pad * 2) / bbox.width; + var scaleY = (svgH - pad * 2) / bbox.height; + var k = Math.min(scaleX, scaleY, 3); + var tx = svgW / 2 - (bbox.x + bbox.width / 2) * k - margin[3]; + var ty = svgH / 2 - (bbox.y + bbox.height / 2) * k - margin[0]; + svgEl.call(zoom.transform, d3.zoomIdentity.translate(tx, ty).scale(k)); +} + // Auto-pan viewport to center on a node after expand/click. function zoomToNode(d) { var currentK = d3.zoomTransform(svgEl.node()).k; From bc2387831dceff28f5f23165d4b155113f0ed38c Mon Sep 17 00:00:00 2001 From: s0lray Date: Thu, 26 Mar 2026 19:08:55 -0400 Subject: [PATCH 2/2] Zoom to fill viewport on mobile initial load (THE-119) Adjust the SVG viewBox on mobile to match the actual rendered aspect ratio instead of the fixed 1280x800 landscape ratio. The previous implementation computed the zoom scale against the viewBox dimensions, but on a tall/narrow mobile screen with min-height: 60vh the SVG element is much taller than the viewBox maps to with xMidYMid meet. By stretching the viewBox to match the real element proportions, the tree's vertical extent now has proper headroom and the initial zoom fills the viewport as expected. Co-Authored-By: Paperclip --- public/js/arf.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/public/js/arf.js b/public/js/arf.js index d83ae7b..3dce43b 100644 --- a/public/js/arf.js +++ b/public/js/arf.js @@ -84,8 +84,16 @@ d3.json("arf.json").then(function(json) { root.children.forEach(collapse); - // On mobile, pre-compute zoom so the collapsed tree fills the viewport. + // On mobile, stretch the viewBox to match the actual rendered aspect ratio + // so the zoom-to-fill calculation uses the real visible area, not the + // fixed 1280x800 letterboxed region. if (window.innerWidth <= 768) { + var rect = svgEl.node().getBoundingClientRect(); + if (rect.width && rect.height) { + svgH = Math.round(svgW * (rect.height / rect.width)); + svgEl.attr("viewBox", "0 0 " + svgW + " " + svgH); + } + // Run tree layout to get final node positions, then compute zoom from data. tree(root); root.descendants().forEach(function(d) { d.y = d.depth * 180; }); @@ -97,7 +105,7 @@ d3.json("arf.json").then(function(json) { if (d.y < minY) minY = d.y; if (d.y > maxY) maxY = d.y; }); - var pad = 60; + var pad = 40; var bw = (maxY - minY) || 1; var bh = (maxX - minX) || 1; var k = Math.min((svgW - pad * 2) / bw, (svgH - pad * 2) / bh, 3);