promo-video

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Sale Promo Video

促销销售视频

Make a punchy 6–15s sale spot that announces ONE offer and makes a viewer act now: the discount lands, the price drops from was→now with a strike-through, the promo code and deadline read clean, the CTA closes. This is the offer announcement — not a feature ad, not a testimonial. Energy, accurate numbers, urgency.
制作一段6–15秒的有力促销短片,聚焦单一优惠,促使观众立即行动:折扣揭晓、原价划掉后现价显示、优惠码和截止日期清晰展示、行动号召(CTA)收尾。这是优惠公告视频——不是功能广告,也不是客户证言。要充满活力、数据准确、营造紧迫感。

When to use

使用场景

  • Discount / percentage-off reveals ("40% OFF", "$20 off").
  • was→now price drops with an animated strike-through.
  • Black Friday / flash sale / clearance / launch-week / seasonal spots.
  • Promo-code reveals with a deadline or countdown.
  • One promo template rendered across many products/offers (price table → N videos).
For performance-ad variants use
ad-creative-video
; for customer quotes use
testimonial-video
. Stay on the offer.
  • 折扣/百分比减免展示(如“40% OFF”“立减20美元”)。
  • 带动画划掉效果的原价→现价降价展示。
  • 黑色星期五/闪购/清仓/首发周/季节性促销短片。
  • 带截止日期或倒计时的优惠码展示。
  • 基于同一促销模板为多款产品/优惠生成视频(价格表→N个视频)。
如需制作效果广告变体,请使用
ad-creative-video
;如需客户证言视频,请使用
testimonial-video
。请始终聚焦优惠本身。

The arc

视频流程

A sale spot is short and ordered. Each beat does one job; never stack two.
BeatJobBudget (of 10s)
HookBrand + occasion ("BLACK FRIDAY") snaps in0–1.5s
Discount revealThe big number lands ("40% OFF")1.5–4s
was→nowOld price strikes through, new price counts down4–7s
ProductOne hero shot, the thing on offer(under price, 4–8s)
Code + urgencyPromo code + deadline, held still7–9s
CTA"SHOP NOW" + URL, clean hold9–10s
Scale tighter for 6s (cut the product beat) or looser for 15s (let each beat breathe). Never lengthen the hook.
促销短片时长较短且流程清晰,每个环节仅完成一项任务,切勿叠加多项内容。
环节作用时长占比(以10秒为例)
钩子品牌+活动主题(如“黑色星期五”)快速切入0–1.5秒
折扣揭晓核心折扣数字呈现(如“40% OFF”)1.5–4秒
原价→现价原价划掉,现价倒计时显示4–7秒
产品展示一款主打产品的镜头(与价格同步展示)(叠加在价格下方,4–8秒)
优惠码+紧迫感优惠码+截止日期,静态展示7–9秒
行动号召(CTA)“立即购买”+网址,清晰静态展示9–10秒
6秒版本可压缩流程(去掉产品展示环节),15秒版本可适当延长每个环节的时长,但钩子环节切勿拉长。

Two non-negotiable rules

两项不可妥协的规则

  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. A promo video that shows the wrong price is worse than no video. Round currency to cents, format with
    Intl.NumberFormat
    , and use
    tabular-nums
    so digits don't jitter mid-count.
  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.
  1. 数字是核心信息——务必准确。 价格和百分比必须通过计算得出,切勿手动重复输入。从原价和折扣推导出现价和折扣比例,避免数据不一致。展示错误价格的促销视频不如不做。货币金额保留到分,使用
    Intl.NumberFormat
    格式化,并启用
    tabular-nums
    确保数字在倒计时过程中不会抖动。
  2. 单视频单优惠,单帧单焦点。 单个折扣、单个优惠码、单个CTA。多个优惠并存会削弱紧迫感。若有两项优惠,请制作两个独立视频。

The signature move: was→now with a strike-through

核心效果:带划掉效果的原价→现价展示

Three things animate together and must stay in sync: the strike-through line draws across the old price, the new price counts down to its final value, and a micro-overshoot punctuates the landing. Drive all three from
useCurrentFrame()
— never a CSS transition or a JS timer — so a server render never desyncs.
jsx
import { useCurrentFrame, interpolate, spring, useVideoConfig } from "remotion";

const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });

export const PriceDrop = ({ was = 79.0, now = 47.4, startAt = 30 }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const f = frame - startAt;

  // 1. strike-through line draws L→R over ~0.4s
  const strike = interpolate(f, [0, 12], [0, 100], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
  // 2. new price counts from `was` down to `now`, eased
  const t = spring({ frame: f - 8, fps, config: { damping: 18, mass: 0.6 } });
  const shown = was + (now - was) * t;
  // 3. landing pop on the final value
  const pop = interpolate(t, [0.9, 1], [1.12, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });

  return (
    <div style={{ fontVariantNumeric: "tabular-nums", textAlign: "center" }}>
      <span style={{ position: "relative", opacity: 0.55, fontSize: 48 }}>
        {usd.format(was)}
        <span style={{ position: "absolute", left: 0, top: "50%", height: 4, background: "#ff3b3b",
                       width: `${strike}%`, transformOrigin: "left" }} />
      </span>
      <div style={{ fontSize: 96, fontWeight: 800, transform: `scale(${pop})` }}>
        {usd.format(shown)}
      </div>
    </div>
  );
};
Order matters: strike the old price first, then drop the new one. Striking and counting at the same instant reads as a glitch. See
references/promo-components.md
for the full composition (discount badge, code reveal, countdown, CTA) wired to props.
三个动画需同步进行:划掉线从左到右覆盖原价、现价倒计时至最终数值、微幅过冲效果强化落地感。所有动画均由
useCurrentFrame()
驱动——切勿使用CSS过渡或JS计时器,避免服务器渲染时出现不同步问题。
jsx
import { useCurrentFrame, interpolate, spring, useVideoConfig } from "remotion";

const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });

export const PriceDrop = ({ was = 79.0, now = 47.4, startAt = 30 }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const f = frame - startAt;

  // 1. 划掉线在约0.4秒内从左到右绘制完成
  const strike = interpolate(f, [0, 12], [0, 100], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
  // 2. 现价从原价平滑过渡到最终价格
  const t = spring({ frame: f - 8, fps, config: { damping: 18, mass: 0.6 } });
  const shown = was + (now - was) * t;
  // 3. 最终价格落地时的微幅放大效果
  const pop = interpolate(t, [0.9, 1], [1.12, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });

  return (
    <div style={{ fontVariantNumeric: "tabular-nums", textAlign: "center" }}>
      <span style={{ position: "relative", opacity: 0.55, fontSize: 48 }}>
        {usd.format(was)}
        <span style={{ position: "absolute", left: 0, top: "50%", height: 4, background: "#ff3b3b",
                       width: `${strike}%`, transformOrigin: "left" }} />
      </span>
      <div style={{ fontSize: 96, fontWeight: 800, transform: `scale(${pop})` }}>
        {usd.format(shown)}
      </div>
    </div>
  );
};
顺序很重要:先划掉原价,再显示现价。同时进行划掉和倒计时会显得像故障效果。完整组件(折扣徽章、优惠码展示、倒计时、CTA)可参考
references/promo-components.md
,已绑定属性配置。

Discount reveal & promo code

折扣揭晓与优惠码

The percentage is the loudest element on screen — bigger than the brand, bigger than the product. Compute it; don't type it.
js
const pct = Math.round((1 - now / was) * 100);   // 79 → 47.4  ⇒  40
const off = was - now;                            // for "$X off" framing
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"). Reveal the code as a tappable-looking chip (mono font, dashed border, light background) and hold it still — a moving code can't be read or screenshotted. Codes that encode urgency convert better:
FLASH40
,
TODAY20
,
LASTCHANCE
.
百分比数字是屏幕上最醒目的元素——比品牌和产品的字号更大。务必通过计算得出,切勿手动输入。
js
const pct = Math.round((1 - now / was) * 100);   // 79 → 47.4  ⇒  40
const off = was - now;                            // 用于“立减X美元”的展示方式
根据价格选择展示方式:高价商品用百分比(“40% OFF”比“立减32美元”更有吸引力),低价商品用固定金额(“立减5美元”比“8% OFF”更直观)。优惠码需展示为类似可点击的芯片样式(等宽字体、虚线边框、浅色背景)并保持静态——移动的优惠码无法被读取或截图。带有紧迫感的优惠码转化率更高:
FLASH40
TODAY20
LASTCHANCE

Urgency that's honest

真实的紧迫感

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. Viewers need ~2s to read a time-sensitive offer, so hold the deadline beat; don't flash it. Keep the countdown on its own line near the code, not racing the price.
js
// frame → mm:ss remaining, derived from a deadline prop (deterministic per frame)
const remaining = Math.max(0, deadlineSec - frame / fps);
const mm = String(Math.floor(remaining / 60)).padStart(2, "0");
const ss = String(Math.floor(remaining % 60)).padStart(2, "0");
只有真实的紧迫感才能提升转化率。展示具体的截止日期(“截止周日23:59”)或实时倒计时——切勿使用虚假计时器。观众需要约2秒时间读取限时优惠,因此截止日期环节需保持静态展示,切勿一闪而过。倒计时需单独一行放在优惠码附近,不要与价格动画同步。
js
// 根据截止时间属性计算剩余时间(基于帧的确定性计算)
const remaining = Math.max(0, deadlineSec - frame / fps);
const mm = String(Math.floor(remaining / 60)).padStart(2, "0");
const ss = String(Math.floor(remaining % 60)).padStart(2, "0");

Motion language: energetic, not chaotic

动效风格:充满活力但不混乱

Sale spots earn their punch from snappy, consistent motion: fast spring entrances (
damping ~12
, overshoot), hard cuts on accents, one accent color for savings (a hot red/green) reused everywhere. Keep ONE enter curve across all beats so speed reads as confidence. Avoid drift/parallax that softens urgency. Land the discount reveal on the strongest audio beat if scored.
促销短片的冲击力来自简洁一致的动效:快速弹簧入场(
damping ~12
,带过冲)、重音处硬切、所有优惠元素使用同一强调色(亮红/亮绿)。所有环节使用统一的入场曲线,让速度感传递出自信。避免削弱紧迫感的漂移/视差效果。若添加音效,折扣揭晓需同步最强的音频节拍。

Template × data — many products, one template

模板×数据——多款产品,一个模板

The payoff of code-driven promos: a price/offer table renders a video per product, with prices guaranteed accurate because they come from the data, not a designer retyping. Make the offer an input prop; hardcode nothing.
jsx
// one composition, different offer → different video
export const Promo = ({ offer }) => { /* reads offer.was/now/code/deadline/img — no constants */ };
bash
undefined
代码驱动促销的优势:价格/优惠表可为每个产品生成一个视频,价格由数据保证准确,无需设计师手动重复输入。将优惠设为输入属性,切勿硬编码任何内容。
jsx
// 一个组件,不同优惠配置→不同视频
export const Promo = ({ offer }) => { /* 读取offer.was/now/code/deadline/img — 无硬编码常量 */ };
bash
// 为/offers目录下的每个优惠配置生成促销视频
for f in offers/*.json; do
  npx remotion render Promo "out/$(basename "$f" .json).mp4" --props="$f"
done
渲染前需验证每一行数据(现价<原价、优惠码存在、图片有效),确保错误数据提前暴露,避免输出价格错误的视频。将颜色/字体/CTA统一放在主题对象中,确保200个促销视频保持品牌一致性,仅数字部分变化。优惠数据结构、验证规则和批量脚本可参考
references/promo-components.md

render the promo for every offer row in /offers

输出检查清单

for f in offers/*.json; do npx remotion render Promo "out/$(basename "$f" .json).mp4" --props="$f" done

Validate each row before render (now < was, code present, image exists) so a bad row fails loud instead of shipping a wrong price. Keep colors/fonts/CTA in one theme object so 200 promos stay brand-locked and only the numbers change. See `references/promo-components.md` for the offer schema, validation, and batch script.
  • 现价和折扣比例由原价计算得出——切勿手动重复输入;使用
    Intl.NumberFormat
    格式化并保留到分,启用
    tabular-nums
  • 先绘制划掉线,再显示现价;两者均由
    useCurrentFrame()
    纯函数驱动。
  • 单视频单优惠、单优惠码、单CTA;折扣是最醒目的元素。
  • 紧迫感来自真实的截止日期/倒计时,展示时长≥2秒,非虚假闪烁计时器。
  • 所有环节使用统一的入场曲线和优惠强调色。
  • 优惠为输入属性;批量渲染前验证每一行数据(现价<原价)。

Output checklist

交付与验证(渲染静帧→MP4)

  • now
    and
    % off
    are computed from
    was
    — never typed twice; rounded and
    Intl.NumberFormat
    -formatted with
    tabular-nums
    .
  • Strike draws first, new price drops second; both pure functions of
    useCurrentFrame()
    .
  • One offer, one code, one CTA per video; discount is the loudest element.
  • Urgency is a real deadline/countdown, held ≥2s, not a fake flashing timer.
  • One enter curve and one savings accent color across all beats.
  • Offer is an input prop; each data row is validated (now < was) before batch render.
打包工具
scripts/
目录):使用
scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png
拼接静帧,然后用
scripts/probe-mp4.sh out.mp4 [WxH] [fps]
验证编码。详情见
scripts/README.md
交付物为MP4视频,而非在线页面。促销视频涉及准确的价格信息——验证环节主要检查数据是否正确、产品图片是否存在
输出规范:
  • 一个Remotion项目,包含已注册的组件(
    <Composition>
    + zod
    schema
    +
    defaultProps
    );折扣揭晓、划掉线、倒计时和CTA均由
    useCurrentFrame()
    纯函数驱动(无CSS过渡、计时器、
    Date.now()
    )。
  • 产品图片通过
    staticFile()
    加载,使用
    delayRender
    /
    continueRender
    确保主图(和品牌字体)在帧渲染前加载完成;现价/折扣比例由原价计算得出,切勿手动重复输入。
  • 交付物=渲染后的
    out/*.mp4
    视频+完整项目文件(可根据优惠配置重新渲染)。
验证流程——渲染静帧→检查→编码。 先输出低成本PNG静帧,确认数据和图片正确后再生成视频。
bash
undefined

Deliver & verify (rendered stills → MP4)

基于最终使用的优惠属性渲染精准帧的静帧,而非默认属性

Packaged helper (
scripts/
): tile your stills with
scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png
, then assert the encode with
scripts/probe-mp4.sh out.mp4 [WxH] [fps]
. See
scripts/README.md
.
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.
Output contract:
  • A Remotion project with the composition registered (
    <Composition>
    + zod
    schema
    +
    defaultProps
    ); discount reveal, strike-through, count-down and CTA all pure functions of
    useCurrentFrame()
    (no CSS transitions, timers,
    Date.now()
    ).
  • Product image loaded via
    staticFile()
    , gated with
    delayRender
    /
    continueRender
    so the hero shot (and brand font) is present before the frame renders —
    now
    /
    % off
    computed from
    was
    , never typed twice.
  • Deliverable = the rendered
    out/*.mp4
    plus the project (re-render per offer row).
Verify loop — render stills → inspect → encode. Cheap PNGs first, video only once the numbers and image are right.
bash
undefined
npx remotion still Promo out/f-discount.png --frame=60 --props=offer.json # “40% OFF”核心数字已呈现 npx remotion still Promo out/f-price.png --frame=120 --props=offer.json # 原价已划掉,现价稳定显示 npx remotion still Promo out/f-cta.png --frame=270 --props=offer.json # 优惠码+截止日期+CTA结尾帧

Frame-exact stills WITH THE OFFER PROPS YOU'LL SHIP, not defaultProps

结束帧=总帧数-1(可通过npx remotion compositions查看)

npx remotion still Promo out/f-discount.png --frame=60 --props=offer.json # "40% OFF" big number landed npx remotion still Promo out/f-price.png --frame=120 --props=offer.json # was struck through, now settled npx remotion still Promo out/f-cta.png --frame=270 --props=offer.json # code + deadline + CTA end-card

检查每张PNG的**准确性**(原价/现价/折扣比例准确;原价上有划掉线;优惠码拼写正确;产品图片正确)和**是否存在异常**(图片空白/未加载、数字抖动或错误、价格溢出/超出画布、划掉线位置错误、CTA/优惠码超出安全区域、比例错误/黑边)。

```bash

end frame = durationInFrames - 1 (npx remotion compositions reads it)

确认静帧无误后再生成视频:


Inspect each PNG for **fidelity** (exact `was`/`now`/`% off`; was→now strike-through drawn over the old price; promo code spelled right; product image is the right one) AND **artifacts** (image blank/not loaded, digits jittering or wrong, price overflow/off-canvas, strike on the wrong line, CTA/code outside the title-safe area, wrong aspect/letterboxing).

```bash
npx remotion render Promo out/promo.mp4 --props=offer.json npx remotion render Promo out/promo.gif --props=offer.json --codec=gif # README预览动图

**批量渲染(一个模板,多款产品):** 基于价格表生成N个视频时,先验证一个代表性优惠的静帧,再批量渲染全目录——提前发现一个错误价格或空白图片,比输出N个错误视频更有价值。

**完成前检查:**
1. 折扣/价格/CTA环节的静帧渲染正常,无错误,产品图片已加载(非空白)。
2. 原价/现价/折扣比例准确(由原价计算得出);原价已划掉,现价稳定,优惠码/截止日期正确。
3. 所有动效由帧驱动——无CSS过渡/计时器/`Date.now()`/`Math.random()`。
4. 最终使用的优惠属性渲染正确,而非仅默认属性。
5. 完整MP4已编码并可正常播放;(可选)已生成README预览GIF。

Only after stills are clean:

参考文件

npx remotion render Promo out/promo.mp4 --props=offer.json npx remotion render Promo out/promo.gif --props=offer.json --codec=gif # README first-screen proof

**Batch (one template, many products):** when rendering the price table → N videos, verify ONE representative offer's props via stills before batch-rendering the catalog — a wrong price or blank image caught once beats shipping it N times.

**Before you finish:**
1. Stills render cleanly at discount / price / CTA — no errors, product image actually loaded (not blank).
2. `was`/`now`/`% off` are exact (computed from `was`); strike-through over the old price, new price settled, code/deadline correct.
3. All motion frame-driven — no CSS transitions / timers / `Date.now()` / `Math.random()`.
4. The **shipped** offer props render correctly, not just `defaultProps`.
5. Full MP4 encoded and plays; (optional) GIF rendered for the README.
  • references/promo-components.md
    ——一个完整可运行的Remotion促销组件,由优惠属性驱动:包含折扣徽章、原价→现价划掉+倒计时、优惠码芯片、实时倒计时、CTA结尾帧——以及优惠数据结构、逐行验证规则、主题对象、模板×数据批量渲染脚本(用于生成N个产品视频)。

Reference files

  • references/promo-components.md
    — a complete runnable Remotion promo composition driven by an offer prop: discount badge, was→now strike-through + count-down, promo-code chip, live countdown, and CTA end-card — plus the offer schema, per-row validation, the theme object, and the template×data batch render script for N products.