Motion Grammar · Exposure Sheet Timing12fps · 83.33ms poses

Motion on twos

In hand-drawn animation, most action is shot “on twos”—each drawing is held for two frames at 24fps (12 poses per second, 83.33ms per pose). FRAME/12 applies the same spacing chart to discrete UI state changes while keeping pointer tracking instant.

Stepped · 12fps83.33ms per held pose · steps(3, end)

On twos (FRAME/12 default)

F1–20 msPose 1 / 4
F3–483 msPose 2 / 4
F5–6167 msPose 3 / 4
F7–8250 msSettled

Recovered revenue (stepped count-up)

$120,054

Up 11.4% vs prior 30 days

$0$2k$4k$6kOct 31Oct 24Oct 17Oct 10Oct 3
Show stepped stage data table
DateStepped revenue wipe
Oct 31, 2026$5,654
Oct 30, 2026$5,179
Oct 29, 2026$4,720
Oct 28, 2026$4,038
Oct 27, 2026$3,998
Oct 26, 2026$4,187
Oct 25, 2026$5,029
Oct 24, 2026$5,325
Oct 23, 2026$5,065
Oct 22, 2026$3,837
Oct 21, 2026$3,241
Oct 20, 2026$3,231
Oct 19, 2026$3,127
Oct 18, 2026$4,146
Oct 17, 2026$4,107
Oct 16, 2026$3,719
Oct 15, 2026$3,756
Oct 14, 2026$3,617
Oct 13, 2026$3,582
Oct 12, 2026$3,123
Oct 11, 2026$4,276
Oct 10, 2026$4,048
Oct 9, 2026$3,739
Oct 8, 2026$3,457
Oct 7, 2026$3,499
Oct 6, 2026$3,166
Oct 5, 2026$3,299
Oct 4, 2026$3,686
Oct 3, 2026$4,351
Oct 2, 2026$3,852

Top interventions (two-frame impact burst)

Last 30 days · 4-pose popStep + 2-frame SVG burst

Smooth · 60fps

Continuous 60fps easing

SAMPLING16.7 msContinuous RAF
INDICATORcubic-bezier260ms glide
CHART WIPE45 frames750ms easeOut
REDUCED MOTIONAuto CutOS preference

Recovered revenue (smooth mode)

$120,054

Up 11.4% vs prior 30 days

$0$2k$4k$6kOct 31Oct 24Oct 17Oct 10Oct 3
Show comparison stage data table
DateComparison revenue wipe
Oct 31, 2026$5,654
Oct 30, 2026$5,179
Oct 29, 2026$4,720
Oct 28, 2026$4,038
Oct 27, 2026$3,998
Oct 26, 2026$4,187
Oct 25, 2026$5,029
Oct 24, 2026$5,325
Oct 23, 2026$5,065
Oct 22, 2026$3,837
Oct 21, 2026$3,241
Oct 20, 2026$3,231
Oct 19, 2026$3,127
Oct 18, 2026$4,146
Oct 17, 2026$4,107
Oct 16, 2026$3,719
Oct 15, 2026$3,756
Oct 14, 2026$3,617
Oct 13, 2026$3,582
Oct 12, 2026$3,123
Oct 11, 2026$4,276
Oct 10, 2026$4,048
Oct 9, 2026$3,739
Oct 8, 2026$3,457
Oct 7, 2026$3,499
Oct 6, 2026$3,166
Oct 5, 2026$3,299
Oct 4, 2026$3,686
Oct 3, 2026$4,351
Oct 2, 2026$3,852

Top interventions (comparison)

Last 30 days · smooth transition

Exposure SheetF1–2 (0ms) → F3–4 (83ms) → F5–6 (167ms) → F7–8 (250ms)

Four held drawings across 250 milliseconds

When the range selector moves from 7 to 90 days, the active cell indicator steps through three intermediate poses held for 83.33ms each. Because the indicator usesmix-blend-mode: difference, any numeral it crosses inverts cleanly at every held drawing without blurry sub-pixel anti-aliasing.

F1–27309073090F3–47309073090F5–67309073090F7–873090730900 ms83 ms167 ms250 ms

Where each motion grammar belongs

Stepped motion is strictly for discrete state changes triggered by a decision. Never step pointer tracking, scrolling, or typing.

FRAME/12 timing rules enforced across all registry components and hooks.
GrammarTimingUsed forNever used for
Stepped (on twos)12fps · 83.33ms per pose (250–750ms)SegmentedControl cell jump, Metric count-up, ToneAreaChart clip wipe, ToneBarList reveal, Badge impact burstPointer hover, scroll, drag, crosshair, text input
Immediate (hand)0ms latency (1:1 pointer)Button offset shadow on hover, chart pointer/keyboard crosshair, scrolling, typing in fieldsDelayed or stepped cursor followers
Cut (instant)0ms hard cutprefers-reduced-motion: reduce (automatic override), initial page render at rest, modal open/closeUnrequested entrance animations on first paint
Hook API@/hooks/use-on-twos

Sampling an easing curve on twos

Instead of linear steps, useOnTwos quantizes time into 83.33ms buckets and evaluates a cubic ease-out spacing chart at each held pose—giving fast initial spacing and a crisp settle.

Install motion hooks
npx shadcn@latest add https://frame12.dev/r/use-on-twos.json
hooks/use-on-twos.ts
export const FRAME_MS = 1000 / 12; // 83.33ms ("on twos")
const easeOut = (t: number) => 1 - Math.pow(1 - t, 3);

// Inside requestAnimationFrame:
const k = Math.min(frames, Math.floor(elapsed / FRAME_MS) + 1);
const progress = easeOut(k / frames);