From 529a0efbfec392e80bdf946a72f50109835abe55 Mon Sep 17 00:00:00 2001 From: andwati Date: Sun, 30 Aug 2026 19:12:44 +0300 Subject: [PATCH] Add a 404 page concept and a spring-physics motion primitive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 404: "Page Fault" — a SIGSEGV/memory-fault framing distinct from the retired terminal easter egg, still fitting the CS-flavored identity. Plain/unstyled for now like the rest of the placeholder layout; restyle once the design system lands. src/lib/spring.ts: a small CSS+Web Animations API spring utility (damped harmonic oscillator -> keyframe progress values), per the locked "no animation library" decision. Not wired into any component yet — this is the foundational primitive the design system's motion work will build on, kept free of any visual/component opinions. --- apps/site/src/lib/spring.ts | 66 +++++++++++++++++++++++++++++++++++ apps/site/src/pages/404.astro | 14 ++++++++ 2 files changed, 80 insertions(+) create mode 100644 apps/site/src/lib/spring.ts create mode 100644 apps/site/src/pages/404.astro diff --git a/apps/site/src/lib/spring.ts b/apps/site/src/lib/spring.ts new file mode 100644 index 0000000..52c38df --- /dev/null +++ b/apps/site/src/lib/spring.ts @@ -0,0 +1,66 @@ +export interface SpringOptions { + stiffness?: number; + damping?: number; + mass?: number; + /** Stop simulating once position and velocity are both within this of rest. */ + precision?: number; +} + +/** + * Simulates a damped harmonic oscillator from 0 to 1 and returns per-frame + * progress values (60fps), for use as Web Animations API keyframes. Pure + * physics simulation, no DOM/CSS opinions — see `animateSpring` below for + * the usual way to consume it. + */ +export function springProgress({ + stiffness = 170, + damping = 26, + mass = 1, + precision = 0.005, +}: SpringOptions = {}): number[] { + const frames: number[] = []; + let position = 0; + let velocity = 0; + const dt = 1 / 60; + const maxDuration = 4; // seconds, safety cap against under-damped configs + for (let t = 0; t < maxDuration; t += dt) { + const acceleration = + (-stiffness * (position - 1) - damping * velocity) / mass; + velocity += acceleration * dt; + position += velocity * dt; + frames.push(position); + if (Math.abs(1 - position) < precision && Math.abs(velocity) < precision) + break; + } + frames.push(1); + return frames; +} + +/** + * Animates `element` from `from` to `to` along a spring curve, via the + * native Web Animations API — no animation library dependency. `toKeyframe` + * shapes each interpolated value into whatever CSS properties you need, + * e.g. `(v) => ({ transform: \`translateY(${(1 - v) * 20}px)\`, opacity: v })`. + * Honors `prefers-reduced-motion` by jumping straight to the end state. + */ +export function animateSpring( + element: HTMLElement, + options: SpringOptions & { + from: number; + to: number; + toKeyframe: (value: number) => Keyframe; + }, +): Animation { + const { from, to, toKeyframe, ...spring } = options; + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + return element.animate([toKeyframe(to)], { duration: 1, fill: "forwards" }); + } + const progress = springProgress(spring); + const keyframes = progress.map((p) => toKeyframe(from + (to - from) * p)); + const durationMs = (progress.length / 60) * 1000; + return element.animate(keyframes, { + duration: durationMs, + fill: "forwards", + easing: "linear", + }); +} diff --git a/apps/site/src/pages/404.astro b/apps/site/src/pages/404.astro new file mode 100644 index 0000000..6363fed --- /dev/null +++ b/apps/site/src/pages/404.astro @@ -0,0 +1,14 @@ +--- +import Base from "../layouts/Base.astro"; +--- + + +

Page Fault

+

SIGSEGV — the address you requested was never mapped.

+

+ Whatever used to live here has been moved, renamed, or never existed in + this address space to begin with. Unlike a real page fault, there's no + handler that's going to page this back in for you. +

+

Return to a valid address →

+