Motion
Springs, keyframe tables, the motion rules, and the moves to copy.
Motion in a page is plain web motion: CSS, Web Animations, or a window.seek(t) function. core/motion/springs.js adds pure math for it: functions of time, no state, no Math.random, no dependencies. core/motion/README.md is the reference.
import { spring, track, approach, kf, springLinear, springDuration, curveToLinear, CURVES, SPRINGS, rng, noise1 } from '../../core/motion/springs.js';A spring as an easing
The renderer seeks document.getAnimations(), so a native animation needs no seek.
const { k, d } = SPRINGS.snappy;
el.animate([{ transform: 'translateY(40px)', opacity: 0 }, { transform: 'none', opacity: 1 }],
{ duration: springDuration(k, d) * 1000, easing: springLinear(k, d), fill: 'both', delay: 600 });Do not fit a cubic-bezier() by hand. It starts too hard and stops too early. Use curveToLinear(fn) to turn any curve into an exact CSS linear() easing. CURVES holds expoOut, spring and overshoot.
Spring presets in SPRINGS: snappy for UI and leading edges, default, heavy for big type and logos, playful for visible overshoot.
A canvas in window.seek(t)
window.seek = (t) => {
const x = track(t, [[0, 100], [1.2, 640], [2.4, 280]], 170, 26); // one spring per target change
const zoom = approach(t * 30, 1, 1.35, 0.15); // frame number, 15% of the gap per frame
ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
ctx.fillRect(x, 200, 80, 80);
};A [[f, v]] table with kf
Tunable numbers stay as literals so the studio can edit them. Values can be arrays, for colours.
const ROT = [[0, 0], [24, 90], [60, 90], [84, 0]];
const TINT = [[0, [20, 20, 30]], [60, [40, 90, 255]]];
const f = t * 30;
el.style.rotate = `${kf(f, ROT, 'easeInOutCubic')}deg`;
const [r, g, b] = kf(f, TINT);Rules that are not your defaults
- Arrive fast, land soft. Use
approach(f, from, to, k)with k 0.12 to 0.19, notease-outon everything. - Exits run faster and shorter than entrances.
- Stagger siblings 30 to 80 ms. Never let a group land on one frame.
- Blur follows motion. A moving thing may blur. A still thing never does.
- Text holds long enough to read (rule
readable-hold). Fix a fast read with a hold, never with a slower move. - The slowest beat is at least 3 times the fastest.
- Cut on the beat, or two frames early.
- Adjacent transitions change axis or direction.
The rules live in taste/rules/, one per file (readable-hold.md, speed-bands.md, no-tells.md). taste/build/CARD.md holds the rules the judge scores against and 5 anti-patterns.
Moves to copy
prompts/moves/README.md lists proven moves, grouped by job. Each move is a short CSS and Web Animations snippet with a one-second clip. Choose from the clip, copy the snippet, and keep its numbers. A few examples:
| Job | Moves |
|---|---|
| land a title, soft | tracking-collapse, mask-rise, blur-word-cascade |
| type a command | caret-typing, caret-follow |
| change between shots | cut-on-motion, whip-pan, zoom-through, color-block-wipe |
| move the camera | camera-moves, pull-back-reveal, parallax-dive, rack-focus |
| show a click | cursor-click, notification-pop |
| prove a number | count-up, chart-build |
The gallery is on the moves page. bin/vawe moves renders the clips again when a demo changes.
To draft only the seconds you work on, run bin/vawe dev <page> --from s --to s.