From 05fcb1cbfc4c823795612a439765f9b7f171a02c Mon Sep 17 00:00:00 2001 From: Pouyan Jahangiri Date: Sun, 26 Jul 2026 16:45:04 -0700 Subject: [PATCH] Centre the phone's opening world equidistant top and bottom The opening frame reserved a fixed pill band, which over-reserved and pushed the world UP - the margins were not equal. Measure the floating pill's real top edge instead and frame into the space above it, so the world sits dead-centre of the visible area: equal margins left/right and top/bottom on any device (the safe-area no longer throws the centring off). Recenter uses the same measure. --- src/lib/components/bench/LineageCanvas.svelte | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib/components/bench/LineageCanvas.svelte b/src/lib/components/bench/LineageCanvas.svelte index 7d2afa1..c5e7bf5 100644 --- a/src/lib/components/bench/LineageCanvas.svelte +++ b/src/lib/components/bench/LineageCanvas.svelte @@ -191,12 +191,15 @@ } /** - * On a phone the transport pill floats over the bottom of the canvas; reserve its band so a framed - * tree sits ABOVE it rather than with its lowest node tucked underneath. Desktop reserves nothing. + * The vertical room a framed world gets. On a phone the transport pill floats over the bottom of the + * canvas, so we frame into the space from the top of the canvas to the TOP OF THE PILL - measured + * live, so the world ends up centred with EQUAL margins top and bottom whatever the device's + * safe-area does to the pill's height. Off the phone (no floating pill) it is the whole canvas. */ - const PILL_BAND = 120; - function bottomReserve() { - return shell.narrow ? PILL_BAND : 0; + function availableHeight(rect: DOMRect): number { + if (!shell.narrow) return rect.height; + const pill = document.querySelector('aside.rail')?.getBoundingClientRect(); + return pill && pill.top > rect.top ? pill.top - rect.top : rect.height; } /** Frame the whole tree in the viewport - the "recenter" action. */ @@ -207,7 +210,7 @@ return; } const { minX, minY, maxX, maxY } = treeBounds(bench.worlds); - canvas.fitBox(minX, minY, maxX, maxY, rect.width, rect.height - bottomReserve()); + canvas.fitBox(minX, minY, maxX, maxY, rect.width, availableHeight(rect)); } // Wheel must be a NON-PASSIVE listener or preventDefault is ignored and the page scrolls instead @@ -243,9 +246,10 @@ : treeBounds(bench.worlds); const s = Math.min(1, (rect.width - 2 * MARGIN) / NODE_W); // 1 on anything but a narrow phone canvas.scale = s; + // Equal margins: centre horizontally in the canvas, and vertically in the space above the pill - + // so the world sits dead-centre of what you can actually see, not tucked up or under the pill. canvas.tx = (rect.width - (maxX - minX) * s) / 2 - minX * s; - // Centre in the height ABOVE the phone's floating pill, so the opening frame clears it too. - canvas.ty = (rect.height - bottomReserve() - (maxY - minY) * s) / 2 - minY * s; + canvas.ty = (availableHeight(rect) - (maxY - minY) * s) / 2 - minY * s; } onMount(() => {