{
  "slug": "counter-confetti",
  "title": "Counter Confetti Celebration",
  "tagline": "A big number counts up with an easeOutQuart sprint and a scale overshoot; a beat before it lands, 52 confetti pieces burst in from both sides on parabolic arcs, an impact ring expands, and the label's letter-spacing tightens to close.",
  "description": "A big number counts up with an easeOutQuart sprint and a scale overshoot; a beat before it lands, 52 confetti pieces burst in from both sides on parabolic arc",
  "category": "data",
  "tags": [
    "shotcraft",
    "remotion",
    "data"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 5.1,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/counter-confetti/poster-17889e35efbd.jpg",
    "video": "https://gimgs.net/motion/media/counter-confetti/video-5da316df61bd.mp4"
  },
  "variants": [
    {
      "key": "counter-confetti",
      "label": "counter-confetti",
      "description": "A big number counts up with an easeOutQuart sprint and a scale overshoot; a beat before it lands, 52 confetti pieces burst in from both sides on parabolic arcs, an impact ring expands, and the label's letter-spacing tightens to close.",
      "use": "",
      "video": "https://gimgs.net/motion/media/counter-confetti/video-5da316df61bd.mp4",
      "poster": "https://gimgs.net/motion/media/counter-confetti/poster-17889e35efbd.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/counter-confetti.html",
  "json_url": "https://gimgs.net/motion/counter-confetti.json",
  "template_url": "https://gimgs.net/motion/counter-confetti/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/counter-confetti.md",
  "updated_at": "2026-09-24T07:39:52.361Z",
  "prompt": "## When to use\n\nThe celebration beat for milestone/achievement numbers: user count, revenue, downloads — any \"pop the champagne\" metric reveal.\n\n## Intention\n\nThe number isn't \"displayed,\" it \"crosses the finish line\": the counting curve is fast-then-slow, like a 100m sprint slowing into a finish-line replay, and the confetti bursts a beat before the number lands — the celebration arrives half a beat ahead of the result, emotion outrunning information, so viewers feel excited before they even read the number clearly.\n\n## Duration & energy\n\n- Duration: About 4.6s (138f@30fps; counting 0.3–2.6s · confetti from 2.4s · settles at 3.3s).\n- Energy: High (a counting wind-up plus a burst payoff — a textbook emotional-peak shot).\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n数字不是\"显示\"出来的，是\"冲线\"的：计数曲线前快后慢像百米冲刺进入慢镜，纸屑在数字到位**之前**抢拍爆开——庆祝比结果先到半拍，情绪压过信息，观众先兴奋再看清数字。\n\n## 动效核心\n- 计数走 easeOutQuart（t=0.06–0.56）：前 1/3 时间跑完 2/3 数值，符合\"冲刺-减速-压线\"的体感；`toLocaleString` 千分位格式\n- 数字 scale 两段：0.2→1.3 冲入（outCubic），计数到位后 1.3→1 回落（outBack 轻弹）——大一号入场、落定收正\n- **抢拍**：纸屑 BURST=0.52 早于计数到位 0.56——庆祝先于结果 0.04（约 5 帧），这半拍是这张卡的灵魂\n- 52 片纸屑真物理：从画面两侧 ±250px 出发，初速 vx 150–450 向中冲 / vy −230~−450 上抛，重力 g 900–1420 拉回，自转 ±750°，每片错峰 ≤0.06\n- 到位冲击环：0.545 起 scale 0.35→2.95 扩散淡出（outQuart）；标签 0.62 淡入且字距 11px→5px 收紧——\"落章\"感\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 计数曲线 | easeOutQuart，0.06–0.56 | 换 outCubic 冲刺感减弱；线性计数是最常见的\"没有情绪\"写法 |\n| 抢拍量 | BURST 提前 0.04 | 提前 0–0.02 读作同拍；>0.08 纸屑像\"别人的庆祝\" |\n| 纸屑数量 | 52 片（8 色调色板） | <30 稀疏不成雨；>80 遮数字。PAL 是\"演出彩纸\"，一般不随品牌换色 |\n| 重力/初速比 | g≈2.5×|vy| | 决定抛物线顶点在画面上 1/3 处；g 太小纸屑飘出画顶 |\n| scale 过冲 | 1.3 峰值 + outBack 回落 | 数字是主角，过冲 >1.5 会撞到标签排版 |\n| 终值 | p*1000（演示值） | 换真实数字：保持\"从 0 滚到终值\"，跳变起点会丢冲刺感 |\n\n## 已知坑\n- 纸屑物理用 `life = u*1.1` 的归一化秒——改 dur 后重力体感会变（同 u 对应不同真实时长），需重调 g/vy\n- 抢拍差 0.04 是硬编码在 BURST 与计数终点两处的常量，改计数窗口时同步平移 BURST 保持提前量\n- 纸屑 opacity 尾段 0.74 起衰减到 0.05——别让纸屑活到 t=1 还在飞，落定段（标签收字距）需要干净背景\n- 配 SFX：计数段 tick 渐密 + BURST 拍 pop/confetti + 到位拍 impact——三层缺一情绪都塌（对照 sound-design.md counter 类）\n\n## 参考实现\ndemos/data/counter-confetti/\n（CounterConfetti.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/data/counter-confetti/CounterConfetti.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/data/counter-confetti/CounterConfetti.tsx\n\nThe component imports shared fixtures from `demos/_fixtures` and helpers from `assets/lib` in the repo; SKILL.md there documents the render workflow.\n",
  "flow": [
    {
      "step": "Read the recipe",
      "note": "Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points."
    },
    {
      "step": "Get the code",
      "tool": "git",
      "cmd": "git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install"
    },
    {
      "step": "Register + render",
      "tool": "remotion",
      "cmd": "npx remotion render <CompositionId> out.mp4   # component: demos/data/counter-confetti/CounterConfetti.tsx"
    },
    {
      "step": "Or re-implement",
      "note": "Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing."
    }
  ],
  "files": [
    {
      "path": "demos/data/counter-confetti/CounterConfetti.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/data/counter-confetti/CounterConfetti.tsx"
    }
  ],
  "contract": "React/Remotion composition (TSX) from the open-source video-shotcraft library (Apache-2.0). It is not a browser-playable template: clone the repo, register the component in a Remotion project and render with `npx remotion render`. The recipe (prompt) describes timing, easing and parameters so you can also re-implement the shot in any engine.",
  "render": {
    "engine": "remotion",
    "frames": 153,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/data/counter-confetti/CounterConfetti.tsx"
    ],
    "note": "Clone https://github.com/Vincentwei1021/video-shotcraft (npm install). The component lives under demos/{category}/{name}/ and imports shared fixtures from demos/_fixtures and helpers from assets/lib. Copy it (with those imports) into a Remotion project, register a <Composition> with the template's width/height/fps/duration in Root.tsx, then `npx remotion render <CompositionId> out.mp4`. SKILL.md in the repo documents the full Claude Code / Codex workflow. The prompt below is the shot recipe (timing, easing, parameter table, pitfalls) — enough to re-implement the shot in HTML/WAAPI or another engine if you prefer."
  },
  "source": "// ===== demos/data/counter-confetti/CounterConfetti.tsx =====\n// counter-confetti — Counter Confetti 数字冲刺纸屑（motion-lab 定稿转原生 Remotion）\n// 大数字 0 → 1000 用 easeOutQuart 冲刺，scale 走 [0.2,1.3,1.3,1] 过冲；数字到位前\n// 5 帧就从两侧 burst 出 8 色纸屑，带重力下落与自转飘散。\n// 设计坐标 480×270（DesignStage 等比放大），参数表数值以此坐标系标定。\nimport React from 'react';\nimport { DesignStage, E, lerp, rand, seg, useT } from '../../_fixtures/Motion';\n\nexport const COUNTER_CONFETTI_DURATION = 138; // 4600ms @30fps\n\nconst PAL = ['#ff6b8b', '#ffb347', '#ffe86b', '#7bff9e', '#5fd8ff', '#6c8cff', '#c86cff', '#ff6cf0'];\nconst BURST = 0.52; // 计数到位(0.56)前\"抢拍\"\n\n// 52 片纸屑的静态参数（种子与 effect.js 完全一致，确定性可复现）\nconst BITS = Array.from({ length: 52 }, (_, i) => {\n  const isRect = rand(i * 3) > 0.4;\n  const w = 5 + rand(i + 2) * 6;\n  const h = isRect ? 8 + rand(i + 5) * 7 : w;\n  const side = i % 2 ? 1 : -1;\n  return {\n    w,\n    h,\n    isRect,\n    color: PAL[i % 8],\n    x0: side * 250, // 从画面两侧出发（px，相对中心）\n    y0: (rand(i + 30) - 0.5) * 60,\n    vx: -side * (150 + rand(i * 5 + 1) * 300), // 向画面中间冲\n    vy: -(230 + rand(i * 7 + 3) * 220),\n    g: 900 + rand(i + 60) * 520,\n    spin: (rand(i + 90) - 0.5) * 1500,\n    d: rand(i + 120) * 0.06, // 每片略微错峰\n  };\n});\n\nexport const CounterConfetti: React.FC = () => {\n  const t = useT();\n  // 计数：easeOutQuart 冲刺\n  const p = seg(t, 0.06, 0.56, E.outQuart);\n  const val = Math.round(p * 1000);\n  // scale 过冲 [0.2,1.3,1.3,1]\n  const s1 = seg(t, 0.06, 0.3, E.outCubic);\n  const s2 = seg(t, 0.56, 0.72, E.outBack);\n  const sc = lerp(s1, 0.2, 1.3) + s2 * (1 - 1.3);\n  // 到位冲击环\n  const rp = seg(t, 0.545, 0.75, E.outQuart);\n  return (\n    <DesignStage bg=\"radial-gradient(80% 80% at 50% 45%,#161a2b,#07080e 75%)\">\n      {/* 中心辉光 */}\n      <div\n        style={{\n          position: 'absolute',\n          left: '50%',\n          top: '47%',\n          width: 340,\n          height: 340,\n          margin: -170,\n          borderRadius: '50%',\n          background: 'radial-gradient(circle,rgba(120,160,255,.28),transparent 66%)',\n          opacity: 0.35 + seg(t, 0.5, 0.62, E.outCubic) * 0.65 - seg(t, 0.72, 1) * 0.5,\n        }}\n      />\n      {/* 大数字（渐变填充文字，外层 scale 过冲） */}\n      <div style={{ position: 'absolute', left: '50%', top: '47%', transformOrigin: '50% 50%', transform: `scale(${sc})` }}>\n        <div\n          style={{\n            position: 'absolute',\n            left: 0,\n            top: 0,\n            transform: 'translate(-50%,-50%)',\n            whiteSpace: 'nowrap',\n            fontWeight: 800,\n            fontSize: 74,\n            lineHeight: 1,\n            // 原栈 -apple-system 在 Remotion 无头浏览器解析不到（落到 Arial），\n            // 补 Helvetica 兜底——度量与原片的 SF Pro 逐字形吻合\n            fontFamily: \"-apple-system,Helvetica,'Segoe UI',sans-serif\",\n            letterSpacing: -2,\n            background: 'linear-gradient(180deg,#ffffff,#a9bcff)',\n            WebkitBackgroundClip: 'text',\n            backgroundClip: 'text',\n            color: 'transparent',\n            textShadow: '0 0 34px rgba(130,160,255,.28)',\n            opacity: Math.min(1, seg(t, 0.02, 0.12) * 1.2),\n          }}\n        >\n          {val.toLocaleString('en-US')}\n        </div>\n      </div>\n      {/* 底部标签：淡入 + letter-spacing 收拢 */}\n      <div\n        style={{\n          position: 'absolute',\n          left: '50%',\n          top: '70%',\n          transform: 'translate(-50%,0)',\n          fontWeight: 700,\n          fontSize: 10,\n          lineHeight: 1,\n          fontFamily: '-apple-system,Helvetica,sans-serif', // 同上：Helvetica 兜底对齐原片\n\n          color: '#7d88a8',\n          opacity: seg(t, 0.62, 0.78, E.outCubic),\n          letterSpacing: lerp(seg(t, 0.62, 0.82, E.outCubic), 11, 5),\n        }}\n      >\n        METRIC THIS WEEK\n      </div>\n      {/* 到位冲击环 */}\n      <div\n        style={{\n          position: 'absolute',\n          left: '50%',\n          top: '47%',\n          width: 120,\n          height: 120,\n          margin: -60,\n          borderRadius: '50%',\n          border: '2px solid rgba(160,190,255,.8)',\n          // 原渲染无全局 border-box：120px 是内容宽，2px 边框外扩到 124px\n          // （Remotion 注入了 * { box-sizing:border-box }，显式还原才对得上原片）\n          boxSizing: 'content-box',\n          opacity: rp > 0 ? (1 - rp) * 0.9 : 0,\n          transform: `scale(${0.35 + rp * 2.6})`,\n        }}\n      />\n      {/* 纸屑：抢拍 burst + 重力下落 + 自转 */}\n      {BITS.map((b, i) => {\n        const u = seg(t, BURST + b.d, 1);\n        const life = u * 1.1; // 秒级时间尺度\n        const x = b.x0 + b.vx * life;\n        const y = b.y0 + b.vy * life + 0.5 * b.g * life * life;\n        return (\n          <div\n            key={i}\n            style={{\n              position: 'absolute',\n              left: '50%',\n              top: '50%',\n              width: b.w,\n              height: b.h,\n              background: b.color,\n              borderRadius: b.isRect ? 2 : '50%',\n              willChange: 'transform',\n              opacity: u <= 0 ? 0 : Math.min(1, u * 8) * (1 - seg(u, 0.74, 1) * 0.95),\n              transform: `translate(calc(-50% + ${x}px),calc(-50% + ${y}px)) rotate(${b.spin * life}deg) scale(${0.8 + (1 - u) * 0.35})`,\n            }}\n          />\n        );\n      })}\n    </DesignStage>\n  );\n};\n\n\n"
}