From 3b76025e10c9ae4dc06e294e2a4b701536260cf1 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 10 Oct 2026 21:01:06 +0900 Subject: [PATCH] fix: anchor trunk origin to the slash scale --- site/src/scripts/landing.ts | 4 +++- site/tests/browser/landing.spec.ts | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/site/src/scripts/landing.ts b/site/src/scripts/landing.ts index d89ed09..c8b53df 100644 --- a/site/src/scripts/landing.ts +++ b/site/src/scripts/landing.ts @@ -263,7 +263,9 @@ export function initLanding() { const extension = timing.trace(progress(time, flow.grow, 1000)); const converge = timing.travel(progress(time, flow.converge, 560)); const longLength = Math.max(0, innerHeight * .4 - root.offsetHeight * 7.2 / 2 - 8); - const lineStartY = centreY + root.offsetHeight * scale / 2 + 8 * (1 - converge); + // Anchor to the root's local bottom edge at every scale. Only the tip + // extends/converges; the origin has no independent screen-space offset. + const lineStartY = centreY + root.offsetHeight * scale / 2; const finalLength = lastRootBranch.offsetTop + lastRootBranch.offsetHeight / 2 - slot.offsetTop - slot.offsetHeight; const tipY = lineStartY + mix(longLength * extension, finalLength * scale, converge); const handoff = progress(time, flow.handoff, 280); diff --git a/site/tests/browser/landing.spec.ts b/site/tests/browser/landing.spec.ts index 8e4964c..e93e0fb 100644 --- a/site/tests/browser/landing.spec.ts +++ b/site/tests/browser/landing.spec.ts @@ -89,7 +89,7 @@ test('hover streams are masked at the actual slash edges throughout the loop', a await expect(page.locator('.tap-stream-b')).toHaveCSS('opacity', '0'); }); -test('trunk thickness keeps the same proportion to the slash throughout scaling', async ({ page }, testInfo) => { +test('trunk thickness and origin stay proportional to the slash throughout scaling', async ({ page }, testInfo) => { await page.clock.install({ time: new Date('2026-01-01T00:00:00Z') }); await page.clock.pauseAt(new Date('2026-01-01T00:00:01Z')); await armed(page); @@ -102,12 +102,20 @@ test('trunk thickness keeps the same proportion to the slash throughout scaling' const path = document.querySelector('.trunk-line')!; const matrix = path.getScreenCTM()!; const stroke = parseFloat(getComputedStyle(path).strokeWidth) * Math.hypot(matrix.a, matrix.b); - const slashHeight = document.querySelector('.root-shape')!.getBoundingClientRect().height; - return { stroke, slashHeight, opacity: Number(getComputedStyle(path).opacity) }; + const slash = document.querySelector('.root-shape')!.getBoundingClientRect(); + const start = path.getPointAtLength(0).matrixTransform(matrix); + return { + stroke, slashHeight: slash.height, opacity: Number(getComputedStyle(path).opacity), + anchorX: (start.x - slash.left) / slash.width, + gapRatio: (start.y - slash.bottom) / slash.height, + }; }); expect(ink.opacity).toBeGreaterThan(0); // At rest: 1px trunk, 21px of actual slash ink (56/64 of its 24px box). expect(ink.stroke / ink.slashHeight).toBeCloseTo(1 / 21, 5); + // The same local anchor at every scale: centred, 1.5px below 21px of ink. + expect(ink.anchorX).toBeCloseTo(.5, 5); + expect(ink.gapRatio).toBeCloseTo(1 / 14, 5); expect(ink.stroke).toBeLessThan(previousWidth); if ([1520, 2000, 2480].includes(time)) await page.screenshot({ path: testInfo.outputPath(`proportions-${time}.png`) }); previousTime = time; @@ -194,7 +202,7 @@ test('continuous motion overlaps growth, travel and reveal without intermediate expect(Math.abs(extended.dx)).toBeLessThan(.1); expect(Math.abs(extended.axis)).toBeLessThan(.1); expect(extended.dy).toBeGreaterThan(0); - expect(extended.gap).toBeCloseTo(8, 0); + expect(extended.gap).toBeCloseTo(0, 1); expect(extended.bottom).toBeLessThan(page.viewportSize()!.height); await page.screenshot({ path: testInfo.outputPath('04-travelling-trunk.png') }); await page.clock.runFor(920); // 2600ms: moving tree, SVG handoff AND panel onset.