{
  "slug": "montage-rhythm-moves",
  "title": "Montage rhythm moves",
  "tagline": "Three montage-rhythm styles: drop-blackout-slam, a blackout wind-up drop; wright-triple-cut, a triple-clack close-up cut; and domino-cascade, a domino-chain entrance.",
  "description": "Three montage-rhythm styles: drop-blackout-slam, a blackout wind-up drop; wright-triple-cut, a triple-clack close-up cut; and domino-cascade, a domino-chain e",
  "category": "rhythm",
  "tags": [
    "shotcraft",
    "remotion",
    "rhythm"
  ],
  "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/montage-rhythm-moves/poster-7cce9b3442f5.jpg",
    "video": "https://gimgs.net/motion/media/montage-rhythm-moves/video-10046c42ab7c.mp4"
  },
  "variants": [
    {
      "key": "drop-blackout-slam",
      "label": "drop-blackout-slam · Blackout wind-up drop",
      "description": "Mid-playback, one frame cuts to a dead-silent pure black hold for 12f, then the main visual slams in with a screen shake plus a bright ring.",
      "use": "The one beat right before the film's single biggest climax; the entrance of a launch slogan/big text",
      "video": "https://gimgs.net/motion/media/montage-rhythm-moves/video-10046c42ab7c.mp4",
      "poster": "https://gimgs.net/motion/media/montage-rhythm-moves/poster-7cce9b3442f5.jpg"
    },
    {
      "key": "wright-triple-cut",
      "label": "wright-triple-cut · Triple close-up clack cut",
      "description": "Three 10f extreme close-ups hard-cut in rapid succession (each \"still 4 – move 3 – still 3\"), the third snaps back to a wide shot revealing the result.",
      "use": "A sketch of \"the operating process\": three-step setup, one-click-done style narratives",
      "video": "https://gimgs.net/motion/media/montage-rhythm-moves/video-74a0bbc5f11c.mp4",
      "poster": "https://gimgs.net/motion/media/montage-rhythm-moves/poster-wright-triple-cut-4e703cb4364d.jpg"
    },
    {
      "key": "domino-cascade",
      "label": "domino-cascade · Domino-chain entrance",
      "description": "The title slams down → a shockwave bounces the card row up → the last card knocks the sidebar sliding into place, momentum transferring stage by stage.",
      "use": "A narrative version of a whole page entering at the open; replaces a plain, one-by-one fade-in",
      "video": "https://gimgs.net/motion/media/montage-rhythm-moves/video-0863ec3868cc.mp4",
      "poster": "https://gimgs.net/motion/media/montage-rhythm-moves/poster-domino-cascade-8fa9e59a5229.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/montage-rhythm-moves.html",
  "json_url": "https://gimgs.net/motion/montage-rhythm-moves.json",
  "template_url": "https://gimgs.net/motion/montage-rhythm-moves/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/montage-rhythm-moves.md",
  "updated_at": "2026-09-24T07:40:07.595Z",
  "prompt": "## When to use\n\nParagraph-level rhythm design: wind-up and burst (A), process sketch (B), opening chain reaction (C); complements beat-cut-moves (which arranges cut spacing) — these three manage the \"shape of a section's breathing\".\n\n## Intention\n\nbeat-cut-moves manages how cut intervals are spaced; these three styles manage a bigger layer of rhythmic storytelling: A is \"silence as wind-up\" — one beat before the drop cuts to a dead-silent full black hold, so the burst lands harder, used for the one shot right before the film's biggest peak; B is a \"process triple\" — three extreme close-ups clack in rapid succession, the third snaps back to a wide shot revealing the result, an Edgar Wright-style edit that turns \"it's this simple\" into muscle memory; C is \"momentum transfer\" — a title slams down and shakes cards loose, each element's entrance triggered by the previous one's impact, a Rube Goldberg-style telling of a page's content entering as a chain reaction.\n\n## Duration & energy\n\n- Duration: A: 4.3s / B: 4.3s / C: 5s\n- Energy: High\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\nbeat-cut-moves 管切点间隔怎么排，这三式管更大一层的节奏叙事：\nA 是\"静默即蓄力\"——drop 前一拍切全黑死寂憋一整拍，爆开才够响\n（EDM 演出 blackout 惯例），给全片最高潮的前一秒用；B 是\"流程三连\"\n——三个超近特写咔哒咔哒咔哒连打（按下/拨动/翻牌），第三声甩回全景\n亮结果，Edgar Wright 式把\"操作很简单\"剪成肌肉记忆；C 是\"动量传递\"\n——标题砸落震起卡片、卡片落地撞进侧边栏，每个元素的入场由上一个的\n撞击触发，Rube Goldberg 式把一页内容的入场讲成连锁反应。\n\n## 三式选型\n| 式 | 做法 | 适用 |\n|----|------|------|\n| A drop-blackout-slam 黑场蓄爆 | 正常播放中一帧切纯黑死寂 12f，然后主视觉带震屏+亮环砸入 | 全片唯一最高潮的前一拍；发布口号/大字的登场 |\n| B wright-triple-cut 三连特写 | 三个 10f 超近特写硬切连打（各\"静4-动3-静3\"），第三声甩回全景亮结果 | \"操作流程\"速写：三步设置、一键完成类叙事 |\n| C domino-cascade 连锁入场 | 标题砸落→震波弹起卡片列→末卡撞滑侧边栏进场，动量方向逐级传递 | 开场整页入场的叙事化版本；替代平铺直叙的逐个 fade-in |\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| A 铺垫 | 前段画面轻微 scale 呼吸 1.0↔1.02 + 节拍点每 12f 闪 | 必须先让\"片子在正常播\"可感，黑掉才有落差 |\n| A 黑场 | 一帧直切纯黑（#0c0c0c），12f **完全无物** | 命门：黑场里放任何东西（logo/微光）都在泄压——死寂才是蓄力 |\n| A 爆入 | 主视觉 scale 1.35→1.0（5f out cubic）+ 10px 震屏 exp(-t/2.5) 12f 收干 + 亮环 80→900px 16f 消散 | 三件事同帧起爆；震屏 t≥14 强制归零保真静止 |\n| B 特写结构 | 每个 10f：静 4f + 动作 3f + 静 3f；动作用 p<0.5 二值跳变保干脆 | 动作 3f 是\"咔哒\"的物理时长；拉长成 6f 就是普通交互动画 |\n| B 构图统一 | 三特写同倍率感、主体都在屏心 | 构图乱跳读作三段素材硬拼，连切节奏散 |\n| B 甩回 | 6f whip（Easing.poly(5) out）+ 2 层错帧副本（opacity 0.35/0.18）模拟糊 | 甩回落点的\"结果\"要提亮泛光——三连咔哒的答案必须亮着 |\n| C 动量链 | 每级 startFrame = 上一级 impact 帧；初速方向继承上一撞击方向 | 命门：竖砸→竖弹→（末卡歪 3° 给出横向）→横滑，方向没有出处则\"连锁\"读作巧合 |\n| C 撞击震动 | 4f 衰减序列 [A,-0.6A,0.3A,-0.12A,0]，两次幅度 10/6px 递减 | 幅度递减表现能量损耗；等幅两震读作重复播放 |\n| C 卡片弹跳 | 依次隔 5f 各弹 60px 抛物线 12f | 40px/3f 间隔在正常速度下读不清传递感（本批实渲加码） |\n| 收尾 | A 爆入后 ≥50f / B 甩回后 ≥45f / C 全链落定后 ≥50f 真静止 | 三式都是重拳，hold 按平常两倍给（同 beat-cut B 判例） |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- A 式全片 ≤1 次且必须配声design：黑场同帧音频也切静、爆开同帧音爆\n  （sound-design §4.5）——无声版黑场读作播放器卡了 12 帧\n- A 的震屏与 impact-feedback、C 的震动与 domino 内部震动都属震屏族，\n  同一镜头只能有一个震源\n- B 式三个特写 10f/个节奏很快是设计意图（已验证可读），但特写内容换成\n  真实 UI 后若操作复杂（不是一眼动作），拉到 12–14f\n- B 式甩回与 shot-transitions E 式甩镜同技术根——B 的 whip 是镜内收尾\n  不是转场，别在 B 之后再接 E 式（连甩读作晕）\n- C 式链最多三级（标题→卡片→侧栏）；四级以上观众跟不住因果链，\n  后面的入场退化回普通错峰\n\n## 参考实现\ndemos/rhythm/montage-rhythm-moves/\n（DominoCascade.tsx / DropBlackoutSlam.tsx / WrightTripleCut.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/rhythm/montage-rhythm-moves/DominoCascade.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/montage-rhythm-moves/DominoCascade.tsx\n- demos/rhythm/montage-rhythm-moves/DropBlackoutSlam.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/montage-rhythm-moves/DropBlackoutSlam.tsx\n- demos/rhythm/montage-rhythm-moves/WrightTripleCut.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/montage-rhythm-moves/WrightTripleCut.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/montage-rhythm-moves/DominoCascade.tsx, demos/rhythm/montage-rhythm-moves/DropBlackoutSlam.tsx, demos/rhythm/montage-rhythm-moves/WrightTripleCut.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/montage-rhythm-moves/DominoCascade.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/montage-rhythm-moves/DominoCascade.tsx"
    },
    {
      "path": "demos/rhythm/montage-rhythm-moves/DropBlackoutSlam.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/montage-rhythm-moves/DropBlackoutSlam.tsx"
    },
    {
      "path": "demos/rhythm/montage-rhythm-moves/WrightTripleCut.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/montage-rhythm-moves/WrightTripleCut.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/montage-rhythm-moves/DominoCascade.tsx",
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/montage-rhythm-moves/DropBlackoutSlam.tsx",
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/montage-rhythm-moves/WrightTripleCut.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/montage-rhythm-moves/DominoCascade.tsx =====\n// 多米诺连锁入场（domino-cascade）——Rube Goldberg / OK Go MV。\n// 三级动量链，每级 startFrame = 上一级 impact 帧：\n// ① 帧 36–51 标题 \"CHAIN REACTION\" 从画外顶 ease-in(cubic) 砸落上半屏，\n//    impact 帧 51 全画面竖向震一拍（4f 衰减）；\n// ② 帧 51 起下方 4 张卡片被震得依次（隔 5f）向上弹 60px 抛物线落回（12f），\n//    末卡落地帧 78 = 第二次撞击（再震一拍 + 末卡向左歪 3° 给出横向动量）；\n// ③ 帧 78–100 左侧深色侧边栏被横向撞滑进场，Easing.out(cubic) 带过冲回弹；帧 100–150 全体真静止。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { G, Card, TitleBlock } from '../../_fixtures/Fixtures';\n\nconst easeInCubic = Easing.in(Easing.cubic);\nconst easeOutCubic = Easing.out(Easing.cubic);\n\n// —— 关键帧 ——\nconst TITLE_START = 36; // 砸落开始（前 36f hold 读布景）\nconst IMPACT_1 = 51; // 标题落地 = 第一次撞击\nconst CARD_STAGGER = 5;\nconst CARD_DUR = 12;\nconst IMPACT_2 = IMPACT_1 + 3 * CARD_STAGGER + CARD_DUR; // 78，末卡落地\nconst SIDE_END = IMPACT_2 + 14; // 92 侧边栏到位（过冲点）\nconst SIDE_SETTLE = SIDE_END + 8; // 100 回弹结束，此后真静止\n\n// 撞击震动：一拍，4f 内衰减归零\nconst shake = (f: number, at: number, amp: number) => {\n  if (f < at || f > at + 4) return 0;\n  const seq = [amp, -amp * 0.6, amp * 0.3, -amp * 0.12, 0];\n  return seq[f - at];\n};\n\n// 卡片行几何：内容中心 1080（给左侧 240 侧边栏留出位置）\nconst CARD_W = 340;\nconst CARD_H = 220;\nconst GAP = 40;\nconst ROW_W = 4 * CARD_W + 3 * GAP; // 1480\nconst ROW_LEFT = 1080 - ROW_W / 2; // 340\nconst CARD_TOP = 700; // 卡片底边 920，落在地板线上\n\nexport const DominoCascade: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // ① 标题砸落：画外顶 → 上半屏，ease-in 加速读作\"砸\"\n  const titleTop = interpolate(frame, [TITLE_START, IMPACT_1], [-260, 240], {\n    easing: easeInCubic,\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n\n  // 两次撞击的全画面竖向震动\n  const shakeY = shake(frame, IMPACT_1, 10) + shake(frame, IMPACT_2, 6);\n\n  // ② 卡片弹跳：抛物线 4t(1-t)，依次隔 5f\n  const cardDy = (i: number) => {\n    const s = IMPACT_1 + i * CARD_STAGGER;\n    const t = Math.min(1, Math.max(0, (frame - s) / CARD_DUR));\n    return -60 * 4 * t * (1 - t);\n  };\n  // 末卡落地时向左歪 3°（横向动量的可见出处），随后回正\n  const lastCardRot = interpolate(\n    frame,\n    [IMPACT_2 - 9, IMPACT_2, SIDE_END],\n    [0, -3, 0],\n    { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' },\n  );\n\n  // ③ 侧边栏横向撞滑：带初速滑入 + 过冲回弹\n  let sideX: number;\n  if (frame < IMPACT_2) {\n    sideX = -260;\n  } else if (frame < SIDE_END) {\n    const t = (frame - IMPACT_2) / (SIDE_END - IMPACT_2);\n    sideX = -260 + 272 * easeOutCubic(t); // 冲到 +12（约 5% 过冲）\n  } else if (frame < SIDE_SETTLE) {\n    const t = (frame - SIDE_END) / (SIDE_SETTLE - SIDE_END);\n    sideX = 12 * (1 - Easing.inOut(Easing.quad)(t));\n  } else {\n    sideX = 0;\n  }\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: G.bg, overflow: 'hidden', position: 'relative' }}>\n      {/* 全画面震动容器 */}\n      <div style={{ position: 'absolute', inset: 0, transform: `translateY(${shakeY}px)` }}>\n        {/* 地板线：卡片落点 */}\n        <div style={{ position: 'absolute', left: ROW_LEFT - 30, top: 928, width: ROW_W + 60, height: 6, background: G.bar, borderRadius: 3 }} />\n\n        {/* ① 砸落的标题 */}\n        <div style={{ position: 'absolute', left: 240, width: 1680, top: titleTop, display: 'flex', justifyContent: 'center' }}>\n          <TitleBlock text=\"CHAIN REACTION\" size={120} />\n        </div>\n\n        {/* ② 被震弹起的 4 张卡片 */}\n        {[0, 1, 2, 3].map((i) => (\n          <div\n            key={i}\n            style={{\n              position: 'absolute',\n              left: ROW_LEFT + i * (CARD_W + GAP),\n              top: CARD_TOP,\n              transform: `translateY(${cardDy(i)}px)${i === 3 ? ` rotate(${lastCardRot}deg)` : ''}`,\n              transformOrigin: '50% 100%',\n            }}\n          >\n            <Card w={CARD_W} h={CARD_H} seed={i + 2} />\n          </div>\n        ))}\n\n        {/* ③ 被撞滑进场的侧边栏 */}\n        <div\n          style={{\n            position: 'absolute', left: 0, top: 0, width: 240, height: 1080,\n            background: G.side, transform: `translateX(${sideX}px)`,\n            padding: '32px 24px', boxSizing: 'border-box',\n            display: 'flex', flexDirection: 'column', gap: 22,\n          }}\n        >\n          <div style={{ width: 44, height: 44, borderRadius: 10, background: '#777775' }} />\n          {Array.from({ length: 8 }).map((_, i) => (\n            <div key={i} style={{ height: 13, width: `${58 + ((i * 31) % 38)}%`, background: G.sideBar, borderRadius: 6 }} />\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n\n\n// ===== demos/rhythm/montage-rhythm-moves/DropBlackoutSlam.tsx =====\n// 落拍黑场爆开（drop-blackout-slam）——EDM concert blackout 的节奏手法：\n// 帧 0–49:假面板正常\"播放\"(scale 呼吸 1.0↔1.02)+右下角节拍点每 12f 闪一下;\n// 帧 50–61:一帧切纯黑 #0c0c0c,整整 12f 死寂,屏上完全无物(蓄力全靠这一拍静默);\n// 帧 62:白色大标题 \"DROP\" 从 scale 1.35 撞到 1.0(5f cubic out),同帧整屏\n// 10px 震屏指数衰减(τ≈2.5f,约 12f 收干),底色变 G.ink 并从中心泛出一圈\n// 快速消散的亮环;帧 ~80 起全静止到 130,收尾 ≥40f 真静止。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { FakeDashboard, G } from '../../_fixtures/Fixtures';\n\n// 帧确定伪随机(硬规矩:禁 Math.random)\nconst h = (n: number) => {\n  const s = Math.sin(n * 127.3) * 43758.5453;\n  return s - Math.floor(s);\n};\n\nconst BLACK_IN = 50; // 切黑帧\nconst SLAM = 62; // 爆入帧\n\nexport const DropBlackoutSlam: React.FC = () => {\n  const f = useCurrentFrame();\n\n  // ===== 段 1:帧 0–49 正常播放 =====\n  if (f < BLACK_IN) {\n    // scale 呼吸 1.0↔1.02,周期 48f\n    const breathe = 1.01 + 0.01 * Math.sin((f / 48) * Math.PI * 2);\n    // 节拍点:每 12f 闪一下(亮 4f 衰减)\n    const beatPhase = f % 12;\n    const beatOn = interpolate(beatPhase, [0, 1, 5], [0.25, 1, 0.25], {\n      extrapolateLeft: 'clamp',\n      extrapolateRight: 'clamp',\n    });\n    const beatScale = interpolate(beatPhase, [0, 1, 5], [1, 1.5, 1], {\n      extrapolateLeft: 'clamp',\n      extrapolateRight: 'clamp',\n    });\n    return (\n      <div style={{ width: 1920, height: 1080, background: G.bg, overflow: 'hidden', position: 'relative' }}>\n        <div style={{ width: 1920, height: 1080, transform: `scale(${breathe})`, transformOrigin: '50% 50%' }}>\n          <FakeDashboard variant=\"B\" />\n        </div>\n        {/* 右下角节拍点 */}\n        <div\n          style={{\n            position: 'absolute',\n            right: 56,\n            bottom: 48,\n            width: 36,\n            height: 36,\n            borderRadius: 18,\n            background: G.ink,\n            opacity: beatOn,\n            transform: `scale(${beatScale})`,\n          }}\n        />\n      </div>\n    );\n  }\n\n  // ===== 段 2:帧 50–61 纯黑死寂,屏上完全无物 =====\n  if (f < SLAM) {\n    return <div style={{ width: 1920, height: 1080, background: '#0c0c0c' }} />;\n  }\n\n  // ===== 段 3:帧 62 起爆入 =====\n  const t = f - SLAM;\n\n  // 标题:scale 1.35 → 1.0,5f cubic out\n  const slamScale = interpolate(t, [0, 5], [1.35, 1], {\n    easing: Easing.out(Easing.cubic),\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n\n  // 震屏:10px 指数衰减,τ≈2.5f,~12f 收干;t≥14 强制归零保证结尾真静止\n  const amp = t >= 14 ? 0 : 10 * Math.exp(-t / 2.5);\n  const shakeX = amp === 0 ? 0 : (h(f * 3.7 + 1) - 0.5) * 2 * amp;\n  const shakeY = amp === 0 ? 0 : (h(f * 7.1 + 2) - 0.5) * 2 * amp;\n\n  // 亮环:从中心快速扩散并消散,~16f 走完(t≥16 后 opacity=0 且不再画)\n  const ringR = interpolate(t, [0, 16], [80, 900], {\n    easing: Easing.out(Easing.cubic),\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const ringOpacity = interpolate(t, [0, 3, 16], [0.85, 0.55, 0], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: G.ink, overflow: 'hidden', position: 'relative' }}>\n      <div\n        style={{\n          width: 1920,\n          height: 1080,\n          transform: `translate(${shakeX}px, ${shakeY}px)`,\n          display: 'flex',\n          alignItems: 'center',\n          justifyContent: 'center',\n          position: 'relative',\n        }}\n      >\n        {/* 亮环:快速扩散消散 */}\n        {t < 16 && (\n          <div\n            style={{\n              position: 'absolute',\n              left: 960 - ringR,\n              top: 540 - ringR,\n              width: ringR * 2,\n              height: ringR * 2,\n              borderRadius: '50%',\n              border: '10px solid #ffffff',\n              opacity: ringOpacity,\n            }}\n          />\n        )}\n        <div\n          style={{\n            fontFamily: 'Helvetica, Arial, sans-serif',\n            fontWeight: 800,\n            fontSize: 340,\n            color: '#ffffff',\n            letterSpacing: -6,\n            transform: `scale(${slamScale})`,\n            lineHeight: 1,\n          }}\n        >\n          DROP\n        </div>\n      </div>\n    </div>\n  );\n};\n\n\n// ===== demos/rhythm/montage-rhythm-moves/WrightTripleCut.tsx =====\n// 三连咔哒特写（wright-triple-cut）——Edgar Wright《僵尸肖恩》流程三连快切:\n// 帧 0–24 全景 hold(FakeDashboard A);随后三个 10f 特写硬切连打,每个特写\n// 前 4f 静止、中 3f 动作、后 3f 静止:①25–34 光标按下按钮(圆点缩一圈+按钮变深)\n// ②35–44 开关左拨右(圆钮 3f 滑动+轨道 G.mid→G.ink) ③45–54 大数字 rotateX 翻牌 0→1;\n// 帧 55 甩回全景:6f whip translateX 滑入,高速段 3 层错帧副本模拟运动模糊,\n// 中上卡片提亮泛光+大 \"1\" 标记结果;帧 ~68 起全静止到 130(真静止 ≥60f)。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { FakeDashboard, G } from '../../_fixtures/Fixtures';\n\nconst HOLD_END = 25; // 全景 hold 结束\nconst C1 = 25; // 特写一:光标按下\nconst C2 = 35; // 特写二:开关\nconst C3 = 45; // 特写三:翻牌\nconst WHIP = 55; // 甩回全景\n\n// 特写统一舞台:G.panel 底 + 居中大白卡,主体屏心、同倍率感\nconst CloseupStage: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n  <div style={{ width: 1920, height: 1080, background: G.panel, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\n    <div style={{\n      width: 1400, height: 800, background: G.card, border: `4px solid ${G.border}`,\n      borderRadius: 28, boxShadow: '0 6px 24px rgba(0,0,0,0.08)',\n      display: 'flex', alignItems: 'center', justifyContent: 'center',\n    }}>\n      {children}\n    </div>\n  </div>\n);\n\n// 结果卡片(3×2 网格中上格)的几何:sidebar 220 + padding 36,列宽 524,行高 454\nconst RESULT = { left: 808, top: 108, w: 524, h: 454 };\n\nexport const WrightTripleCut: React.FC = () => {\n  const f = useCurrentFrame();\n\n  // ===== 帧 0–24:全景 hold =====\n  if (f < HOLD_END) {\n    return <FakeDashboard variant=\"A\" />;\n  }\n\n  // ===== 特写一(25–34):光标从悬停到按下,按钮变深 =====\n  if (f < C2) {\n    const t = f - C1; // 局部 0–9;动作窗 4–6\n    const p = interpolate(t, [4, 7], [0, 1], {\n      easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n    });\n    const cursorScale = 1 - 0.3 * p; // 1 → 0.7 缩一圈\n    const btnBg = p < 0.5 ? G.bar : '#5c5c5a'; // 按下瞬间变深\n    const btnY = 8 * p; // 按钮被压下 8px\n    return (\n      <CloseupStage>\n        <div style={{ position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\n          <div style={{\n            width: 620, height: 190, borderRadius: 40, background: btnBg,\n            border: `5px solid ${G.mid}`, boxSizing: 'border-box',\n            transform: `translateY(${btnY}px)`,\n            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 14,\n          }}>\n            <div style={{ width: 260, height: 30, borderRadius: 15, background: p < 0.5 ? G.card : G.line }} />\n          </div>\n          {/* 圆形光标点:悬停在按钮中心,按下缩一圈 */}\n          <div style={{\n            position: 'absolute', left: '50%', top: '50%',\n            width: 96, height: 96, marginLeft: -48, marginTop: -48 + btnY,\n            borderRadius: 48, background: G.ink, border: '8px solid #ffffff',\n            boxSizing: 'border-box', transform: `scale(${cursorScale})`,\n            boxShadow: '0 4px 14px rgba(0,0,0,0.3)',\n          }} />\n        </div>\n      </CloseupStage>\n    );\n  }\n\n  // ===== 特写二(35–44):开关从左拨到右 =====\n  if (f < C3) {\n    const t = f - C2;\n    const p = interpolate(t, [4, 7], [0, 1], {\n      easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n    });\n    const trackW = 560; const trackH = 240; const knob = 192; const pad = 24;\n    const knobX = pad + p * (trackW - knob - pad * 2); // 左 → 右\n    const trackBg = p < 0.5 ? G.mid : G.ink;\n    return (\n      <CloseupStage>\n        <div style={{\n          position: 'relative', width: trackW, height: trackH, borderRadius: trackH / 2,\n          background: trackBg, boxSizing: 'border-box',\n        }}>\n          <div style={{\n            position: 'absolute', top: pad, left: knobX,\n            width: knob, height: knob, borderRadius: knob / 2, background: G.card,\n            boxShadow: '0 6px 18px rgba(0,0,0,0.35)',\n          }} />\n        </div>\n      </CloseupStage>\n    );\n  }\n\n  // ===== 特写三(45–54):大数字翻牌 0 → 1(rotateX) =====\n  if (f < WHIP) {\n    const t = f - C3;\n    const p = interpolate(t, [4, 7], [0, 1], {\n      easing: Easing.inOut(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n    });\n    const showOne = p >= 0.5;\n    const angle = showOne ? (1 - p) * 2 * 90 : -p * 2 * 90; // 0→-90 折走,90→0 翻入\n    const digit = showOne ? '1' : '0';\n    return (\n      <CloseupStage>\n        <div style={{ perspective: 1200 }}>\n          <div style={{\n            width: 440, height: 560, borderRadius: 32, background: G.ink,\n            display: 'flex', alignItems: 'center', justifyContent: 'center',\n            transform: `rotateX(${angle}deg)`, backfaceVisibility: 'hidden',\n            boxShadow: '0 8px 28px rgba(0,0,0,0.25)',\n          }}>\n            <div style={{\n              fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 800,\n              fontSize: 420, color: '#ffffff', lineHeight: 1,\n            }}>\n              {digit}\n            </div>\n          </div>\n        </div>\n      </CloseupStage>\n    );\n  }\n\n  // ===== 帧 55 起:甩回全景 + 结果卡片亮起 =====\n  const t = f - WHIP;\n  // whip:6f 从右侧 900px 高速滑入,poly(5) out 急减速;t≥6 恒为 0(真静止)\n  const whipX = t >= 6 ? 0 : interpolate(t, [0, 6], [900, 0], {\n    easing: Easing.out(Easing.poly(5)), extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n  });\n  const speeding = t < 4; // 高速段:3 层错帧副本模拟模糊感\n\n  // 结果卡片:泛光 57–84 缓升后恒定(帧 84 起真静止);弹一下 57–64 后恒为 1\n  const glow = interpolate(f, [57, 84], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });\n  const pop = f >= 64 ? 1 : interpolate(f, [57, 60, 64], [1, 1.07, 1], {\n    easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n  });\n\n  const dash = (x: number, opacity: number, key: string) => (\n    <div key={key} style={{ position: 'absolute', left: 0, top: 0, transform: `translateX(${x}px)`, opacity }}>\n      <FakeDashboard variant=\"A\" />\n    </div>\n  );\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: G.bg, overflow: 'hidden', position: 'relative' }}>\n      {speeding && dash(whipX + 220, 0.18, 'echo2')}\n      {speeding && dash(whipX + 110, 0.35, 'echo1')}\n      {dash(whipX, 1, 'main')}\n      {/* 结果卡片提亮泛光 */}\n      <div style={{\n        position: 'absolute',\n        left: RESULT.left, top: RESULT.top, width: RESULT.w, height: RESULT.h,\n        transform: `translateX(${whipX}px) scale(${pop})`,\n        borderRadius: 14, boxSizing: 'border-box',\n        background: `rgba(255,255,255,${0.72 * glow})`,\n        border: `5px solid rgba(47,47,47,${glow})`,\n        boxShadow: `0 0 ${70 * glow}px rgba(255,255,255,${0.95 * glow}), 0 0 ${26 * glow}px rgba(47,47,47,${0.28 * glow})`,\n        display: 'flex', alignItems: 'center', justifyContent: 'center',\n        opacity: glow,\n      }}>\n        <div style={{\n          fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 800,\n          fontSize: 260, color: G.ink, lineHeight: 1,\n        }}>\n          1\n        </div>\n      </div>\n    </div>\n  );\n};\n\n\n"
}