@sweberdev/lagrangian 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spring.ts","../src/decay.ts","../src/loop.ts","../src/reduced-motion.ts","../src/velocity.ts","../src/value.ts","../src/animate.ts","../src/drag.ts","../src/integrate.ts","../src/world.ts"],"sourcesContent":["// Damped harmonic oscillator, solved in closed form: m·x'' + c·x' + k·(x − target) = 0.\n// The exact solution means there is no integration error, no frame-rate dependence, and the\n// position and velocity at any moment are known, which is what makes interruptions seamless.\n\n/** Position and velocity of a motion at one moment. */\nexport interface MotionState {\n value: number;\n velocity: number;\n done: boolean;\n}\n\n/** A motion maps elapsed seconds to a state. Springs, decay and your own functions fit this. */\nexport type Motion = (t: number) => MotionState;\n\nexport interface SpringOptions {\n /** Spring constant k. Higher is snappier. Default 170. */\n stiffness?: number;\n /** Damping coefficient c. Lower bounces more. Default 26. */\n damping?: number;\n /** Mass m. Heavier is slower and carries more momentum. Default 1. */\n mass?: number;\n /**\n * Perceptual duration in seconds, used together with `bounce` instead of\n * stiffness and damping (as in SwiftUI). It is the period of the undamped spring.\n */\n duration?: number;\n /** -1 to 1. 0 is critically damped, 0.3 is a gentle bounce, negative values are overdamped. */\n bounce?: number;\n /** The motion counts as finished once it is this close to the target... */\n restDelta?: number;\n /** ...and slower than this (units per second). */\n restSpeed?: number;\n}\n\nexport interface SpringParams {\n stiffness: number;\n damping: number;\n mass: number;\n}\n\n/** Resolves stiffness, damping and mass, also from `duration` and `bounce`. */\nexport function springParams(options: SpringOptions = {}): SpringParams {\n const mass = options.mass ?? 1;\n if (options.duration !== undefined || options.bounce !== undefined) {\n const duration = Math.max(options.duration ?? 0.5, 0.01);\n const bounce = Math.min(Math.max(options.bounce ?? 0, -0.99), 1);\n const omega = (2 * Math.PI) / duration;\n const zeta = bounce >= 0 ? 1 - bounce : 1 / (1 + bounce);\n return { stiffness: mass * omega * omega, damping: 2 * mass * zeta * omega, mass };\n }\n return { stiffness: options.stiffness ?? 170, damping: options.damping ?? 26, mass };\n}\n\n/** Damping ratio: below 1 the spring overshoots, 1 is critical, above 1 it creeps. */\nexport function dampingRatio(options: SpringOptions = {}): number {\n const { stiffness, damping, mass } = springParams(options);\n return damping / (2 * Math.sqrt(stiffness * mass));\n}\n\nexport function restThresholds(from: number, to: number, options: SpringOptions = {}) {\n const span = Math.max(Math.abs(to - from), Math.abs(to) * 0.01, 1e-3);\n const restDelta = options.restDelta ?? Math.min(Math.max(span * 1e-3, 1e-4), 0.5);\n return { restDelta, restSpeed: options.restSpeed ?? restDelta * 10 };\n}\n\n/**\n * A spring from `from` to `to`, starting with `velocity` (units per second).\n * Returns the exact state for any elapsed time.\n */\nexport function spring(\n from: number,\n to: number,\n velocity = 0,\n options: SpringOptions = {},\n): Motion {\n const { stiffness: k, damping: c, mass: m } = springParams(options);\n const { restDelta, restSpeed } = restThresholds(from, to, options);\n const d0 = from - to;\n const v0 = velocity;\n const w0 = Math.sqrt(k / m);\n const zeta = c / (2 * Math.sqrt(k * m));\n\n let displacement: (t: number) => [number, number];\n if (zeta < 1 - 1e-6) {\n const a = zeta * w0;\n const wd = w0 * Math.sqrt(1 - zeta * zeta);\n const B = (v0 + a * d0) / wd;\n displacement = (t) => {\n const e = Math.exp(-a * t);\n const cos = Math.cos(wd * t);\n const sin = Math.sin(wd * t);\n return [e * (d0 * cos + B * sin), e * ((B * wd - a * d0) * cos - (a * B + d0 * wd) * sin)];\n };\n } else if (zeta <= 1 + 1e-6) {\n const B = v0 + w0 * d0;\n displacement = (t) => {\n const e = Math.exp(-w0 * t);\n return [e * (d0 + B * t), e * (v0 - w0 * B * t)];\n };\n } else {\n const s = Math.sqrt(zeta * zeta - 1);\n const r1 = -w0 * (zeta - s);\n const r2 = -w0 * (zeta + s);\n const c1 = (v0 - r2 * d0) / (r1 - r2);\n const c2 = d0 - c1;\n displacement = (t) => {\n const e1 = Math.exp(r1 * t);\n const e2 = Math.exp(r2 * t);\n return [c1 * e1 + c2 * e2, c1 * r1 * e1 + c2 * r2 * e2];\n };\n }\n\n return (t) => {\n const [x, v] = displacement(Math.max(t, 0));\n if (Math.abs(x) <= restDelta && Math.abs(v) <= restSpeed) {\n return { value: to, velocity: 0, done: true };\n }\n return { value: to + x, velocity: v, done: false };\n };\n}\n\n/**\n * Seconds until a motion settles, sampled at 1 ms. Returns Infinity when it does not settle\n * within `limit` seconds (an undamped spring never does).\n */\nexport function settleTime(motion: Motion, limit = 30): number {\n for (let t = 0; t <= limit; t += 1 / 1000) {\n if (motion(t).done) return t;\n }\n return Number.POSITIVE_INFINITY;\n}\n\n/** The value a motion comes to rest at, or its value after `limit` seconds. */\nexport function restValue(motion: Motion, limit = 30): number {\n for (let t = 0; t <= limit; t += 1 / 60) {\n const state = motion(t);\n if (state.done) return state.value;\n }\n return motion(limit).value;\n}\n","// Inertia: a moving object slowed by friction proportional to its speed (like iOS scrolling).\n// v(t) = v0·e^(−t/τ), x(t) = x0 + v0·τ·(1 − e^(−t/τ)), so it comes to rest at x0 + v0·τ.\n// With bounds it bounces off the edge on a spring; with snap points it lands exactly on one.\n\nimport { type Motion, type SpringOptions, spring } from \"./spring\";\n\nexport type Snap = readonly number[] | ((restingPoint: number) => number);\n\nexport interface DecayOptions {\n /** Starting velocity in units per second. Defaults to the value's current velocity. */\n velocity?: number;\n /**\n * Fraction of speed kept per millisecond. 0.998 is the iOS \"normal\" scroll feel (default),\n * 0.99 is \"fast\" and stops much sooner.\n */\n deceleration?: number;\n /** Lower bound. Crossing it hands over to a spring that settles on the edge. */\n min?: number;\n /** Upper bound. */\n max?: number;\n /** Snap points, or a function that maps the natural resting point to a snapped one. */\n snap?: Snap;\n /** Spring used at the edges and when a snap point needs a spring. */\n bounce?: SpringOptions;\n /** The motion ends once it is this close to its resting point. Default 0.5. */\n restDelta?: number;\n}\n\nconst EDGE_SPRING: SpringOptions = { stiffness: 400, damping: 40 };\n\nexport function timeConstant(deceleration = 0.998): number {\n const rate = Math.min(Math.max(deceleration, 0.5), 0.99999);\n return -1 / (1000 * Math.log(rate));\n}\n\n/** Where a throw with this velocity comes to rest without bounds or snap points. */\nexport function projectRest(from: number, velocity: number, deceleration = 0.998): number {\n return from + velocity * timeConstant(deceleration);\n}\n\n/** Nearest snap point to `value`. */\nexport function nearest(points: readonly number[], value: number): number {\n let best = value;\n let distance = Number.POSITIVE_INFINITY;\n for (const point of points) {\n const d = Math.abs(point - value);\n if (d < distance) {\n distance = d;\n best = point;\n }\n }\n return best;\n}\n\nfunction resolveSnap(snap: Snap, value: number): number {\n return typeof snap === \"function\" ? snap(value) : nearest(snap, value);\n}\n\nfunction friction(from: number, v0: number, tau: number, end: number, restDelta: number): Motion {\n return (t) => {\n const e = Math.exp(-Math.max(t, 0) / tau);\n const value = from + v0 * tau * (1 - e);\n const velocity = v0 * e;\n if (Math.abs(end - value) <= restDelta) return { value: end, velocity: 0, done: true };\n return { value, velocity, done: false };\n };\n}\n\n/** A throw with friction from `from`, optionally with bounds and snap points. */\nexport function decay(from: number, options: DecayOptions = {}): Motion {\n const v0 = options.velocity ?? 0;\n const tau = timeConstant(options.deceleration);\n const restDelta = options.restDelta ?? 0.5;\n const min = options.min ?? Number.NEGATIVE_INFINITY;\n const max = options.max ?? Number.POSITIVE_INFINITY;\n const edge = { ...EDGE_SPRING, restDelta, ...options.bounce };\n const clamp = (x: number) => Math.min(Math.max(x, min), max);\n\n // Already outside the bounds (e.g. let go while rubber-banding): spring back to the edge.\n if (from < min || from > max) return spring(from, clamp(from), v0, edge);\n\n const natural = from + v0 * tau;\n\n if (options.snap !== undefined) {\n const target = clamp(resolveSnap(options.snap, natural));\n const distance = target - from;\n // Stretch or shorten the friction so the throw ends exactly on the snap point, as long as\n // that still feels like the same throw. Otherwise a spring carries the velocity there.\n const fitted = v0 !== 0 ? distance / v0 : -1;\n if (fitted > tau / 4 && fitted < tau * 4) return friction(from, v0, fitted, target, restDelta);\n return spring(from, target, v0, { ...edge, ...options.bounce });\n }\n\n if (natural >= min && natural <= max) return friction(from, v0, tau, natural, restDelta);\n\n // The throw leaves the bounds: glide until the edge, then let a spring catch it there.\n const bound = natural < min ? min : max;\n const crossing = -tau * Math.log(1 - (bound - from) / (v0 * tau));\n const glide = friction(from, v0, tau, natural, 0);\n const vAtEdge = v0 * Math.exp(-crossing / tau);\n const catcher = spring(bound, bound, vAtEdge, edge);\n return (t) => (t < crossing ? glide(t) : catcher(t - crossing));\n}\n\n/**\n * Elastic overscroll, as on iOS: the further past the edge, the harder it pulls back.\n * `distance` is how far past the edge, `dimension` the size of the visible area.\n */\nexport function rubberband(distance: number, dimension: number, constant = 0.55): number {\n if (dimension <= 0 || constant <= 0) return 0;\n const sign = Math.sign(distance);\n const d = Math.abs(distance);\n return sign * (1 - 1 / ((d * constant) / dimension + 1)) * dimension;\n}\n\n/** Applies `rubberband` to a value outside [min, max]. Values inside stay unchanged. */\nexport function rubberClamp(\n value: number,\n min: number,\n max: number,\n dimension: number,\n constant = 0.55,\n): number {\n if (value < min) return min + rubberband(value - min, dimension, constant);\n if (value > max) return max + rubberband(value - max, dimension, constant);\n return value;\n}\n","// One shared frame loop for every running value and world.\n// Tasks get the frame time step in seconds. A long pause (hidden tab, debugger) is clamped so a\n// simulation never takes one giant step and objects never fly through walls.\n\nexport type Task = (dt: number, now: number) => void;\n\nconst MAX_DT = 1 / 20;\nconst tasks = new Set<Task>();\nlet scheduled = false;\nlet manual = false;\nlet last = -1;\nlet clock = 0;\n\nconst hasRaf = () => typeof requestAnimationFrame === \"function\";\n\nfunction schedule(): void {\n if (scheduled || manual || tasks.size === 0) return;\n scheduled = true;\n if (hasRaf()) requestAnimationFrame(frame);\n else setTimeout(() => frame(now()), 16);\n}\n\nfunction frame(time: number): void {\n scheduled = false;\n if (manual) return;\n const dt = last < 0 ? 1 / 60 : Math.min(Math.max((time - last) / 1000, 0), MAX_DT);\n last = time;\n run(dt, time);\n if (tasks.size > 0) schedule();\n else last = -1;\n}\n\nfunction run(dt: number, time: number): void {\n for (const task of [...tasks]) {\n if (tasks.has(task)) task(dt, time);\n }\n}\n\n/** Current time in milliseconds, from the manual clock while the loop is in manual mode. */\nexport function now(): number {\n if (manual) return clock;\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\nexport const loop = {\n /** Runs `task` on every frame until the returned function is called. */\n add(task: Task): () => void {\n tasks.add(task);\n schedule();\n return () => {\n tasks.delete(task);\n };\n },\n /**\n * Switches to manual stepping (for tests, server rendering or video export) or back to\n * requestAnimationFrame.\n */\n manual(on = true): void {\n manual = on;\n last = -1;\n if (!on) schedule();\n },\n /** Advances the manual clock by `ms` and runs one frame. Only has an effect in manual mode. */\n step(ms = 1000 / 60): void {\n if (!manual) return;\n clock += ms;\n run(Math.min(ms / 1000, MAX_DT), clock);\n },\n /** Number of running tasks. */\n get size(): number {\n return tasks.size;\n },\n};\n","export type ReducedMotion = \"user\" | \"always\" | \"never\";\n\nlet mode: ReducedMotion = \"user\";\n\n/**\n * \"user\" (default) follows the prefers-reduced-motion setting, \"always\" skips animated\n * transitions everywhere, \"never\" ignores the setting. Direct manipulation (dragging, the\n * world simulation) always follows the pointer; only automatic motion is skipped.\n */\nexport function setReducedMotion(value: ReducedMotion): void {\n mode = value;\n}\n\nexport function reducedMotion(): boolean {\n if (mode !== \"user\") return mode === \"always\";\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n","// Estimates velocity from recent samples with a least-squares line over the last 100 ms,\n// which is far less jittery than the difference of the last two pointer events.\n\nconst WINDOW = 100;\nconst STALE = 60;\n\nexport class VelocityTracker {\n #samples: { t: number; x: number }[] = [];\n\n /** Adds a sample: `time` in milliseconds, `value` in any unit. */\n add(time: number, value: number): void {\n const samples = this.#samples;\n const last = samples[samples.length - 1];\n if (last && time <= last.t) {\n last.x = value;\n return;\n }\n samples.push({ t: time, x: value });\n while (samples.length > 2 && (samples[0] as { t: number }).t < time - WINDOW) samples.shift();\n }\n\n /** Velocity in units per second at `time` (ms). 0 when the pointer has been resting. */\n velocity(time?: number): number {\n const samples = this.#samples;\n const last = samples[samples.length - 1];\n if (!last || samples.length < 2) return 0;\n if (time !== undefined && time - last.t > STALE) return 0;\n const recent = samples.filter((s) => s.t >= last.t - WINDOW);\n if (recent.length < 2) return 0;\n let mt = 0;\n let mx = 0;\n for (const s of recent) {\n mt += s.t;\n mx += s.x;\n }\n mt /= recent.length;\n mx /= recent.length;\n let num = 0;\n let den = 0;\n for (const s of recent) {\n num += (s.t - mt) * (s.x - mx);\n den += (s.t - mt) * (s.t - mt);\n }\n return den === 0 ? 0 : (num / den) * 1000;\n }\n\n reset(): void {\n this.#samples = [];\n }\n}\n","// A number with momentum. It always knows its velocity, so a new animation, a throw or a drag\n// can take over mid-flight without a jolt: the physics continues from the current state.\n\nimport { type DecayOptions, decay } from \"./decay\";\nimport { loop, now } from \"./loop\";\nimport { reducedMotion } from \"./reduced-motion\";\nimport { type Motion, restValue, type SpringOptions, spring } from \"./spring\";\nimport { VelocityTracker } from \"./velocity\";\n\n/** A running animation. Await `finished` to know whether it completed. */\nexport interface Animation {\n /** Resolves with true when the motion came to rest, false when it was interrupted. */\n readonly finished: Promise<boolean>;\n stop(): void;\n}\n\nexport type Listener = (value: number, velocity: number) => void;\n\nexport function settled(value: boolean): Animation {\n const finished = Promise.resolve(value);\n return { finished, stop() {} };\n}\n\nexport class PhysicsValue {\n #value: number;\n #velocity = 0;\n #tracked = false;\n #listeners = new Set<Listener>();\n #tracker = new VelocityTracker();\n #cancel: (() => void) | null = null;\n #resolve: ((completed: boolean) => void) | null = null;\n\n constructor(initial = 0) {\n this.#value = initial;\n }\n\n get(): number {\n return this.#value;\n }\n\n /** Units per second, from the running motion or from recent `set` calls. */\n get velocity(): number {\n return this.#tracked ? this.#tracker.velocity(now()) : this.#velocity;\n }\n\n get animating(): boolean {\n return this.#cancel !== null;\n }\n\n /** Sets the value directly (e.g. while dragging). Stops any motion and tracks the velocity. */\n set(value: number): void {\n this.#halt(false);\n this.#tracked = true;\n this.#tracker.add(now(), value);\n this.#velocity = this.#tracker.velocity();\n this.#emit(value);\n }\n\n /** Sets the value and drops all momentum. */\n jump(value: number): void {\n this.#halt(false);\n this.#tracker.reset();\n this.#tracked = false;\n this.#velocity = 0;\n this.#emit(value);\n }\n\n /** Springs to `target`, starting from the current position and velocity. */\n to(target: number, options: SpringOptions & { velocity?: number } = {}): Animation {\n const from = this.#value;\n return this.start(spring(from, target, options.velocity ?? this.velocity, options));\n }\n\n /** Throws the value with friction, optionally into bounds or onto snap points. */\n throw(options: DecayOptions = {}): Animation {\n return this.start(\n decay(this.#value, { ...options, velocity: options.velocity ?? this.velocity }),\n );\n }\n\n /** Runs any motion. Elapsed time starts at 0 on the next frame. */\n start(motion: Motion): Animation {\n this.#halt(false);\n if (reducedMotion()) {\n this.jump(restValue(motion));\n return settled(true);\n }\n let elapsed = 0;\n let resolve!: (completed: boolean) => void;\n const finished = new Promise<boolean>((r) => {\n resolve = r;\n });\n this.#resolve = resolve;\n const remove = loop.add((dt) => {\n elapsed += dt;\n const state = motion(elapsed);\n this.#velocity = state.velocity;\n this.#emit(state.value);\n if (state.done) this.#halt(true);\n });\n this.#cancel = remove;\n this.#tracked = false;\n this.#tracker.reset();\n return {\n finished,\n stop: () => {\n if (this.#resolve === resolve) this.#halt(false);\n },\n };\n }\n\n /** Stops where it is. The velocity is kept, so a following `to` or `throw` continues it. */\n stop(): void {\n this.#halt(false);\n }\n\n /** Calls `listener` on every change. Returns the unsubscribe function. */\n on(listener: Listener): () => void {\n this.#listeners.add(listener);\n return () => {\n this.#listeners.delete(listener);\n };\n }\n\n #emit(value: number): void {\n this.#value = value;\n for (const listener of [...this.#listeners]) listener(value, this.#velocity);\n }\n\n #halt(completed: boolean): void {\n const cancel = this.#cancel;\n const resolve = this.#resolve;\n this.#cancel = null;\n this.#resolve = null;\n if (cancel) {\n cancel();\n // Interrupted motions keep their momentum for whatever comes next.\n if (completed) this.#velocity = 0;\n }\n resolve?.(completed);\n }\n}\n\n/** Creates a value with momentum. */\nexport function value(initial = 0): PhysicsValue {\n return new PhysicsValue(initial);\n}\n","// Spring elements to new values. Every element keeps one physical value per property, so a new\n// call while the old one is still moving continues with the current velocity instead of\n// restarting.\n\nimport type { SpringOptions } from \"./spring\";\nimport { type Animation, PhysicsValue } from \"./value\";\n\nexport type TransformProp = \"x\" | \"y\" | \"z\" | \"scale\" | \"scaleX\" | \"scaleY\" | \"rotate\";\nexport type Prop = TransformProp | \"opacity\" | `--${string}`;\nexport type Props = Partial<Record<Prop, number>>;\n\nexport interface AnimateOptions extends SpringOptions {\n /** Starting velocity per property, e.g. from a gesture. */\n velocity?: Props;\n /** Seconds to wait before starting. */\n delay?: number;\n /** Extra delay per element when animating several, in seconds. */\n stagger?: number;\n}\n\ntype Target = Element | Iterable<Element> | ArrayLike<Element> | string;\n\nconst TRANSFORMS: readonly TransformProp[] = [\"x\", \"y\", \"z\", \"rotate\", \"scale\", \"scaleX\", \"scaleY\"];\nconst registry = new WeakMap<Element, Map<Prop, PhysicsValue>>();\n\nfunction initial(element: Element, prop: Prop): number {\n if (prop === \"scale\" || prop === \"scaleX\" || prop === \"scaleY\") return 1;\n if (prop === \"opacity\") {\n const style = (element as HTMLElement).style;\n const inline = style ? Number.parseFloat(style.opacity) : Number.NaN;\n if (!Number.isNaN(inline)) return inline;\n if (typeof getComputedStyle !== \"function\") return 1;\n const computed = Number.parseFloat(getComputedStyle(element).opacity);\n return Number.isNaN(computed) ? 1 : computed;\n }\n if (prop.startsWith(\"--\") && typeof getComputedStyle === \"function\") {\n const computed = Number.parseFloat(getComputedStyle(element).getPropertyValue(prop));\n return Number.isNaN(computed) ? 0 : computed;\n }\n return 0;\n}\n\nfunction render(element: Element, values: Map<Prop, PhysicsValue>): void {\n const style = (element as HTMLElement | SVGElement).style;\n if (!style) return;\n const get = (prop: TransformProp) => values.get(prop)?.get();\n const parts: string[] = [];\n const x = get(\"x\");\n const y = get(\"y\");\n const z = get(\"z\");\n if (x !== undefined || y !== undefined || z !== undefined) {\n parts.push(`translate3d(${x ?? 0}px, ${y ?? 0}px, ${z ?? 0}px)`);\n }\n const rotate = get(\"rotate\");\n if (rotate !== undefined) parts.push(`rotate(${rotate}deg)`);\n const scale = get(\"scale\");\n if (scale !== undefined) parts.push(`scale(${scale})`);\n const scaleX = get(\"scaleX\");\n if (scaleX !== undefined) parts.push(`scaleX(${scaleX})`);\n const scaleY = get(\"scaleY\");\n if (scaleY !== undefined) parts.push(`scaleY(${scaleY})`);\n if (TRANSFORMS.some((prop) => values.has(prop))) style.transform = parts.join(\" \");\n const opacity = values.get(\"opacity\");\n if (opacity) style.opacity = String(Math.min(Math.max(opacity.get(), 0), 1));\n for (const [prop, value] of values) {\n if (prop.startsWith(\"--\")) style.setProperty(prop, String(value.get()));\n }\n}\n\n/**\n * The physical value behind one property of an element. Use it to read the live velocity,\n * to drive the element from a gesture with `set`, or to listen to changes.\n */\nexport function getValue(element: Element, prop: Prop): PhysicsValue {\n let values = registry.get(element);\n if (!values) {\n values = new Map();\n registry.set(element, values);\n }\n let v = values.get(prop);\n if (!v) {\n const map = values;\n v = new PhysicsValue(initial(element, prop));\n let queued = false;\n v.on(() => {\n // Several properties change in the same frame: write the style once.\n if (queued) return;\n queued = true;\n queueMicrotask(() => {\n queued = false;\n render(element, map);\n });\n });\n values.set(prop, v);\n }\n return v;\n}\n\nfunction elements(target: Target): Element[] {\n if (typeof target === \"string\") {\n return typeof document === \"undefined\" ? [] : [...document.querySelectorAll(target)];\n }\n if (typeof Element !== \"undefined\" && target instanceof Element) return [target];\n return Array.from(target as ArrayLike<Element>);\n}\n\nfunction group(animations: Animation[]): Animation {\n const finished = Promise.all(animations.map((a) => a.finished)).then((all) => all.every(Boolean));\n return {\n finished,\n stop: () => {\n for (const a of animations) a.stop();\n },\n };\n}\n\nfunction delayed(seconds: number, start: () => Animation): Animation {\n if (seconds <= 0) return start();\n let inner: Animation | null = null;\n let stopped = false;\n let resolve!: (value: boolean) => void;\n const finished = new Promise<boolean>((r) => {\n resolve = r;\n });\n const timer = setTimeout(() => {\n if (stopped) return;\n inner = start();\n inner.finished.then(resolve);\n }, seconds * 1000);\n return {\n finished,\n stop: () => {\n stopped = true;\n clearTimeout(timer);\n if (inner) inner.stop();\n else resolve(false);\n },\n };\n}\n\n/**\n * Springs one or more elements to the given values.\n *\n * ```ts\n * animate(\".card\", { x: 120, rotate: 4, opacity: 1 }, { duration: 0.6, bounce: 0.3 })\n * ```\n */\nexport function animate(target: Target, props: Props, options: AnimateOptions = {}): Animation {\n const { velocity, delay = 0, stagger = 0, ...spring } = options;\n const animations = elements(target).map((element, index) =>\n delayed(delay + index * stagger, () =>\n group(\n (Object.keys(props) as Prop[]).map((prop) =>\n getValue(element, prop).to(props[prop] as number, {\n ...spring,\n ...(velocity?.[prop] !== undefined ? { velocity: velocity[prop] } : {}),\n }),\n ),\n ),\n ),\n );\n return group(animations);\n}\n\n/** Sets values immediately, without animation, and drops their momentum. */\nexport function set(target: Target, props: Props): void {\n for (const element of elements(target)) {\n for (const prop of Object.keys(props) as Prop[]) {\n getValue(element, prop).jump(props[prop] as number);\n }\n }\n}\n","// Drag an element with the pointer and throw it: rubber-band past the bounds, glide with\n// friction on release, settle on snap points. The element moves via its x/y physical values,\n// so `animate()` and a drag can hand over to each other at any moment.\n\nimport { getValue } from \"./animate\";\nimport { rubberClamp, type Snap } from \"./decay\";\nimport type { SpringOptions } from \"./spring\";\nimport type { PhysicsValue } from \"./value\";\n\nexport interface Bounds {\n left?: number;\n right?: number;\n top?: number;\n bottom?: number;\n}\n\nexport interface DragInfo {\n x: number;\n y: number;\n velocityX: number;\n velocityY: number;\n}\n\nexport interface DraggableOptions {\n /** Restrict movement to one axis. Default \"both\". */\n axis?: \"x\" | \"y\" | \"both\";\n /**\n * Movement limits in pixels relative to the start position, or an element the dragged\n * element has to stay inside.\n */\n bounds?: Bounds | Element | (() => Bounds | Element);\n /** Resistance past the bounds, 0 to 1 (0.55 like iOS), or false to stop hard at the edge. */\n rubberband?: number | false;\n /** Keep moving with friction after release. Default true. */\n inertia?: boolean;\n /** Fraction of speed kept per millisecond during the throw. Default 0.998. */\n deceleration?: number;\n /** Snap points per axis, in pixels relative to the start position. */\n snap?: { x?: Snap; y?: Snap };\n /** Spring for snapping and for bouncing off the bounds. */\n spring?: SpringOptions;\n onStart?: (info: DragInfo) => void;\n onMove?: (info: DragInfo) => void;\n /** Called on release with the throw velocity. */\n onEnd?: (info: DragInfo) => void;\n}\n\nexport interface Draggable {\n readonly x: PhysicsValue;\n readonly y: PhysicsValue;\n readonly dragging: boolean;\n destroy(): void;\n}\n\ntype Range = [number, number];\nconst FREE: Range = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];\n\nfunction ranges(element: Element, x: number, y: number, bounds: DraggableOptions[\"bounds\"]) {\n const b = typeof bounds === \"function\" ? bounds() : bounds;\n if (!b) return { x: FREE, y: FREE };\n if (typeof Element !== \"undefined\" && b instanceof Element) {\n const outer = b.getBoundingClientRect();\n const inner = element.getBoundingClientRect();\n // Origin of the element without its current translation.\n const left = inner.left - x;\n const top = inner.top - y;\n return {\n x: [outer.left - left, outer.right - left - inner.width] as Range,\n y: [outer.top - top, outer.bottom - top - inner.height] as Range,\n };\n }\n const box = b as Bounds;\n return {\n x: [box.left ?? FREE[0], box.right ?? FREE[1]] as Range,\n y: [box.top ?? FREE[0], box.bottom ?? FREE[1]] as Range,\n };\n}\n\n/** Makes an element draggable and throwable. */\nexport function draggable(element: HTMLElement, options: DraggableOptions = {}): Draggable {\n const axis = options.axis ?? \"both\";\n const x = getValue(element, \"x\");\n const y = getValue(element, \"y\");\n const moveX = axis !== \"y\";\n const moveY = axis !== \"x\";\n let pointer: number | null = null;\n let start = { px: 0, py: 0, x: 0, y: 0 };\n let range = { x: FREE, y: FREE };\n\n const info = (): DragInfo => ({\n x: x.get(),\n y: y.get(),\n velocityX: x.velocity,\n velocityY: y.velocity,\n });\n\n const previousTouchAction = element.style.touchAction;\n element.style.touchAction = axis === \"x\" ? \"pan-y\" : axis === \"y\" ? \"pan-x\" : \"none\";\n\n const place = (value: number, [min, max]: Range, dimension: number) => {\n if (options.rubberband === false) return Math.min(Math.max(value, min), max);\n return rubberClamp(value, min, max, dimension, options.rubberband ?? 0.55);\n };\n\n const down = (event: PointerEvent) => {\n if (pointer !== null || (event.pointerType === \"mouse\" && event.button !== 0)) return;\n pointer = event.pointerId;\n element.setPointerCapture?.(event.pointerId);\n x.stop();\n y.stop();\n start = { px: event.clientX, py: event.clientY, x: x.get(), y: y.get() };\n range = ranges(element, start.x, start.y, options.bounds);\n // Grabbing stops the momentum; the samples from here on give the throw velocity.\n x.jump(start.x);\n y.jump(start.y);\n if (moveX) x.set(start.x);\n if (moveY) y.set(start.y);\n options.onStart?.(info());\n };\n\n const move = (event: PointerEvent) => {\n if (event.pointerId !== pointer) return;\n const viewport =\n typeof window !== \"undefined\" ? window : { innerWidth: 1000, innerHeight: 1000 };\n if (moveX) x.set(place(start.x + event.clientX - start.px, range.x, viewport.innerWidth));\n if (moveY) y.set(place(start.y + event.clientY - start.py, range.y, viewport.innerHeight));\n options.onMove?.(info());\n };\n\n const release = (value: PhysicsValue, [min, max]: Range, snap: Snap | undefined) => {\n const throwOptions = {\n min,\n max,\n bounce: options.spring,\n ...(options.deceleration !== undefined ? { deceleration: options.deceleration } : {}),\n ...(snap !== undefined ? { snap } : {}),\n };\n if (options.inertia !== false) {\n value.throw(throwOptions);\n return;\n }\n // Without inertia only snap points and bounds move the element after release.\n value.throw({ ...throwOptions, velocity: 0 });\n };\n\n const up = (event: PointerEvent) => {\n if (event.pointerId !== pointer) return;\n pointer = null;\n element.releasePointerCapture?.(event.pointerId);\n options.onEnd?.(info());\n if (moveX) release(x, range.x, options.snap?.x);\n if (moveY) release(y, range.y, options.snap?.y);\n };\n\n element.addEventListener(\"pointerdown\", down);\n element.addEventListener(\"pointermove\", move);\n element.addEventListener(\"pointerup\", up);\n element.addEventListener(\"pointercancel\", up);\n\n return {\n x,\n y,\n get dragging() {\n return pointer !== null;\n },\n destroy() {\n element.removeEventListener(\"pointerdown\", down);\n element.removeEventListener(\"pointermove\", move);\n element.removeEventListener(\"pointerup\", up);\n element.removeEventListener(\"pointercancel\", up);\n element.style.touchAction = previousTouchAction;\n },\n };\n}\n","// Runge-Kutta 4 for your own equations of motion, e.g. derived from a Lagrangian.\n// Write the system as first-order derivatives of a state vector and step it.\n\n/** Returns d(state)/dt for a state at time t. */\nexport type Derivatives = (state: readonly number[], t: number) => number[];\n\n/** One classic fourth-order Runge-Kutta step of size `dt`. */\nexport function rk4(state: readonly number[], t: number, dt: number, f: Derivatives): number[] {\n const n = state.length;\n const add = (k: readonly number[], h: number) => state.map((s, i) => s + (k[i] ?? 0) * h);\n const k1 = f(state, t);\n const k2 = f(add(k1, dt / 2), t + dt / 2);\n const k3 = f(add(k2, dt / 2), t + dt / 2);\n const k4 = f(add(k3, dt), t + dt);\n const next = new Array<number>(n);\n for (let i = 0; i < n; i++) {\n next[i] =\n (state[i] ?? 0) +\n (dt / 6) * ((k1[i] ?? 0) + 2 * (k2[i] ?? 0) + 2 * (k3[i] ?? 0) + (k4[i] ?? 0));\n }\n return next;\n}\n\nexport interface System {\n /** Current state vector. */\n readonly state: readonly number[];\n /** Simulated time in seconds. */\n readonly time: number;\n /** Advances by `seconds` in fixed steps, so results do not depend on the frame rate. */\n advance(seconds: number): readonly number[];\n /** Replaces the state, e.g. after the user grabbed something. */\n reset(state: readonly number[]): void;\n}\n\n/** A system of ODEs stepped with RK4 at a fixed step (default 1/240 s). */\nexport function system(f: Derivatives, initial: readonly number[], step = 1 / 240): System {\n let state = [...initial];\n let time = 0;\n let pending = 0;\n return {\n get state() {\n return state;\n },\n get time() {\n return time;\n },\n advance(seconds) {\n pending += seconds;\n while (pending >= step) {\n state = rk4(state, time, step, f);\n time += step;\n pending -= step;\n }\n return state;\n },\n reset(next) {\n state = [...next];\n pending = 0;\n },\n };\n}\n","// A small 2D physics world for UI: round bodies with gravity, air drag, collisions with\n// restitution, Coulomb friction that makes them roll, springs between bodies and a pointer\n// joint for grabbing and throwing. Fixed time step, so it behaves the same at 60 and 144 Hz.\n\nimport { loop } from \"./loop\";\n\nexport interface Vector {\n x: number;\n y: number;\n}\n\nexport interface Rect {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\nexport interface BodyOptions {\n /** Centre in pixels. */\n x: number;\n y: number;\n radius: number;\n /** Mass in kg. Defaults to the area times `density`. */\n mass?: number;\n /** kg per square meter, used when `mass` is not given. Default 1. */\n density?: number;\n /** Bounciness, 0 (clay) to 1 (perfectly elastic). Default 0.5. */\n restitution?: number;\n /** Coulomb friction coefficient. Default 0.4. */\n friction?: number;\n /** Fixed bodies never move but others collide with them. */\n fixed?: boolean;\n /** Initial velocity in pixels per second. */\n vx?: number;\n vy?: number;\n /** Angle in radians and angular velocity in radians per second. */\n angle?: number;\n spin?: number;\n /** An element positioned at the top left of the world that follows this body. */\n element?: HTMLElement | SVGElement | null;\n}\n\nexport interface Body {\n x: number;\n y: number;\n vx: number;\n vy: number;\n angle: number;\n spin: number;\n readonly radius: number;\n readonly mass: number;\n restitution: number;\n friction: number;\n readonly fixed: boolean;\n element: HTMLElement | SVGElement | null;\n /** @internal */ invMass: number;\n /** @internal */ invInertia: number;\n}\n\nexport interface LinkOptions {\n /** Rest length in pixels. Defaults to the current distance. */\n length?: number;\n /** Oscillation frequency in Hz. Higher is stiffer. Default 4. */\n frequency?: number;\n /** Damping ratio, 0 to 1. Default 0.3. */\n damping?: number;\n}\n\nexport interface Link {\n a: Body;\n b: Body;\n length: number;\n frequency: number;\n damping: number;\n}\n\nexport interface WorldOptions {\n /** Gravity in m/s². Default { x: 0, y: 9.81 }. */\n gravity?: Vector;\n /** Scale between the simulation (meters) and the screen. Default 500 pixels per meter. */\n pixelsPerMeter?: number;\n /** Linear air drag per second. Default 0.05. */\n airDrag?: number;\n /**\n * Rolling resistance coefficient: a braking torque proportional to the contact force that\n * makes rolling bodies come to a stop and keeps resting piles still. Default 0.05.\n */\n rollingResistance?: number;\n /** Walls in pixels, or an element whose content box becomes the walls. */\n bounds?: Rect | HTMLElement | null;\n /** Simulation steps per second. Default 240. */\n rate?: number;\n /** Collision solver passes per step. More gives steadier stacks. Default 4. */\n iterations?: number;\n /** Called after every frame, e.g. to draw on a canvas. */\n onFrame?: (world: World) => void;\n /** Called when two bodies (or a body and a wall) hit with an impact speed in px/s. */\n onCollide?: (a: Body, b: Body | null, speed: number) => void;\n}\n\nexport interface Grab {\n move(x: number, y: number): void;\n release(): void;\n}\n\nconst SLEEP_DISTANCE = 1;\nconst SLEEP_TIME = 1;\n\nexport class World {\n readonly bodies: Body[] = [];\n readonly links: Link[] = [];\n gravity: Vector;\n pixelsPerMeter: number;\n airDrag: number;\n rollingResistance: number;\n iterations: number;\n bounds: Rect | null = null;\n readonly #rate: number;\n readonly #options: WorldOptions;\n #container: HTMLElement | null = null;\n #grabs = new Set<{ body: Body; target: Vector }>();\n #stopLoop: (() => void) | null = null;\n #pending = 0;\n #still = 0;\n #snapshot: [number, number, number][] = [];\n #observer: ResizeObserver | null = null;\n #running = false;\n\n constructor(options: WorldOptions = {}) {\n this.#options = options;\n this.gravity = options.gravity ?? { x: 0, y: 9.81 };\n this.pixelsPerMeter = options.pixelsPerMeter ?? 500;\n this.airDrag = options.airDrag ?? 0.05;\n this.rollingResistance = options.rollingResistance ?? 0.05;\n this.iterations = options.iterations ?? 4;\n this.#rate = options.rate ?? 240;\n this.setBounds(options.bounds ?? null);\n }\n\n /** Sets the walls. With an element, they follow its size. */\n setBounds(bounds: Rect | HTMLElement | null): void {\n this.#observer?.disconnect();\n this.#observer = null;\n this.#container = null;\n if (bounds && typeof HTMLElement !== \"undefined\" && bounds instanceof HTMLElement) {\n this.#container = bounds;\n const read = () => {\n this.bounds = { left: 0, top: 0, right: bounds.clientWidth, bottom: bounds.clientHeight };\n this.wake();\n };\n read();\n if (typeof ResizeObserver === \"function\") {\n this.#observer = new ResizeObserver(read);\n this.#observer.observe(bounds);\n }\n } else {\n this.bounds = (bounds as Rect | null) ?? null;\n this.wake();\n }\n }\n\n add(options: BodyOptions): Body {\n const ppm = this.pixelsPerMeter;\n const r = options.radius;\n const area = Math.PI * (r / ppm) ** 2;\n const mass = options.fixed\n ? Number.POSITIVE_INFINITY\n : (options.mass ?? area * (options.density ?? 1));\n const body: Body = {\n x: options.x,\n y: options.y,\n vx: options.vx ?? 0,\n vy: options.vy ?? 0,\n angle: options.angle ?? 0,\n spin: options.spin ?? 0,\n radius: r,\n mass,\n restitution: options.restitution ?? 0.5,\n friction: options.friction ?? 0.4,\n fixed: options.fixed ?? false,\n element: options.element ?? null,\n invMass: options.fixed ? 0 : 1 / mass,\n // Solid disc: I = ½·m·r², here with r in pixels so impulses stay in pixel units.\n invInertia: options.fixed ? 0 : 2 / (mass * r * r),\n };\n this.bodies.push(body);\n this.#draw(body);\n this.wake();\n return body;\n }\n\n remove(body: Body): void {\n const index = this.bodies.indexOf(body);\n if (index >= 0) this.bodies.splice(index, 1);\n for (let i = this.links.length - 1; i >= 0; i--) {\n const link = this.links[i] as Link;\n if (link.a === body || link.b === body) this.links.splice(i, 1);\n }\n for (const grab of this.#grabs) if (grab.body === body) this.#grabs.delete(grab);\n }\n\n /** Connects two bodies with a damped spring. */\n link(a: Body, b: Body, options: LinkOptions = {}): Link {\n const link: Link = {\n a,\n b,\n length: options.length ?? Math.hypot(b.x - a.x, b.y - a.y),\n frequency: options.frequency ?? 4,\n damping: options.damping ?? 0.3,\n };\n this.links.push(link);\n this.wake();\n return link;\n }\n\n unlink(link: Link): void {\n const index = this.links.indexOf(link);\n if (index >= 0) this.links.splice(index, 1);\n }\n\n /** Body under a point, if any. */\n bodyAt(x: number, y: number): Body | null {\n for (let i = this.bodies.length - 1; i >= 0; i--) {\n const body = this.bodies[i] as Body;\n if ((body.x - x) ** 2 + (body.y - y) ** 2 <= body.radius ** 2) return body;\n }\n return null;\n }\n\n /** Pulls a body towards a moving point with a stiff spring. Released bodies keep their speed. */\n grab(body: Body, x: number, y: number): Grab {\n const grab = { body, target: { x, y } };\n this.#grabs.add(grab);\n this.wake();\n return {\n move: (nx, ny) => {\n grab.target = { x: nx, y: ny };\n this.wake();\n },\n release: () => {\n this.#grabs.delete(grab);\n },\n };\n }\n\n /** Adds an impulse in kg·px/s at the centre (or `at` a point on the body for spin). */\n push(body: Body, impulse: Vector, at?: Vector): void {\n body.vx += impulse.x * body.invMass;\n body.vy += impulse.y * body.invMass;\n if (at)\n body.spin += ((at.x - body.x) * impulse.y - (at.y - body.y) * impulse.x) * body.invInertia;\n this.wake();\n }\n\n /**\n * Lets the pointer grab and throw bodies inside `element` (default: the bounds element).\n * Returns a function that removes the listeners.\n */\n bindPointer(element: HTMLElement | null = this.#container): () => void {\n if (!element) return () => {};\n const active = new Map<number, Grab>();\n const local = (event: PointerEvent) => {\n const rect = element.getBoundingClientRect();\n return { x: event.clientX - rect.left, y: event.clientY - rect.top };\n };\n const down = (event: PointerEvent) => {\n const p = local(event);\n const body = this.bodyAt(p.x, p.y);\n if (!body || body.fixed) return;\n event.preventDefault();\n element.setPointerCapture?.(event.pointerId);\n active.set(event.pointerId, this.grab(body, p.x, p.y));\n };\n const move = (event: PointerEvent) => {\n const grab = active.get(event.pointerId);\n if (!grab) return;\n const p = local(event);\n grab.move(p.x, p.y);\n };\n const up = (event: PointerEvent) => {\n active.get(event.pointerId)?.release();\n active.delete(event.pointerId);\n };\n const touchAction = element.style.touchAction;\n element.style.touchAction = \"none\";\n element.addEventListener(\"pointerdown\", down);\n element.addEventListener(\"pointermove\", move);\n element.addEventListener(\"pointerup\", up);\n element.addEventListener(\"pointercancel\", up);\n return () => {\n for (const grab of active.values()) grab.release();\n element.style.touchAction = touchAction;\n element.removeEventListener(\"pointerdown\", down);\n element.removeEventListener(\"pointermove\", move);\n element.removeEventListener(\"pointerup\", up);\n element.removeEventListener(\"pointercancel\", up);\n };\n }\n\n /** Starts the simulation on the shared frame loop. */\n start(): this {\n this.#running = true;\n this.wake();\n return this;\n }\n\n /** Pauses the simulation. */\n stop(): this {\n this.#running = false;\n this.#stopLoop?.();\n this.#stopLoop = null;\n return this;\n }\n\n /** True while the world runs and something moves. A resting world uses no CPU. */\n get awake(): boolean {\n return this.#stopLoop !== null;\n }\n\n /** Resumes a resting world, e.g. after you changed bodies by hand. */\n wake(): void {\n this.#still = 0;\n this.#snapshot = [];\n if (!this.#running || this.#stopLoop) return;\n this.#stopLoop = loop.add((dt) => this.#frame(dt));\n }\n\n destroy(): void {\n this.stop();\n this.#observer?.disconnect();\n this.bodies.length = 0;\n this.links.length = 0;\n this.#grabs.clear();\n }\n\n #frame(dt: number): void {\n const h = 1 / this.#rate;\n this.#pending = Math.min(this.#pending + dt, h * 32);\n while (this.#pending >= h) {\n this.step(h);\n this.#pending -= h;\n }\n for (const body of this.bodies) this.#draw(body);\n this.#options.onFrame?.(this);\n\n // Sleep when nothing has moved more than a pixel for a second. Measuring displacement\n // instead of speed ignores the tiny velocity that gravity adds to resting bodies every step.\n if (this.#grabs.size > 0 || this.#moved()) {\n this.#still = 0;\n this.#snapshot = this.bodies.map((b) => [b.x, b.y, b.angle * b.radius]);\n } else {\n this.#still += dt;\n }\n if (this.#still >= SLEEP_TIME) {\n this.#stopLoop?.();\n this.#stopLoop = null;\n }\n }\n\n #moved(): boolean {\n const snapshot = this.#snapshot;\n if (snapshot.length !== this.bodies.length) return true;\n return this.bodies.some((b, i) => {\n const [x, y, arc] = snapshot[i] as [number, number, number];\n return (\n Math.abs(b.x - x) > SLEEP_DISTANCE ||\n Math.abs(b.y - y) > SLEEP_DISTANCE ||\n Math.abs(b.angle * b.radius - arc) > SLEEP_DISTANCE\n );\n });\n }\n\n #draw(body: Body): void {\n const el = body.element;\n if (!el) return;\n const r = body.radius;\n el.style.transform = `translate3d(${body.x - r}px, ${body.y - r}px, 0) rotate(${body.angle}rad)`;\n }\n\n /** Advances the simulation by `dt` seconds. Called for you while the world runs. */\n step(dt: number): void {\n const ppm = this.pixelsPerMeter;\n const gx = this.gravity.x * ppm;\n const gy = this.gravity.y * ppm;\n const drag = Math.exp(-this.airDrag * dt);\n\n for (const body of this.bodies) {\n if (body.fixed) continue;\n body.vx = (body.vx + gx * dt) * drag;\n body.vy = (body.vy + gy * dt) * drag;\n body.spin *= drag;\n }\n\n for (const link of this.links) this.#spring(link, dt);\n\n for (const { body, target } of this.#grabs) {\n // A critically damped 8 Hz joint, integrated implicitly so it never explodes:\n // v' = (v + dt·k·(target − x)) / (1 + dt·c + dt²·k)\n const w = 2 * Math.PI * 8;\n const k = w * w;\n const c = 2 * w;\n const den = 1 + dt * c + dt * dt * k;\n body.vx = (body.vx + dt * k * (target.x - body.x)) / den;\n body.vy = (body.vy + dt * k * (target.y - body.y)) / den;\n body.spin *= 0.9;\n }\n\n for (const body of this.bodies) {\n if (body.fixed) continue;\n body.x += body.vx * dt;\n body.y += body.vy * dt;\n body.angle += body.spin * dt;\n }\n\n const bodies = [...this.bodies].sort((a, b) => a.x - a.radius - (b.x - b.radius));\n const restingSpeed = 0.5 * ppm;\n for (let pass = 0; pass < this.iterations; pass++) {\n for (let i = 0; i < bodies.length; i++) {\n const a = bodies[i] as Body;\n for (let j = i + 1; j < bodies.length; j++) {\n const b = bodies[j] as Body;\n if (b.x - b.radius > a.x + a.radius) break;\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const sum = a.radius + b.radius;\n const d2 = dx * dx + dy * dy;\n if (d2 >= sum * sum) continue;\n const d = Math.sqrt(d2);\n const nx = d > 1e-9 ? dx / d : 1;\n const ny = d > 1e-9 ? dy / d : 0;\n this.#contact(a, b, nx, ny, sum - d, restingSpeed, pass === 0);\n }\n if (this.bounds && !a.fixed) this.#walls(a, this.bounds, restingSpeed, pass === 0);\n }\n }\n }\n\n #spring(link: Link, dt: number): void {\n const { a, b } = link;\n const inv = a.invMass + b.invMass;\n if (inv === 0) return;\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const d = Math.hypot(dx, dy) || 1e-9;\n const nx = dx / d;\n const ny = dy / d;\n const m = 1 / inv;\n const w = 2 * Math.PI * link.frequency;\n const k = m * w * w;\n const c = 2 * m * link.damping * w;\n const vn = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny;\n const force = -k * (d - link.length) - c * vn;\n const j = force * dt;\n a.vx -= j * nx * a.invMass;\n a.vy -= j * ny * a.invMass;\n b.vx += j * nx * b.invMass;\n b.vy += j * ny * b.invMass;\n }\n\n // Resolves one contact. n points from a to b; a may be a wall (null).\n #contact(\n a: Body | null,\n b: Body,\n nx: number,\n ny: number,\n depth: number,\n restingSpeed: number,\n report: boolean,\n ): void {\n const ima = a ? a.invMass : 0;\n const imb = b.invMass;\n const inv = ima + imb;\n if (inv === 0) return;\n\n // Push apart, shared by inverse mass.\n const correction = Math.max(depth - 0.01, 0) / inv;\n if (a) {\n a.x -= nx * correction * ima;\n a.y -= ny * correction * ima;\n }\n b.x += nx * correction * imb;\n b.y += ny * correction * imb;\n\n const ra = a ? a.radius : 0;\n const rb = b.radius;\n const iia = a ? a.invInertia : 0;\n const iib = b.invInertia;\n // Velocity of the contact point on each body: v + ω × r, with r = ±radius·n.\n const tx = -ny;\n const ty = nx;\n const relative = () => {\n const vax = a ? a.vx - a.spin * ra * ny : 0;\n const vay = a ? a.vy + a.spin * ra * nx : 0;\n const vbx = b.vx + b.spin * rb * ny;\n const vby = b.vy - b.spin * rb * nx;\n return { x: vbx - vax, y: vby - vay };\n };\n let rv = relative();\n const vn = rv.x * nx + rv.y * ny;\n if (vn >= 0) return;\n\n const e = -vn < restingSpeed ? 0 : Math.min(a ? a.restitution : 1, b.restitution);\n const jn = (-(1 + e) * vn) / inv;\n if (a) {\n a.vx -= jn * nx * ima;\n a.vy -= jn * ny * ima;\n }\n b.vx += jn * nx * imb;\n b.vy += jn * ny * imb;\n if (report && -vn > restingSpeed) this.#options.onCollide?.(b, a, -vn);\n\n // Coulomb friction along the tangent, which also makes round bodies roll.\n rv = relative();\n const vt = rv.x * tx + rv.y * ty;\n const kt = inv + ra * ra * iia + rb * rb * iib;\n const mu = Math.sqrt((a ? a.friction : 1) * b.friction);\n const jt = Math.max(Math.min(-vt / kt, mu * jn), -mu * jn);\n if (a) {\n a.vx -= jt * tx * ima;\n a.vy -= jt * ty * ima;\n a.spin -= jt * ra * iia;\n }\n b.vx += jt * tx * imb;\n b.vy += jt * ty * imb;\n b.spin -= jt * rb * iib;\n\n // Rolling resistance: a torque against the spin, limited so it never reverses it.\n const brake = (body: Body, r: number) => {\n const limit = this.rollingResistance * jn * r * body.invInertia;\n body.spin = Math.abs(body.spin) <= limit ? 0 : body.spin - Math.sign(body.spin) * limit;\n };\n if (a) brake(a, ra);\n brake(b, rb);\n }\n\n #walls(body: Body, walls: Rect, restingSpeed: number, report: boolean): void {\n const r = body.radius;\n if (body.y + r > walls.bottom)\n this.#contact(null, body, 0, -1, body.y + r - walls.bottom, restingSpeed, report);\n if (body.y - r < walls.top)\n this.#contact(null, body, 0, 1, walls.top - (body.y - r), restingSpeed, report);\n if (body.x - r < walls.left)\n this.#contact(null, body, 1, 0, walls.left - (body.x - r), restingSpeed, report);\n if (body.x + r > walls.right)\n this.#contact(null, body, -1, 0, body.x + r - walls.right, restingSpeed, report);\n }\n}\n\n/** Creates a world. Call `start()` to run it on the frame loop, or `step()` it yourself. */\nexport function world(options: WorldOptions = {}): World {\n return new World(options);\n}\n"],"mappings":"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","names":["value","value","value","last","value","initial","value","spring","value","initial"]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@sweberdev/lagrangian",
3
+ "version": "0.1.0",
4
+ "description": "Animation with real physics: analytic springs that keep their velocity when interrupted, inertia with bounds and snap points, draggable throws, and a small 2D world with gravity, collisions and friction.",
5
+ "license": "MIT",
6
+ "author": "Seya Weber (https://github.com/sxwxbxr)",
7
+ "homepage": "https://packages.sweber.dev/lagrangian/docs",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Weber-Development/lagrangian.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Weber-Development/lagrangian/issues"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "main": "./dist/index.cjs",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "import": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "require": {
28
+ "types": "./dist/index.d.cts",
29
+ "default": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "src",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "keywords": [
41
+ "animation",
42
+ "physics",
43
+ "spring",
44
+ "spring-animation",
45
+ "inertia",
46
+ "momentum",
47
+ "drag",
48
+ "gesture",
49
+ "gravity",
50
+ "collision",
51
+ "physics-engine",
52
+ "rk4",
53
+ "reduced-motion"
54
+ ],
55
+ "publishConfig": {
56
+ "access": "public",
57
+ "provenance": true
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^22.20.5",
61
+ "esbuild": "^0.27.7",
62
+ "jsdom": "^27.0.0",
63
+ "tsup": "^8.5.1",
64
+ "typescript": "^5.9.0",
65
+ "vitest": "^5.0.3"
66
+ },
67
+ "scripts": {
68
+ "build": "tsup",
69
+ "test": "vitest run",
70
+ "typecheck": "tsc -p tsconfig.json",
71
+ "size": "node scripts/size.mjs"
72
+ }
73
+ }
package/src/animate.ts ADDED
@@ -0,0 +1,172 @@
1
+ // Spring elements to new values. Every element keeps one physical value per property, so a new
2
+ // call while the old one is still moving continues with the current velocity instead of
3
+ // restarting.
4
+
5
+ import type { SpringOptions } from "./spring";
6
+ import { type Animation, PhysicsValue } from "./value";
7
+
8
+ export type TransformProp = "x" | "y" | "z" | "scale" | "scaleX" | "scaleY" | "rotate";
9
+ export type Prop = TransformProp | "opacity" | `--${string}`;
10
+ export type Props = Partial<Record<Prop, number>>;
11
+
12
+ export interface AnimateOptions extends SpringOptions {
13
+ /** Starting velocity per property, e.g. from a gesture. */
14
+ velocity?: Props;
15
+ /** Seconds to wait before starting. */
16
+ delay?: number;
17
+ /** Extra delay per element when animating several, in seconds. */
18
+ stagger?: number;
19
+ }
20
+
21
+ type Target = Element | Iterable<Element> | ArrayLike<Element> | string;
22
+
23
+ const TRANSFORMS: readonly TransformProp[] = ["x", "y", "z", "rotate", "scale", "scaleX", "scaleY"];
24
+ const registry = new WeakMap<Element, Map<Prop, PhysicsValue>>();
25
+
26
+ function initial(element: Element, prop: Prop): number {
27
+ if (prop === "scale" || prop === "scaleX" || prop === "scaleY") return 1;
28
+ if (prop === "opacity") {
29
+ const style = (element as HTMLElement).style;
30
+ const inline = style ? Number.parseFloat(style.opacity) : Number.NaN;
31
+ if (!Number.isNaN(inline)) return inline;
32
+ if (typeof getComputedStyle !== "function") return 1;
33
+ const computed = Number.parseFloat(getComputedStyle(element).opacity);
34
+ return Number.isNaN(computed) ? 1 : computed;
35
+ }
36
+ if (prop.startsWith("--") && typeof getComputedStyle === "function") {
37
+ const computed = Number.parseFloat(getComputedStyle(element).getPropertyValue(prop));
38
+ return Number.isNaN(computed) ? 0 : computed;
39
+ }
40
+ return 0;
41
+ }
42
+
43
+ function render(element: Element, values: Map<Prop, PhysicsValue>): void {
44
+ const style = (element as HTMLElement | SVGElement).style;
45
+ if (!style) return;
46
+ const get = (prop: TransformProp) => values.get(prop)?.get();
47
+ const parts: string[] = [];
48
+ const x = get("x");
49
+ const y = get("y");
50
+ const z = get("z");
51
+ if (x !== undefined || y !== undefined || z !== undefined) {
52
+ parts.push(`translate3d(${x ?? 0}px, ${y ?? 0}px, ${z ?? 0}px)`);
53
+ }
54
+ const rotate = get("rotate");
55
+ if (rotate !== undefined) parts.push(`rotate(${rotate}deg)`);
56
+ const scale = get("scale");
57
+ if (scale !== undefined) parts.push(`scale(${scale})`);
58
+ const scaleX = get("scaleX");
59
+ if (scaleX !== undefined) parts.push(`scaleX(${scaleX})`);
60
+ const scaleY = get("scaleY");
61
+ if (scaleY !== undefined) parts.push(`scaleY(${scaleY})`);
62
+ if (TRANSFORMS.some((prop) => values.has(prop))) style.transform = parts.join(" ");
63
+ const opacity = values.get("opacity");
64
+ if (opacity) style.opacity = String(Math.min(Math.max(opacity.get(), 0), 1));
65
+ for (const [prop, value] of values) {
66
+ if (prop.startsWith("--")) style.setProperty(prop, String(value.get()));
67
+ }
68
+ }
69
+
70
+ /**
71
+ * The physical value behind one property of an element. Use it to read the live velocity,
72
+ * to drive the element from a gesture with `set`, or to listen to changes.
73
+ */
74
+ export function getValue(element: Element, prop: Prop): PhysicsValue {
75
+ let values = registry.get(element);
76
+ if (!values) {
77
+ values = new Map();
78
+ registry.set(element, values);
79
+ }
80
+ let v = values.get(prop);
81
+ if (!v) {
82
+ const map = values;
83
+ v = new PhysicsValue(initial(element, prop));
84
+ let queued = false;
85
+ v.on(() => {
86
+ // Several properties change in the same frame: write the style once.
87
+ if (queued) return;
88
+ queued = true;
89
+ queueMicrotask(() => {
90
+ queued = false;
91
+ render(element, map);
92
+ });
93
+ });
94
+ values.set(prop, v);
95
+ }
96
+ return v;
97
+ }
98
+
99
+ function elements(target: Target): Element[] {
100
+ if (typeof target === "string") {
101
+ return typeof document === "undefined" ? [] : [...document.querySelectorAll(target)];
102
+ }
103
+ if (typeof Element !== "undefined" && target instanceof Element) return [target];
104
+ return Array.from(target as ArrayLike<Element>);
105
+ }
106
+
107
+ function group(animations: Animation[]): Animation {
108
+ const finished = Promise.all(animations.map((a) => a.finished)).then((all) => all.every(Boolean));
109
+ return {
110
+ finished,
111
+ stop: () => {
112
+ for (const a of animations) a.stop();
113
+ },
114
+ };
115
+ }
116
+
117
+ function delayed(seconds: number, start: () => Animation): Animation {
118
+ if (seconds <= 0) return start();
119
+ let inner: Animation | null = null;
120
+ let stopped = false;
121
+ let resolve!: (value: boolean) => void;
122
+ const finished = new Promise<boolean>((r) => {
123
+ resolve = r;
124
+ });
125
+ const timer = setTimeout(() => {
126
+ if (stopped) return;
127
+ inner = start();
128
+ inner.finished.then(resolve);
129
+ }, seconds * 1000);
130
+ return {
131
+ finished,
132
+ stop: () => {
133
+ stopped = true;
134
+ clearTimeout(timer);
135
+ if (inner) inner.stop();
136
+ else resolve(false);
137
+ },
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Springs one or more elements to the given values.
143
+ *
144
+ * ```ts
145
+ * animate(".card", { x: 120, rotate: 4, opacity: 1 }, { duration: 0.6, bounce: 0.3 })
146
+ * ```
147
+ */
148
+ export function animate(target: Target, props: Props, options: AnimateOptions = {}): Animation {
149
+ const { velocity, delay = 0, stagger = 0, ...spring } = options;
150
+ const animations = elements(target).map((element, index) =>
151
+ delayed(delay + index * stagger, () =>
152
+ group(
153
+ (Object.keys(props) as Prop[]).map((prop) =>
154
+ getValue(element, prop).to(props[prop] as number, {
155
+ ...spring,
156
+ ...(velocity?.[prop] !== undefined ? { velocity: velocity[prop] } : {}),
157
+ }),
158
+ ),
159
+ ),
160
+ ),
161
+ );
162
+ return group(animations);
163
+ }
164
+
165
+ /** Sets values immediately, without animation, and drops their momentum. */
166
+ export function set(target: Target, props: Props): void {
167
+ for (const element of elements(target)) {
168
+ for (const prop of Object.keys(props) as Prop[]) {
169
+ getValue(element, prop).jump(props[prop] as number);
170
+ }
171
+ }
172
+ }
package/src/decay.ts ADDED
@@ -0,0 +1,127 @@
1
+ // Inertia: a moving object slowed by friction proportional to its speed (like iOS scrolling).
2
+ // v(t) = v0·e^(−t/τ), x(t) = x0 + v0·τ·(1 − e^(−t/τ)), so it comes to rest at x0 + v0·τ.
3
+ // With bounds it bounces off the edge on a spring; with snap points it lands exactly on one.
4
+
5
+ import { type Motion, type SpringOptions, spring } from "./spring";
6
+
7
+ export type Snap = readonly number[] | ((restingPoint: number) => number);
8
+
9
+ export interface DecayOptions {
10
+ /** Starting velocity in units per second. Defaults to the value's current velocity. */
11
+ velocity?: number;
12
+ /**
13
+ * Fraction of speed kept per millisecond. 0.998 is the iOS "normal" scroll feel (default),
14
+ * 0.99 is "fast" and stops much sooner.
15
+ */
16
+ deceleration?: number;
17
+ /** Lower bound. Crossing it hands over to a spring that settles on the edge. */
18
+ min?: number;
19
+ /** Upper bound. */
20
+ max?: number;
21
+ /** Snap points, or a function that maps the natural resting point to a snapped one. */
22
+ snap?: Snap;
23
+ /** Spring used at the edges and when a snap point needs a spring. */
24
+ bounce?: SpringOptions;
25
+ /** The motion ends once it is this close to its resting point. Default 0.5. */
26
+ restDelta?: number;
27
+ }
28
+
29
+ const EDGE_SPRING: SpringOptions = { stiffness: 400, damping: 40 };
30
+
31
+ export function timeConstant(deceleration = 0.998): number {
32
+ const rate = Math.min(Math.max(deceleration, 0.5), 0.99999);
33
+ return -1 / (1000 * Math.log(rate));
34
+ }
35
+
36
+ /** Where a throw with this velocity comes to rest without bounds or snap points. */
37
+ export function projectRest(from: number, velocity: number, deceleration = 0.998): number {
38
+ return from + velocity * timeConstant(deceleration);
39
+ }
40
+
41
+ /** Nearest snap point to `value`. */
42
+ export function nearest(points: readonly number[], value: number): number {
43
+ let best = value;
44
+ let distance = Number.POSITIVE_INFINITY;
45
+ for (const point of points) {
46
+ const d = Math.abs(point - value);
47
+ if (d < distance) {
48
+ distance = d;
49
+ best = point;
50
+ }
51
+ }
52
+ return best;
53
+ }
54
+
55
+ function resolveSnap(snap: Snap, value: number): number {
56
+ return typeof snap === "function" ? snap(value) : nearest(snap, value);
57
+ }
58
+
59
+ function friction(from: number, v0: number, tau: number, end: number, restDelta: number): Motion {
60
+ return (t) => {
61
+ const e = Math.exp(-Math.max(t, 0) / tau);
62
+ const value = from + v0 * tau * (1 - e);
63
+ const velocity = v0 * e;
64
+ if (Math.abs(end - value) <= restDelta) return { value: end, velocity: 0, done: true };
65
+ return { value, velocity, done: false };
66
+ };
67
+ }
68
+
69
+ /** A throw with friction from `from`, optionally with bounds and snap points. */
70
+ export function decay(from: number, options: DecayOptions = {}): Motion {
71
+ const v0 = options.velocity ?? 0;
72
+ const tau = timeConstant(options.deceleration);
73
+ const restDelta = options.restDelta ?? 0.5;
74
+ const min = options.min ?? Number.NEGATIVE_INFINITY;
75
+ const max = options.max ?? Number.POSITIVE_INFINITY;
76
+ const edge = { ...EDGE_SPRING, restDelta, ...options.bounce };
77
+ const clamp = (x: number) => Math.min(Math.max(x, min), max);
78
+
79
+ // Already outside the bounds (e.g. let go while rubber-banding): spring back to the edge.
80
+ if (from < min || from > max) return spring(from, clamp(from), v0, edge);
81
+
82
+ const natural = from + v0 * tau;
83
+
84
+ if (options.snap !== undefined) {
85
+ const target = clamp(resolveSnap(options.snap, natural));
86
+ const distance = target - from;
87
+ // Stretch or shorten the friction so the throw ends exactly on the snap point, as long as
88
+ // that still feels like the same throw. Otherwise a spring carries the velocity there.
89
+ const fitted = v0 !== 0 ? distance / v0 : -1;
90
+ if (fitted > tau / 4 && fitted < tau * 4) return friction(from, v0, fitted, target, restDelta);
91
+ return spring(from, target, v0, { ...edge, ...options.bounce });
92
+ }
93
+
94
+ if (natural >= min && natural <= max) return friction(from, v0, tau, natural, restDelta);
95
+
96
+ // The throw leaves the bounds: glide until the edge, then let a spring catch it there.
97
+ const bound = natural < min ? min : max;
98
+ const crossing = -tau * Math.log(1 - (bound - from) / (v0 * tau));
99
+ const glide = friction(from, v0, tau, natural, 0);
100
+ const vAtEdge = v0 * Math.exp(-crossing / tau);
101
+ const catcher = spring(bound, bound, vAtEdge, edge);
102
+ return (t) => (t < crossing ? glide(t) : catcher(t - crossing));
103
+ }
104
+
105
+ /**
106
+ * Elastic overscroll, as on iOS: the further past the edge, the harder it pulls back.
107
+ * `distance` is how far past the edge, `dimension` the size of the visible area.
108
+ */
109
+ export function rubberband(distance: number, dimension: number, constant = 0.55): number {
110
+ if (dimension <= 0 || constant <= 0) return 0;
111
+ const sign = Math.sign(distance);
112
+ const d = Math.abs(distance);
113
+ return sign * (1 - 1 / ((d * constant) / dimension + 1)) * dimension;
114
+ }
115
+
116
+ /** Applies `rubberband` to a value outside [min, max]. Values inside stay unchanged. */
117
+ export function rubberClamp(
118
+ value: number,
119
+ min: number,
120
+ max: number,
121
+ dimension: number,
122
+ constant = 0.55,
123
+ ): number {
124
+ if (value < min) return min + rubberband(value - min, dimension, constant);
125
+ if (value > max) return max + rubberband(value - max, dimension, constant);
126
+ return value;
127
+ }