Merge nucleic/calm-willow-ibis-dozm into main

Mirrored from corpo 1b3dc81.
This commit is contained in:
2026-08-11 19:26:28 -07:00
parent 53d33d07e6
commit 42e78d0d66
+36 -18
View File
@@ -872,12 +872,13 @@ const canonical = new URL('/', Astro.site);
product of how many, not how much scroll each one happens to cost. */
grid-auto-rows: 1fr;
gap: 0.45rem;
/* The card's own top rule continues across this column, and the marks hang from
it — so the composition keeps the unbroken right edge every other rule on the
page is ruled to, and the track reads as part of it rather than as a widget
parked alongside. */
border-top: 1px solid var(--line);
padding-top: 0.6rem;
/* No rule of its own across this column. The card's rule belongs to the card and
travels with it as one card hands over to the next, so a fixed length of the
same hairline sitting beside it stays behind and breaks the line in two for the
length of the transition. The rule stops at the track instead, which also keeps
it clear of the marks when it moves. The pixel is the rule's, given back as
padding so the marks do not shift. */
padding-top: calc(0.6rem + 1px);
}
.is-scroll-stack .technology-track {
@@ -1537,14 +1538,23 @@ const canonical = new URL('/', Astro.site);
already somewhere else. So a step entered at fling speed is entered slowly.
The rule the whole thing reduces to: a gesture may cross any boundary it
reaches at reading pace, and the moment it crosses one at fling speed, what is
left of that fling is held to a sixth of its weight. One flick, one product;
a reader who keeps pushing still gets through, just against the brakes. */
const CATCH_STRENGTH = 0.85;
left of that fling is held to a fifteenth of its weight. One flick, one
product; a reader who keeps pushing still gets through, just against the
brakes. */
const CATCH_STRENGTH = 0.95;
/* The stack answers to a shorter gesture than the page it sits in — three
products across nine hundred pixels means a careful two-finger drag spends
itself inside one of them, which reads as a page that has not noticed. So a
delta is worth a third again as much at reading pace, and that extra is given
back as the gesture speeds up, reaching nothing at fling speed. The one curve
answers both halves of the complaint: slow gestures cover more ground, fast
ones cover less, and it is the same number saying so. */
const SLOW_GAIN = 1.35;
/* Below the first, a gesture is reading and is never touched; above the second
it is a fling. Both are per-frame, so they are speeds a hand can feel — about
720px and 1800px a second — rather than counts of an event nobody controls. */
const SLOW_FRAME_PX = 12;
const FAST_FRAME_PX = 30;
840px and 2040px a second — rather than counts of an event nobody controls. */
const SLOW_FRAME_PX = 14;
const FAST_FRAME_PX = 34;
const GESTURE_GAP_MS = 140;
let wheelAt = -Infinity;
@@ -1556,15 +1566,20 @@ const canonical = new URL('/', Astro.site);
the gesture is still free. */
let gestureCatch = 0;
const beatDrag = () => {
/* Nought at reading pace, one at fling speed. Everything below reads the gesture
through this single number: how much a delta is worth, and how hard a boundary
pushes back once one has been crossed at speed. */
const wheelFast = () =>
Math.min(
1,
Math.max(0, (wheelSpeed - SLOW_FRAME_PX) / (FAST_FRAME_PX - SLOW_FRAME_PX)),
);
const beatDrag = (fast: number) => {
const beat = technologyAtScroll();
if (beat === gestureBeat) return 1;
if (!gestureCatch) {
const fast = Math.min(
1,
Math.max(0, (wheelSpeed - SLOW_FRAME_PX) / (FAST_FRAME_PX - SLOW_FRAME_PX)),
);
/* Reading pace: the gesture is entitled to the beat it has reached, and to
the next one it reaches the same way. Only speed is ever held back. */
if (fast <= 0) {
@@ -1609,7 +1624,10 @@ const canonical = new URL('/', Astro.site);
}
wheelAt = now;
scrollRoot.scrollBy({ top: pixels * beatDrag(), behavior: 'auto' });
const fast = wheelFast();
const gain = 1 + (SLOW_GAIN - 1) * (1 - fast);
scrollRoot.scrollBy({ top: pixels * gain * beatDrag(fast), behavior: 'auto' });
},
{ capture: true, passive: false },
);