{
  "slug": "beat-cut-moves",
  "title": "Beat Cut Moves",
  "tagline": "Two ways to use hard cuts as percussion — accelerating hard-cut chains (halving intervals to close in) and paparazzi flash freezes (three white flashes, each a different crop).",
  "description": "Two ways to use hard cuts as percussion — accelerating hard-cut chains (halving intervals to close in) and paparazzi flash freezes (three white flashes, each ",
  "category": "rhythm",
  "tags": [
    "shotcraft",
    "remotion",
    "rhythm",
    "transition"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 4.39,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/beat-cut-moves/poster-fc8d02146c74.jpg",
    "video": "https://gimgs.net/motion/media/beat-cut-moves/video-4c2af7e631f0.mp4"
  },
  "variants": [
    {
      "key": "beat-cut-accelerando",
      "label": "beat-cut-accelerando · Accelerating hard-cut chain",
      "description": "Six views hard-cut full-screen at halving intervals (16→12→8→6→4f), accelerating in; the final cut snaps to a freeze back on the main frame with a light push-in to settle.",
      "use": "A sense of sprinting closer: trailer-style build-up, feature rapid-fire charging toward a conclusion.",
      "video": "https://gimgs.net/motion/media/beat-cut-moves/video-4c2af7e631f0.mp4",
      "poster": "https://gimgs.net/motion/media/beat-cut-moves/poster-fc8d02146c74.jpg"
    },
    {
      "key": "paparazzi-flash",
      "label": "paparazzi-flash · Burst-shot freeze",
      "description": "Three white flashes, each a hard cut to a different crop of the same footage (wide → card close-up → number close-up); the shutter afterglow settles, the third flash holds on the number.",
      "use": "Ritual of a highlight moment: award-ceremony burst shots, crowning a key number.",
      "video": "https://gimgs.net/motion/media/beat-cut-moves/video-62e2b8a90418.mp4",
      "poster": "https://gimgs.net/motion/media/beat-cut-moves/poster-paparazzi-flash-7cc489bebb4c.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/beat-cut-moves.html",
  "json_url": "https://gimgs.net/motion/beat-cut-moves.json",
  "template_url": "https://gimgs.net/motion/beat-cut-moves/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/beat-cut-moves.md",
  "updated_at": "2026-09-24T07:39:43.802Z",
  "prompt": "## When to use\n\nTurning \"the cut itself\" into a drumbeat for highlight/sprint segments; version A is a trailer-style accelerating approach, version B is award-ceremony burst-shot ritual.\n\n## Intention\n\nThe library's existing rhythm vocabulary only governs rate \"within one move\" (speed-ramp varies speed inside a single take) and \"how long to hold after settling\" (the R-series breathing rules) — none of it governs how cut points between multiple shots are arranged. This card fills that gap: hard cuts skip all transitions, and the cut-interval pattern itself becomes the score — shrinking intervals read as an accelerating approach (A), three evenly spaced flashes read as a shutter burst (B). No conflict with the R-series: R governs the hold after settling, this card governs cut-point layout, and after a cut chain ends it must be followed by enough hold to \"pay back\" the breathing room. Note this is a deliberate exception — the rhythm-preference rule is a one-way \"slow down,\" but the harder the cut chain, the longer the hold after it must be.\n\n## Duration & energy\n\n- Duration: A: full run ~4.3s (build 49f + five-cut chain + 35f freeze hold); B: full run ~4.3s (live footage 30f + three flashes + 60f hold).\n- Energy: High\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n库内节奏词汇一直只管\"一条运动内\"的速率（speed-ramp 是同一条 take\n里变速）和\"落定后停多久\"（R 系呼吸判例），没有词管**多镜之间切点\n怎么排**。本卡补这一块：硬切不做任何过渡，切点间隔本身就是乐谱——\n间隔递减读作加速逼近（A），等距三闪读作快门连拍（B）。与 R 系不\n冲突：R 管落定后的停，本卡管切点排布，切串结束必须接足量 hold 把\n呼吸还回去。注意这是刻意的例外——节奏偏好判例是单向\"放慢\"，硬切\n串切得越狠，切完的 hold 要比平常更长。\n\n## 两式选型\n| 式 | 做法 | 适用 |\n|----|------|------|\n| A beat-cut-accelerando 递进硬切串 | 六视图按 16→12→8→6→4f 间隔减半全屏硬切，加速逼近，末刀戛然定格回主画面轻推收住 | 冲刺逼近感：预告片式蓄力、功能连打冲向结论 |\n| B paparazzi-flash 连闪定格 | 三次白闪各硬切同素材不同裁切（全景→卡片特写→数字特写），快门余韵沉降，第三闪停在数字收束 | 高光时刻仪式感：颁奖连拍、关键数字的加冕 |\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| A 切点帧 | 0/49/65/77/85/91/95（建立 49f，间隔 16→12→8→6→4f） | 间隔按\"减半律\"缩，等差缩读不出加速；末间隔 <4f 肉眼跟不上 |\n| A 视图池 | 6 个 = 两块 FakeDashboard × scale 1/1.8/2.6 + 对焦点，transformOrigin 钉焦点再平移居中 | 视图必须是同一产品的不同构图（语义连续），六个无关画面读作故障 |\n| A 切帧快门感 | 切帧 1f brightness 1.05 + 叠 6% 不透明度白层 | 只在切帧亮一下，是\"咔\"不是闪光灯；再厚就抢了 B 式的活 |\n| A 末段收住 | 定格回主画面 hold 35f，scale 1→1.06 ease-out 慢推 20f 后静止 | 戛然而止是这式的句号——末刀后不许再有任何切 |\n| B 三闪切点 | 30/52/70（间隔 22f→18f 渐紧），闪前 30f 慢推\"活素材\"衬托闪后定格 | 闪前必须是活的，全片死素材三闪读不出\"定格\" |\n| B 白闪层 | 每闪 opacity 0.95→0 走 4f ease-out 衰减；闪帧 ±2px seed 哈希全屏抖动 | 0.95 起步才有闪光灯的爆点；衰减 >6f 变柔光转场，失去快门感 |\n| B 快门余韵 | 切入视图 scale 1.03→1 走 6f ease-out 回落 + −16px 沉降归零 | 余韵是\"被拍到晃了一下\"；去掉后三闪读作幻灯片翻页 |\n| B 三裁切 | 同一 Footage 换 scale/transformOrigin：1.0 全景 / 2.3 卡片 / 4.0 数字 | 层层逼近有叙事方向；乱序（特写→全景→特写）读作剪错 |\n| B 末段收束 | 第三闪后数字特写 hold 60f 静止 | 三闪是重击，hold 要给足平常两倍以上 |\n\n## 已知坑\n- **demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验**\n- 两式都是**声音强依赖**手法：切点必须钉在音轨拍点上，每切一声\n  tick（A）/ 快门咔嚓（B），无声版只剩画面乱跳；钉帧走\n  sound-design.md §4.5 相对 shot 起点，不钉绝对帧\n- A 式六视图要\"同一产品的不同构图\"而非六个无关画面——观众要在加速\n  中始终认得这是谁；跨产品/跨场景的素材别用这式\n- 切串全片 ≤1 次（P4 克制原则）：这是全片最响的一记，第二次出现\n  第一次就白打了\n- 节奏偏好判例是单向\"放慢\"，本卡是刻意例外——切完的 hold（A 35f /\n  B 60f）是底线不是建议，宁多勿少\n- 真硬切 = frame 落区间渲染对应视图，无任何过渡帧；手滑加 2f 交叉\n  淡化就不是硬切了，节拍感立即糊掉\n\n## 参考实现\ndemos/rhythm/beat-cut-moves/\n（BeatCutAccelerando.tsx / PaparazziFlash.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/rhythm/beat-cut-moves/BeatCutAccelerando.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/beat-cut-moves/BeatCutAccelerando.tsx\n- demos/rhythm/beat-cut-moves/PaparazziFlash.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/beat-cut-moves/PaparazziFlash.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/rhythm/beat-cut-moves/BeatCutAccelerando.tsx, demos/rhythm/beat-cut-moves/PaparazziFlash.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/rhythm/beat-cut-moves/BeatCutAccelerando.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/beat-cut-moves/BeatCutAccelerando.tsx"
    },
    {
      "path": "demos/rhythm/beat-cut-moves/PaparazziFlash.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/beat-cut-moves/PaparazziFlash.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": 132,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/beat-cut-moves/BeatCutAccelerando.tsx",
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/beat-cut-moves/PaparazziFlash.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/rhythm/beat-cut-moves/BeatCutAccelerando.tsx =====\nimport React from 'react';\nimport { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';\nimport { FakeDashboard, G } from '../../_fixtures/Fixtures';\n\n// beat-cut-accelerando：六个不同构图按 16→12→8→6→4 帧递减间隔全屏硬切，\n// 加速逼近，最后一切戛然定格回主画面并 1→1.06 慢推收住。\n// 真硬切：frame 落在哪个区间就渲染哪个视图，无任何过渡。\n\n// 一个视图 = FakeDashboard 变体 + 缩放 + 对焦点（画面上要被推到屏幕中心的点）\ntype View = { variant: 'A' | 'B'; scale: number; cx: number; cy: number };\n\nconst VIEWS: View[] = [\n  { variant: 'A', scale: 1, cx: 960, cy: 540 },    // v0 全景（建立）\n  { variant: 'A', scale: 1.8, cx: 1070, cy: 576 }, // v1 卡片区\n  { variant: 'A', scale: 2.6, cx: 600, cy: 340 },  // v2 单卡特写\n  { variant: 'B', scale: 1, cx: 960, cy: 540 },    // v3 列表页全景\n  { variant: 'B', scale: 1.9, cx: 1070, cy: 500 }, // v4 列表行区\n  { variant: 'B', scale: 2.8, cx: 1070, cy: 290 }, // v5 单行特写\n];\n\n// 每段起始帧：建立 49f，然后间隔 16→12→8→6→4，末段（回主画面）hold 35f\n// 0–48 v0 | 49–64 v1 | 65–76 v2 | 77–84 v3 | 85–90 v4 | 91–94 v5 | 95–129 定格\nconst CUTS = [0, 49, 65, 77, 85, 91, 95];\nconst FINAL = 95; // 最后一切：戛然定格回主画面\n\nconst ViewShot: React.FC<{ view: View; extraScale?: number }> = ({ view, extraScale = 1 }) => {\n  const s = view.scale * extraScale;\n  return (\n    <div\n      style={{\n        width: 1920,\n        height: 1080,\n        transformOrigin: `${view.cx}px ${view.cy}px`,\n        transform: `translate(${960 - view.cx}px, ${540 - view.cy}px) scale(${s})`,\n      }}\n    >\n      <FakeDashboard variant={view.variant} />\n    </div>\n  );\n};\n\nexport const BeatCutAccelerando: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // 当前落在哪个区间（末段 = 主画面 v0）\n  let seg = 0;\n  for (let i = 0; i < CUTS.length; i++) {\n    if (frame >= CUTS[i]) seg = i;\n  }\n  const isFinal = seg === CUTS.length - 1;\n  const view = isFinal ? VIEWS[0] : VIEWS[seg];\n\n  // 末段慢推：scale 1 → 1.06，ease-out，推 22f 后静止（结尾静止 ≥15f）\n  const push = isFinal\n    ? interpolate(frame, [FINAL, FINAL + 20], [1, 1.06], {\n        extrapolateLeft: 'clamp',\n        extrapolateRight: 'clamp',\n        easing: Easing.out(Easing.cubic),\n      })\n    : 1;\n\n  // 每次硬切的 1f 亮度跳变（约 +5%）模拟快门\n  const isCutFrame = CUTS.some((c, i) => i > 0 && frame === c);\n  const flash = isCutFrame ? 1.05 : 1;\n\n  return (\n    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>\n      <div style={{ position: 'absolute', inset: 0, filter: `brightness(${flash})` }}>\n        <ViewShot view={view} extraScale={push} />\n      </div>\n      {/* 切帧再叠一层极薄白闪，保证肉眼可感 */}\n      {isCutFrame && (\n        <AbsoluteFill style={{ background: '#ffffff', opacity: 0.06 }} />\n      )}\n    </AbsoluteFill>\n  );\n};\n\n\n// ===== demos/rhythm/beat-cut-moves/PaparazziFlash.tsx =====\nimport React from 'react';\nimport { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';\nimport { FakeDashboard, TitleBlock, G } from '../../_fixtures/Fixtures';\n\n// paparazzi flash 连闪定格：高光时刻三连白闪，每闪硬切同一素材的不同裁切\n// （全景→卡片特写→数字特写），每闪切入画面带 1.03→1 回落 + 半格沉降像快门\n// 余韵，白闪帧加微位移抖动模拟快门震，第三闪后停在数字特写收束。\nconst F1 = 30; // 第一闪（间隔 22f）\nconst F2 = 52; // 第二闪（间隔 18f）\nconst F3 = 70; // 第三闪，之后 hold 60f\nconst SETTLE = 6; // 切入回落时长\nconst DECAY = 4; // 白层衰减时长\nconst FLASHES = [F1, F2, F3];\n\n// seed 正弦哈希，返回 -1..1（禁 Math.random）\nconst hash = (i: number) => {\n  const s = Math.sin(i * 127.3) * 43758.5453;\n  return (s - Math.floor(s)) * 2 - 1;\n};\n\n// 同一\"素材\"：全景 dashboard，顶行中间卡片上叠一个大数字，供数字特写裁切\nconst Footage: React.FC = () => (\n  <div style={{ position: 'absolute', width: 1920, height: 1080 }}>\n    <FakeDashboard variant=\"A\" />\n    <div style={{ position: 'absolute', left: 1070, top: 350, transform: 'translate(-50%, -50%)' }}>\n      <TitleBlock text=\"84,213\" size={104} />\n    </div>\n  </div>\n);\n\n// 四个视图：闪前\"活素材\"慢推 + 三个定格裁切（全景 / 卡片 / 数字）\ntype View = { scale: number; origin: string };\nconst VIEW_WIDE: View = { scale: 1.0, origin: '960px 540px' };\nconst VIEW_CARD: View = { scale: 2.3, origin: '1070px 335px' };\nconst VIEW_DIGIT: View = { scale: 4.0, origin: '1070px 350px' };\n\nexport const PaparazziFlash: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // 当前处于哪一段：-1 = 闪前活素材段\n  let seg = -1;\n  for (let i = 0; i < FLASHES.length; i++) {\n    if (frame >= FLASHES[i]) seg = i;\n  }\n\n  let scale: number;\n  let origin: string;\n  let settleScale = 1;\n  let settleY = 0;\n\n  if (seg === -1) {\n    // 闪前：中景慢推（活的），衬托闪后定格的\"死\"\n    origin = '900px 480px';\n    scale = interpolate(frame, [0, F1], [1.16, 1.2], {\n      extrapolateRight: 'clamp',\n      easing: Easing.inOut(Easing.quad),\n    });\n  } else {\n    const view = [VIEW_WIDE, VIEW_CARD, VIEW_DIGIT][seg];\n    scale = view.scale;\n    origin = view.origin;\n    // 切入回落：1.03→1 的 6f 收敛 + 半格沉降（-16px 落回 0）像快门余韵\n    const t = interpolate(frame, [FLASHES[seg], FLASHES[seg] + SETTLE], [1, 0], {\n      extrapolateLeft: 'clamp',\n      extrapolateRight: 'clamp',\n      easing: Easing.out(Easing.cubic),\n    });\n    settleScale = 1 + 0.03 * t;\n    settleY = -16 * t;\n  }\n\n  // 白闪层：每个切点 0.95→0，4f 衰减，取各闪最大值\n  let flash = 0;\n  for (const f of FLASHES) {\n    const o = interpolate(frame, [f, f + DECAY], [0.95, 0], {\n      extrapolateLeft: 'clamp',\n      extrapolateRight: 'clamp',\n      easing: Easing.out(Easing.quad),\n    });\n    if (frame >= f && frame < f + DECAY + 1) flash = Math.max(flash, o);\n  }\n\n  // 白闪帧全屏微位移抖动模拟快门震（seed 哈希，非随机）\n  const inFlash = FLASHES.some((f) => frame >= f && frame < f + DECAY);\n  const jx = inFlash ? 2 * hash(frame * 7 + 1) : 0;\n  const jy = inFlash ? 2 * hash(frame * 13 + 5) : 0;\n\n  return (\n    <AbsoluteFill style={{ background: G.ink, overflow: 'hidden' }}>\n      <div\n        style={{\n          position: 'absolute',\n          inset: 0,\n          transform: `translate(${jx}px, ${jy + settleY}px)`,\n        }}\n      >\n        <div\n          style={{\n            position: 'absolute',\n            width: 1920,\n            height: 1080,\n            transform: `scale(${scale * settleScale})`,\n            transformOrigin: origin,\n          }}\n        >\n          <Footage />\n        </div>\n      </div>\n      {/* 白闪层 */}\n      {flash > 0 && (\n        <AbsoluteFill style={{ background: '#ffffff', opacity: flash }} />\n      )}\n    </AbsoluteFill>\n  );\n};\n\n\n"
}