vaweGitHub

Video componentsProduct moments

Panel live sync

Product momentsvawe3.8 s

panel-live-sync.md

Panel live sync: final frame of the product UI animation
00:00.0000:03.80
Tap the clip to play it.

When to use

Use when the feature is editing with instant feedback: a visual editor, an inspector, a design tool. The page holds two surfaces in one frame, a target (a button on a page) and a panel of controls. A cursor presses a field and scrubs it, and the field’s number and the target change in the same frame, three beats in a row (rotate, radius, padding). The claim is the causality: one gesture, two surfaces, no lag.

The code

js
import '../../core/engine/page-api.js';
const smooth = easeFn('settle'), clamp = (u) => Math.min(1, Math.max(0, u)), lerp = (a, b, u) => a + (b - a) * u;
const seg = (t, a, b) => smooth(clamp((t - a) / (b - a)));
// one gesture per beat: glide to a field, press, scrub, release (seconds); values hold between beats
const beats = [
  { k: 'rot', glide: [0.35, 0.8], press: 0.86, scrub: [0.9, 1.45], release: 1.55 },
  { k: 'rad', glide: [1.55, 1.9], press: 1.95, scrub: [2.0, 2.55], release: 2.65 },
];
const value = { rot: (t) => lerp(0, -9, seg(t, ...beats[0].scrub)), rad: (t) => lerp(14, 58, seg(t, ...beats[1].scrub)) };

vawe.onFrame((t) => {
  const rot = value.rot(t), rad = value.rad(t);
  // the control and the target read the same numbers in the same frame, so they cannot drift apart
  target.style.rotate = `${rot}deg`;
  target.style.borderRadius = `${rad / 10.8}vh`;
  fields.rot.textContent = `${Math.round(rot)} deg`;
  fields.rad.textContent = `${Math.round(rad)} px`;
  for (const b of beats) fields[b.k].classList.toggle('on', t >= b.press && t < b.release);   // accent ring while pressed
  const [x, y] = cursorAt(t);   // glide on a smoothstep, then ride the scrub to the right
  cursor.style.translate = `${x}px ${y}px`;
  cursor.firstChild.style.scale = beats.some((b) => t >= b.press && t < b.release) ? 0.86 : 1;
});

Easing

Each ease is a pair of After Effects handles from core/motion/presets.js. Every curve as CSS.

EaseLeaving a keyArriving at a key
land12% influence, speed 4.875% influence, speed 0
pop12% influence, speed 4.835% influence, speed -1.5
settle60% influence, speed 060% influence, speed 0

Notes

Drive the cursor, the field text and the target from one vawe.onFrame, not from separate animations: two tweens that share a start time still drift when one is edited, and one function cannot. Keep both surfaces fully in frame for the whole clip; a zoom that crops the target out breaks the claim. Show the value with its unit (-9 deg, 58 px) in a mono face with tabular figures so the digits do not jitter. Scrub to the right by about 7 percent of the frame width so the drag is visible on the cursor. Measure the field positions with getBoundingClientRect before any animation starts, since an entrance translate would shift them. The target wears a dashed outline (selection chrome) so the viewer knows what is bound.

Looks (ground, type, radius, accent) live in demo/demo.css; this snippet keeps neutral tokens.

Sound

Sound: none; a scrub is silent, and a tick per value reads as a machine.

Chain it into a film.

Copy chains of video components from the recipes.