countdown-video
This skill should be used when the user asks to "make a countdown timer video", "create a countdown animation", "build a stream starting soon countdown", "do an event/launch/sale countdown", "animate a mm:ss or dd:hh:mm:ss timer", "add digit roll/flip transitions to a counter", or "loop a countdown background". Covers frame-accurate (drift-free) timers, digit flip/roll motion, time formatting, and a configurable duration prop.
1How It Works & Mechanism
There is only one golden rule throughout: the remaining time must be derived from an authoritative clock; storing remaining values in state and decrementing them is strictly forbidden.
Never store remaining time in state and decrement it. Recompute it every frame.
Explain mechanistically why setInterval drifts (callbacks fire at least 1000ms late, compounding when tabs are throttled or rendering frames lag).
A `setInterval(fn, 1000)` countdown **drifts** — the callback fires *at least* 1000 ms later, never exactly, and stacks up when the tab is throttled or a render frame is slow.
On the Remotion side, the dependable clock is useCurrentFrame(): frame N strictly equals N/fps seconds, making "frame accuracy" true by construction.
`useCurrentFrame()` is deterministic: frame N is always rendered at exactly `N/fps` seconds.
Use ceil for rounding and clamp to 0, ensuring "1" displays for a full second and the final frame lands precisely at 00:00.
const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));
Duration is a prop rather than a constant and is bound to durationInFrames, preventing surplus frames after the countdown hits 00:00.
Set the composition's `durationInFrames` to `durationInSeconds * fps` so the video is exactly as long as the countdown.
The web (non-rendering) execution path uses rAF to drive performance.now(), repainting only when the displayed second changes.
if (remaining !== lastShown) { render(remaining); lastShown = remaining; }
The typography layer uses tabular-nums to fix digit widths, preventing layout shifts as numbers tick.
Always set `font-variant-numeric: tabular-nums` (or a monospace digit font) so digits keep equal width and the layout does not jitter as numbers change.
The core principle of digit animation is "animate position, not text content," and it must be frame-driven rather than relying on CSS transitions.
Animate the *position*, not the text content, so the change is a glide, not a pop.
Livestream/standby screens require seamless looping backgrounds: the motion period must evenly divide the timeline, read via frame % loopFrames.
read it as `frame % loopFrames` so there is no visible seam at the wrap
Acceptance criteria are formulated as a mathematical equation: frame N must display ceil(duration − N/fps), landing on 00:00 on the final frame.
at frame N, displayed = ceil(durationInSeconds - N/fps) (e.g. 300s @ 30fps, frame 4500 → 150s → "02:30")
The reference translates these disciplines into runnable components and includes a fixed-date web variant (recalculated via target − Date.now() per frame with timezone handling).
Register it so `durationInFrames` matches the countdown exactly — the video ends when the timer does:
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| cli | npx remotion still / render / compositions |
| cli | scripts/contact-sheet.sh + scripts/probe-mp4.sh(仓库根共享验证工具箱,SKILL.md 明列) |
| package | ffmpeg / ffprobe(系统依赖) |
| cli | npx -y playwright screenshot(scripts/seek-shot.sh,仓库级 Light tier 通用件) |
4Recommended Governance & Evidence
- scripts/: Local script or command execution: (Verification loop explicitly specified in SKILL.md) `npx remotion still Countdown out/f-*.png --frame=N --pro
- 网络端点: External network access declaration: (Optional, usually bypassed) `scripts/seek-shot.sh` invokes `npx -y playwright screenshot`: fetches from npm on first run
- 本地工作目录: File I/O and artifact output: Remotion projects and out/countdown.mp4, out/demo.gif built by the agent following this skill
5Second-Pass Verification
- [ok] 「禁止 setInterval 递减」是否在 reference 的示例实现里也真的遵守(防第一遍只信 SKILL.md 的说法) — reference 全量 grep:countdown-cookbook.md 组件 `const frame = useCurrentFrame();`,注册处 `durationInFrames={DURATION_SECONDS * FPS}`;到日期的 web 组件 `Math.max(0, target - Date.now())` 每帧重算并自述 "never a decrement";digit-flip.md 两个组件同样 `const frame = useCurrentFrame();`。setInterval 全 skill 仅作反例出现 1 次。
- [ok] 「倒计时结束时视频正好结束」是否真被约束住 — SKILL.md 明写 `durationInFrames` = `durationInSeconds * fps`;cookbook 给出 "Register it so `durationInFrames` matches the countdown exactly — the video ends when the timer does:" 的注册片段;收尾清单第 2 条把它列为验收项。文档、示例、验收三者一致,不是单向声明。
- [unlocatable] 「每分钟约丢 1 秒」的漂移量级是否有据 — SKILL.md 写 "Reports of ~1 second lost per minute are common.",但仓库内无基准脚本、无引用、无数据文件;本次为只读侦查且不运行计时实验,故不验证该数值,仅记为文档声明,不在结论中当已核事实使用。
- [ok] seek-shot.sh(Light tier)对本 skill 是否可用(防「Packaged helper」被读者误解为三条脚本都属本 skill) — scripts/README.md 把 seek-shot 归为「standalone HTML + `?t=N` harness」的 Light tier;本 skill 交付是 Remotion 组件(Heavy tier),SKILL.md 的 Packaged helper 行只点名 contact-sheet 与 probe-mp4。不存在悬空引用,也不存在被夸大的能力。
- [ok] 三仓库共享脚本是否同内容(跨包一致性) — md5 逐字节比对:contact-sheet.sh b7dcc28034c9bf8fda5d48683e54d8b5、probe-mp4.sh 167b7977c50f492014e1a6fef8c9ed12、seek-shot.sh 24a4130cd1e64874154429b0b4af12da,iart-tiktok / iart-text-message / iart-youtube 三包完全相同。
- [ok] SKILL.md 是否声称了 reference 未兑现的能力(防文档夸大) — SKILL.md 承诺的四件事——可配置 duration、mm:ss 与 dd:hh:mm:ss、drift-free 计时、无缝循环背景——在 countdown-cookbook.md 与 digit-flip.md 里各有对应实现(§1 组件、§2 到日期、§4 循环、digit-flip 两组件),逐项可定位,无空头承诺。
6Verdict & Scenarios
2730e6053383cccb…