Hand Count
A number that counts up in stop-motion poses, rewritten by hand at every pose
Installation
$ pnpm dlx shadcn@latest add @remocn/hand-countUsage
Every pose the value advances and the digits are written again — same
number, different hand. That re-drawing is the whole illusion, and it is what
separates this from every other number in the registry: rolling-number and
number-wheel glide a mechanical typeface, which is right for a product UI and
wrong for a paper scene.
The component fills its parent with position: absolute; inset: 0, so give it
a frame and it centres itself inside it.
// src/Root.tsx
import { AbsoluteFill, Composition, Sequence } from "remotion";
import { HandCount, handCountDuration } from "@/components/remocn/hand-count";
const COUNT_AT = 12;
const COUNT_FOR = 14;
const COUNT_ENDS = handCountDuration({
delay: COUNT_AT,
durationSteps: COUNT_FOR,
});
const PricingScene = () => (
<AbsoluteFill style={{ background: "#f1eee7" }}>
<Sequence durationInFrames={COUNT_ENDS + 30}>
<HandCount
to={19}
delay={COUNT_AT}
durationSteps={COUNT_FOR}
prefix="$"
decimals={2}
suffix="/mo"
/>
</Sequence>
</AbsoluteFill>
);
export const RemotionRoot = () => (
<Composition
id="HandCount"
component={PricingScene}
durationInFrames={COUNT_ENDS + 30}
fps={30}
width={1280}
height={720}
/>
);A year-in-review stat
The affixes are written in the same hand as the digits, so a unit reads as part of the number rather than a label stuck beside it.
<div style={{ position: "relative", width: 720, height: 220 }}>
<HandCount to={1284} suffix=" commits" durationSteps={18} fontSize={120} />
</div>Sizing the scene
handCountDuration returns the frame the number actually settles on, which
is not always delay + durationSteps * step. The stop-motion clock saturates on
the quantized frame, so an off-grid delay pushes the landing to the next pose
boundary — a <Sequence> sized by the naive sum can cut up to step - 1 frames
before the number arrives.
handCountDuration({ delay: 0, durationSteps: 12, step: 3 }); // 36
handCountDuration({ delay: 1, durationSteps: 12, step: 3 }); // 39, not 37Keep delay a multiple of step and the two agree.
Choosing the ease
in-out is the default and it is the one to keep for a headline number. It
opens on a barely-there move, does most of the counting through the middle, and
settles gently — so every pose in the run carries a visible digit change.
out front-loads the movement hard: at twelve poses it puts almost a quarter
of the count into the very first pose and leaves the last third of the run
barely moving. That is right when the number is a supporting detail arriving
under something else, and wrong when it is the thing being watched.
A literal spring is deliberately not on offer here. Springs start fast by
construction, which is the same problem as out, and the overshoot would show
a price climbing past its own figure before falling back to it.
Landing and holding still
The value lands on to exactly — it never stops at 99.7 — and the jitter
freezes the moment it does. A headline number that keeps twitching for the rest
of the shot is a distraction, not a texture; ambient motion in this kit is
paper-wobble's job.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
torequired | number | — | Value the count lands on, exactly |
from | number | 0 | Value it starts at; above `to` it counts down |
delay | number | 0 | Local frame counting starts; keep it a multiple of step |
durationSteps | number | 18 | Poses spent counting |
decimals | number | 0 | Decimal places; 0 never renders a decimal point |
prefix | string | "" | Written before the number in the same hand |
suffix | string | "" | Written after the number in the same hand |
fontSize | number | 96 | Size of the written number |
color | string | "#26242c" | Ink colour |
weight | 400 | 500 | 600 | 700 | 700 | Font weight |
ease | "in-out" | "out" | "linear" | "in-out" | Shape of the count; `in-out` starts and settles gently, `out` front-loads the movement |
align | "left" | "center" | "center" | Alignment inside the frame it fills |
fontFamily | string | Caveat | Override the handwritten face |
seed | string | "count" | Seed for the per-pose glyph jitter |
step | number | 3 | Frames per stop-motion pose |