promo-video
This skill should be used when the user asks to "make a sale video", "create a promo video", "build a discount/percentage-off reveal", "animate a was→now price with a strike-through", "do a Black Friday / flash-sale / clearance video", "make a promo-code reveal with a countdown", or "batch a promo template across many products". Covers the offer-announcement arc — discount reveal, price strike-through, promo code, urgency, product shot, CTA — and product+offer data → many videos.
1How It Works & Mechanism
Cardinal Rule #1: Numbers are information — prices and discounts must be derived, never input twice.
1. **The number is the message — keep it exact.** Prices and percentages must be computed, never typed twice. Derive `now` and the `% off` from `was` and the discount so they can't disagree.
Store only the ground truth: now and pct are never persisted and must be derived from was/discount, preventing self-contradictions.
One object per product. `now` and `pct` are NOT stored — they are derived, so they can never contradict `was` and `discount`. Store only the source of truth.
Strike first, drop the price second — the wrong sequence will be perceived as a glitch.
Order matters: strike the old price *first*, then drop the new one. Striking and counting at the same instant reads as a glitch.
Round currency to cents before formatting, and render using Intl.
Compute every displayed figure from the offer in one place. Round to cents *before* formatting so the price a viewer reads is exactly the price they pay.
Validate row by row before batching, and fail loud on bad rows: silently rendering bad rows is precisely how "$470 gets marked as $47".
A batch that silently renders a bad row is how a $470 item ships labelled "$47". Validate every offer before rendering and stop the run on the first bad one.
Urgency must be genuine: provide a deadline or a realistic countdown; never use fake timers.
Urgency lifts conversion only when it's real. Show a concrete deadline ("Ends Sun 11:59pm") or a live-looking countdown — never a fake timer.
One focus per frame: one discount, one code, one CTA; if there are two offers, make two separate videos.
2. **One offer, one focus per frame.** A single discount, a single code, a single CTA. Competing offers kill the urgency. If there are two deals, make two videos.
Choose presentation by price tier: use percentages for high prices, and fixed amounts for low prices.
Choose the framing by price: percentage for higher-priced items ("40% OFF" beats "$32 off"), a flat amount for low-priced items ("$5 off" beats "8% off").
The verification loop focuses on "is the money right, is the image present": numeric accuracy and product image loading are non-negotiable.
This is heavy-tier: the deliverable is an MP4, not a live page. A promo carries exact money — the verify pass is mostly *are the numbers right and is the product image actually there*.
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| package | remotion(frame/spring/interpolate/Img/Sequence) |
| cli | node fs(校验时检查图片是否存在) |
| package | Intl.NumberFormat / Intl.DateTimeFormat(运行时内置,无第三方包) |
| cli | npx remotion render(逐 offer 渲染) |
| cli | ffmpeg / ffprobe(仓库共享 contact-sheet.sh、probe-mp4.sh) |
| network | iart.ai(reference 尾部推广链接,具 utm 参数;仅读者点击才请求) |
4Recommended Governance & Evidence
- scripts/: Executes local scripts or commands: The skill directory contains no built-in scripts; SKILL.md/reference directs the agent to execute `node` (for validation/derivation, with source code embedded in documentation) and `npx re`
- 网络端点: Declares external network access: No operational API calls during runtime; price and product data come from local offers/*.json, and product images come from the local public/ directory.
- 本地工作目录: File reads/writes and artifact outputs: Writes `out/<name>.mp4` (one per offer), `out/f-discount.png`, and `out/f-price.png` within the user project.
5Second-Pass Verification
- [discrepancy] 「全部为帧纯函数、禁用 Date.now()」与 reference 实现是否一致 — SKILL.md 明确要求所有动画值为 useCurrentFrame() 纯函数(no CSS transitions, timers, Date.now()),其自身示例也写「derived from a deadline prop (deterministic per frame)」;但 promo-components.md 的 Countdown 实际用 `const remaining = Math.max(0, (end - Date.now()) / 1000 - frame / fps);`——引入挂钟时间,同一 props 在不同时刻渲染会得到不同剩余时间,不可复现。
- [discrepancy] 组件 import 名是否可运行 — promo-components.md 的 composition 写成 AbstractFill(Remotion 并无该导出,应为 AbsoluteFill),且使用处同样写 <AbstractFill …>;照抄会直接 import 失败。
- [ok] 折扣公式是否两处一致 — SKILL.md 的 Math.round((1 - now / was) * 100) 与 priceModel 的 Math.round((1 - now / o.was) * 100) 同式同精度。
- [ok] 校验清单是否与 SKILL.md 文字一致 — validateOffer 覆盖 now≥0 且 <was、discount∈(0,1)(当 now 未给)、code 非空、deadline 可解析、图片存在;与 SKILL.md 的 now < was / code present / image exists 一致且更强。
- [ok] 节拍/时长的一致性 — SKILL.md 的 10s 节拍表与 reference 的帧调度(hook 0 / badge 27 / price 60 / code 210 / cta 270)及 --frames=0-299(10s@30fps)一致。
- [ok] 「先划线后落价」顺序的实现 — PriceDrop 的 strike 从 f=0 起(0–12 帧画完),新价 spring 从 f-8 起跑;与 SKILL.md 的 strike the old price first 时序意图一致。
6Verdict & Scenarios
0c3e003bf0617b1c…