# Stat counter

A big number counts up from zero (pure CSS @property animation, so it seeks perfectly), with label and delta pill. 4 s, 16:9.

- Category: chart · Tags: number, counter, kpi, stat, data
- Spec: 1920×1080 @ 30 fps · 4s (16:9)
- License: CC0 · Tier: free
- JSON: https://gimgs.net/motion/stat-counter.json
- Kind: html
- Template HTML: https://gimgs.net/motion/stat-counter/template.html
- Player: https://gimgs.net/motion/stat-counter/play?p=<base64url JSON params>[&t=<seconds>]
- Credits: gimgs — https://gimgs.net/motion
- Page: https://gimgs.net/motion/stat-counter.html

## Params

| name | type | default | description |
|---|---|---|---|
| `value` | integer | `12500` | Final integer value (no thousands separators — add them via suffix/prefix text if needed) |
| `prefix` | string | `""` | Text before the number ($, €, ~) |
| `suffix` | string | `"+"` | Text after the number (+, %, k, M) |
| `label` | string | `"Templates rendered"` | Uppercase label under the number |
| `delta` | string | `"▲ 38% this month"` | Small pill (empty string hides it) |
| `accent` | string (color) | `"#22c55e"` | Pill, underline colour |
| `bg` | string (color) | `"#0b0c10"` | Background |
| `countSeconds` | number | `2.2` | How long the count-up takes |

Default params (base64url for `?p=`): `eyJ2YWx1ZSI6MTI1MDAsInByZWZpeCI6IiIsInN1ZmZpeCI6IisiLCJsYWJlbCI6IlRlbXBsYXRlcyByZW5kZXJlZCIsImRlbHRhIjoi4payIDM4JSB0aGlzIG1vbnRoIiwiYWNjZW50IjoiIzIyYzU1ZSIsImJnIjoiIzBiMGMxMCIsImNvdW50U2Vjb25kcyI6Mi4yfQ`

## Prompt

## When to use
One KPI on screen: users, revenue, downloads, savings. Reports, pitch videos, year-in-review, product updates.

## How to adapt
- Big numbers read better scaled: `value: 12, suffix: "M"` instead of `12000000`.
- `prefix`/`suffix` are rendered through CSS `content`, so keep them short and plain (currency symbols and % are fine).
- The count-up is a CSS animation on a registered custom property (`@property --n`), which is why any frame `t` renders the exact same digits — needs Chromium ≥ 85 (Playwright/Chrome headless are fine).
- Timeline: grid fade 0–1 s, pop 0.2–0.8 s, count 0.3 → 0.3 + `countSeconds`, label 1.2–1.8 s, delta 1.8–2.4 s, underline 0.4–2.0 s.
- Sequence several KPIs by rendering one clip per stat with the same `accent`/`bg`.

## Do not change
`window.motion`, `#stage` size, `@property --n`, `animation-fill-mode: both`.

## Flow

1. **Pick the stat**
   Round it; scale with suffix (k/M).
2. **Render**
   `frames 0…119 at t=i/30 → ffmpeg -framerate 30 -i frame-%04d.png -c:v libx264 -pix_fmt yuv420p stat.mp4`

## Render

Open frame_url in a headless browser with viewport width×height (deviceScaleFactor 1), wait for document.documentElement.dataset.motionReady === '1', screenshot; repeat for t = i/fps (i = 0…frames-1); then `ffmpeg -framerate <fps> -i frame-%04d.png -c:v libx264 -pix_fmt yuv420p out.mp4`. Or drop ?t to play it live.

## Contract

Self-contained HTML. Declares window.motion = { width, height, fps, duration, params, apply(params) [, seek(t)] }; root element #stage sized width×height; animations are CSS/WAAPI with animation-fill-mode: both so any t can be seeked via document.getAnimations(). The host runtime (injected by /play) merges ?p params, calls apply(), scales #stage to the viewport and, when ?t is given, pauses and seeks deterministically.
