/** Gesture helpers: Apple projection, rubber-band, velocity samples. */ export function project( vPxPerSec: number, decelerationRate = 0.998, ): number { return ((vPxPerSec / 1000) * decelerationRate) / (1 - decelerationRate); } export function rubberband( overshoot: number, dimension: number, constant = 0.55, ): number { if (dimension <= 0) return 0; const sign = Math.sign(overshoot) || 1; const magnitude = Math.abs(overshoot); return ( (sign * dimension * constant * magnitude) / (dimension + constant * magnitude) ); } export type PointerSample = { t: number; y: number; x: number }; export function velocityFromSamples( samples: PointerSample[], axis: "x" | "y" = "y", ): number { if (samples.length < 2) return 0; const recent = samples.slice(-5); const first = recent[0]; const last = recent[recent.length - 1]; const dt = last.t - first.t; if (dt <= 0) return 0; const delta = axis === "y" ? last.y - first.y : last.x - first.x; return (delta / dt) * 1000; }