Motion · Chuyển cảnh

Chuyển cảnh giấu điểm cắt

Ba kiểu chuyển cảnh giấu điểm cắt — che khuất tiền cảnh cắt vô hình, đối đầu va chạm mở màn, và cháy sáng rò rỉ ánh sáng ấm; cắt cứng giấu trong 1-3 khung che khuất/va chạm/đỉnh sáng, khán giả không thấy kéo cắt.

1920×108016:930 fps4.05s · 122 frameremotionGiấy phép: Apache-2.0#shotcraft#remotion#transition

Nguồn: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composition Remotion (React/TSX) — có video preview, không có player trong trình duyệt

Biến thể

invisible-cutinvisible-cut · Che khuất tiền cảnh cắt vô hình
Một thẻ tràn khung kèm mờ chuyển động mạnh quét áp sát ống kính ngang màn hình, trong khung che phủ kín toàn màn hình nền A cắt cứng sang B, rồi thẻ bay ra ngoài khiến khán giả tưởng vẫn là một cảnh liên tục
Muốn đổi cảnh trang-sang-trang hoàn toàn không dấu vết; chủ lực cho kiểu "một cú lấy giả"
versus-slamversus-slam · Đối đầu va chạm mở màn
Hai nửa màn trái phải mỗi bên có mép cắt chéo tăng tốc từ ngoài khung lao vào va chạm hợp lại, khung va chạm kèm chớp trắng + rung màn + đóng dấu VS, điểm cắt chính là cú va chạm
Mở màn hoặc đầu chương mang ngữ nghĩa so sánh/đối đầu (phương án cũ mới, hai sản phẩm, before/after)
light-leak-burnlight-leak-burn · Cháy sáng rò rỉ ánh sáng ấm
Ba khối ánh sáng hổ phách mềm quét chéo qua khung hình, tại khung đỉnh sáng khi đã nuốt khoảng 70% trang cũ thì cắt cứng sang trang mới, ánh sáng tan đi thì trang mới đã vào vị trí
Chuyển chương cần tông ấm/chất phim nhựa; mềm hơn, có hướng, có nhiệt độ hơn chớp trắng

Khi nào dùng

Khi mối nối giữa hai cảnh cần "đổi cảnh không dấu vết" hoặc "mở màn có nghi thức" (thẻ kỹ thuật, cùng tầng lựa chọn với sáu kiểu shot-transitions)

Ý đồ

Cùng họ với kiểu A flash-cut của shot-transitions: flash-cut dùng một lớp chớp trắng che cắt cứng, ba kiểu này đổi sang ba "vật che mắt" khác — thẻ tiền cảnh quét qua (A), khung va chạm đối đầu (B), ánh sáng rò rỉ leo tới đỉnh (C). Nguyên lý chung là đánh lạc hướng kiểu ảo thuật: mắt khán giả bị động tác lớn/ánh sáng mạnh hút đi trong 1-3 khung đó, cắt cứng hoàn tất, khi hồi tâm lại thì cảnh đã đổi, suốt quá trình "không thấy kéo cắt". flash-cut là bản mộc nhất; cần cảm giác hướng chọn A, cần nghi thức chọn B, cần nhiệt độ chọn C.

Thời lượng & năng lượng

  • Thời lượng: n/a (thẻ kỹ thuật; số khung của từng kiểu xem bảng tham số, trích từ ngân sách khung của cảnh lân cận)
  • Năng lượng: n/a (thẻ kỹ thuật, không chiếm ô năng lượng)

Công thức cảnh quay (bản gốc tiếng Trung — timing, easing, bảng tham số, lỗi hay gặp)

意图

与 shot-transitions A 式 flash-cut 同族:flash-cut 用一层白闪盖住硬切, 这三式换了三种"障眼物"——扫过的前景卡片(A)、对撞的撞击帧(B)、 爬到顶峰的漏光(C)。共同原理是魔术误导:观众的眼睛被大动作/强光 吸走的那 1-3 帧里完成硬切,回过神来景已经换了,全程"看不见剪刀"。 flash-cut 是最素的一款;要方向感选 A,要仪式感选 B,要温度选 C。

三式选型

| 式 | 做法 | 适用接缝 | |----|------|----------| | A invisible-cut 前景遮挡隐形切 | 一张超画幅卡片带重运动模糊贴脸横扫,糊满全屏的遮挡帧内背景 A→B 硬切,卡片飞出观众以为还是同一镜 | 想让换景完全无痕的页面→页面;伪"一条 take"的主力款 | | B versus-slam 对撞开屏 | 左右两半屏带斜切边从画外加速对冲撞合,撞击帧白闪+震屏+VS 盖章,切点就是撞击本身 | 对比/对阵语义的开屏或章节头(新旧方案、双产品、before/after)| | C light-leak-burn 暖色漏光 | 三团暖色柔光沿对角线斜扫,光峰帧吞掉旧页约七成时硬切新页,光退散时新页已就位 | 想要暖调/胶片味的章节过渡;比白闪柔、有方向、有温度 |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 横扫 | 卡片 1600x1000 再 scale(1.6),x 从 -2200→2600 共 14f,bezier(0.3,0,0.7,1);切点在扫掠中点全遮帧(demo 里 f47) | 中点必须覆盖 -280..2280 糊满 1920 画幅;两端完全出画 | | A 糊感 | 全场包 <CameraMotionBlur shutterAngle={300} samples={12}> + 卡片自身 blur(8px)/skewX(-v·0.018°) + 4 层手动残影(opacity 0.35→0.07、blur 14px、间隔 0.55f) | 三层糊叠出"贴脸呼啸";只靠 CameraMotionBlur 糊不满遮挡窗口 | | A 带风推挤 | 背景 ±40px:切前 A 被拖向扫掠方向(ease-in),切后 B 从反侧 13f 回稳(ease-out) | 卖"同一镜被风带了一下"的错觉,是无痕感的一半功劳 | | B 斜缝几何 | clip-path polygon 斜边约 78°(缝顶 x=1075→缝底 x=845);两半屏从 ±1200px 以 ease-in(cubic) 10f 对冲到位 | ease-in 加速才有"砸"感;垂直缝读作 PPT 分屏 | | B 撞击三件套 | 撞击帧同发:白闪 0.9→0 共 3f + 整机 shake 12px·e^(−t/1.6) 约 5f 收干 + VS 字块 scale 1.6→1 back(2.6) overshoot 6f 压出 | 三件必须同帧起跑,错开一帧就散;震屏幅度须过肉眼阈值(可感性判例)| | C 光层 | 3 团径向渐变(#f6c878/#e8a44a/#d98a2b,直径 1500/1950/2400px,blur 90px,mixBlendMode screen)沿右上→左下扫约 70f | 光团偏移沿扫掠方向拖尾排布,避免完美同心的"手电筒感" | | C 光强包络 | ease-in 爬升 27f(蓄力)→ 峰值帧藏切 → ease-out 收敛 43f(余温);峰值叠 8% 全屏暖罩 + 近白热核(opacity=intensity²) | 爬升慢收敛快会读作故障闪光;热核只在临近峰值烧起来 | | C 冲淡 | 峰值时页面 filter:contrast 降 45%、brightness 抬 35% | 这是"烧穿"感的来源——光只叠不冲淡,读作贴了张贴纸 |

已知坑

  • demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,

首次实战须以真实素材回验

  • C 式是 Q4 光效判例解封后首个入库光效手法——严守 Q4 三约束:

单点使用(一支片一处,不群发)、正常速度回放自检好看才算过、 光效服务切点而非装饰;与 flash-cut 同片混用算两次光效藏切

  • A 式遮挡物必须真的全遮:demo 里 1600x1000 的卡要 scale(1.6) 才盖满

1920 画幅——换素材尺寸后先逐帧检查切点帧有没有露出背景边缘, 露一条缝整个魔术就穿帮

  • B 式 VS 字块语义仅适配"对比/对阵"内容——没有两方对峙关系时

别当通用转场用;去掉 VS 只留对撞则退化为一款斜缝 wipe,语义要求同降

  • 转场不叠加(规矩同 shot-transitions):一个接缝只用一式;

这三式与六式同层选型,同片同类接缝别既用 flash-cut 又用 C 式

  • 分镜表阶段就逐接缝标注(写进"关键动效"列),帧预算从相邻镜头划(R3)

参考实现

demos/transition/transition-hidden-cut/ (InvisibleCut.tsx / LightLeakBurn.tsx / VersusSlam.tsx)

Mã nguồn tham chiếu (Remotion)

  • demos/transition/transition-hidden-cut/InvisibleCut.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/transition-hidden-cut/InvisibleCut.tsx
  • demos/transition/transition-hidden-cut/LightLeakBurn.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/transition-hidden-cut/LightLeakBurn.tsx
  • demos/transition/transition-hidden-cut/VersusSlam.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/transition/transition-hidden-cut/VersusSlam.tsx

Component import fixture chung từ demos/_fixtures và helper từ assets/lib của repo; xem SKILL.md trong repo để biết quy trình render.

Mọi thứ trên trang này đều có bản máy đọc. Đưa agent link JSON, hoặc thêm MCP server gimgs rồi bảo nó tìm motion template.

  1. Tải JSON — trong đó có công thức cảnh quay (timing, easing, bảng tham số, lỗi hay gặp) và toàn bộ source TSX.
  2. Clone repo video-shotcraft; component import fixture chung từ demos/_fixtures và helper từ assets/lib.
  3. Đăng ký component thành <Composition> trong project Remotion rồi render bằng `npx remotion render`, hoặc dựng lại công thức bằng engine của bạn.
curl -s https://gimgs.net/motion/transition-hidden-cut.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/transition/transition-hidden-cut/InvisibleCut.tsx, demos/transition/transition-hidden-cut/LightLeakBurn.tsx, demos/transition/transition-hidden-cut/VersusSlam.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("transition-hidden-cut")

Prompt

Use the video-shotcraft shot recipe "Chuyển cảnh giấu điểm cắt" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/transition-hidden-cut.json
Raw TSX: https://gimgs.net/motion/transition-hidden-cut/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/transition/transition-hidden-cut/InvisibleCut.tsx, demos/transition/transition-hidden-cut/LightLeakBurn.tsx, demos/transition/transition-hidden-cut/VersusSlam.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 4.05s

1. Read the JSON and follow its "prompt" section (when to use, intention, parameter table, known pitfalls).
2. Adapt it for: <describe what you want>.
3. Either clone the repo, register the component as a <Composition> in a Remotion project and run npx remotion render <CompositionId> out.mp4 — or re-implement the recipe in your own engine (HTML/WAAPI works: keep the same timing and easing).

JSON · Markdown · llms.txt

10,954 chars · Apache-2.0Mở template gốc ↗
// ===== demos/transition/transition-hidden-cut/InvisibleCut.tsx =====
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { CameraMotionBlur } from '@remotion/motion-blur';
import { FakeDashboard, Card, G } from '../../_fixtures/Fixtures';

// invisible-cut:前景遮挡隐形切——一张放大到超出画幅的卡片带重运动模糊
// 从左侧贴脸扫过,糊满屏幕的瞬间背景从 A 无痕换成 B,卡片飞出右侧时
// 观众以为还是同一镜。(invisible-cut + foreground-occlusion-swipe)
const SW_START = 40; // 卡片入场
const SW_END = 54; // 卡片出场(14f 横扫)
const CUT = 47; // 硬切点:扫掠中点,卡片完全糊满画面的那一帧

// 卡片中扫掠曲线上的水平位置(容器 left,未缩放坐标)
const xAt = (f: number) =>
  // 卡片 scale(1.6) 后半宽 1280:起点右缘 -120 / 终点左缘 2120,均完全出画;
  // 中点 f47 覆盖 -280..2280,糊满整个 1920 画幅
  interpolate(f, [SW_START, SW_END], [-2200, 2600], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.bezier(0.3, 0, 0.7, 1),
  });

const Scene: React.FC = () => {
  const frame = useCurrentFrame();
  const x = xAt(frame);
  // 瞬时速度(px/帧),驱动斜切与残影强度
  const v = xAt(frame + 0.5) - xAt(frame - 0.5);
  const sweeping = frame > SW_START - 2 && frame < SW_END + 3;
  // 背景被"带风"轻推:A 被拖向左,切成 B 后从右侧回稳——卖同一镜错觉
  const shove =
    frame < CUT
      ? interpolate(frame, [SW_START, CUT], [0, -40], {
          extrapolateLeft: 'clamp',
          extrapolateRight: 'clamp',
          easing: Easing.in(Easing.quad),
        })
      : interpolate(frame, [CUT, CUT + 13], [40, 0], {
          extrapolateLeft: 'clamp',
          extrapolateRight: 'clamp',
          easing: Easing.out(Easing.cubic),
        });
  return (
    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
      {/* 背景层:硬切藏在遮挡帧内 */}
      <div style={{ position: 'absolute', inset: 0, transform: `translateX(${shove}px)` }}>
        {frame < CUT ? <FakeDashboard variant="A" /> : <FakeDashboard variant="B" />}
      </div>
      {/* 手动残影:4 层拖尾(在主卡身后),保证遮挡窗口糊满全屏 */}
      {sweeping &&
        [4, 3, 2, 1].map((i) => (
          <div
            key={i}
            style={{
              position: 'absolute',
              left: xAt(frame - i * 0.55),
              top: 40,
              width: 1600,
              height: 1000,
              transform: 'scale(1.6)',
              opacity: [0, 0.35, 0.22, 0.13, 0.07][i],
              filter: 'blur(14px)',
            }}
          >
            <Card w={1600} h={1000} seed={9} style={{ width: '100%', height: '100%' }} />
          </div>
        ))}
      {/* 主卡:1600x1000 放大 1.6 倍(2560x1600 超出画幅),自带 blur 加强糊感 */}
      {sweeping && (
        <div
          style={{
            position: 'absolute',
            left: x,
            top: 40,
            width: 1600,
            height: 1000,
            transform: `scale(1.6) skewX(${-v * 0.018}deg)`,
            filter: 'blur(8px)',
            boxShadow: '0 24px 80px rgba(0,0,0,0.3)',
            borderRadius: 20,
          }}
        >
          <Card w={1600} h={1000} seed={9} style={{ width: '100%', height: '100%' }} />
        </div>
      )}
    </AbsoluteFill>
  );
};

export const InvisibleCut: React.FC = () => (
  <CameraMotionBlur shutterAngle={300} samples={12}>
    <Scene />
  </CameraMotionBlur>
);


// ===== demos/transition/transition-hidden-cut/LightLeakBurn.tsx =====
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { FakeDashboard, TitleBlock, G } from '../../_fixtures/Fixtures';

// light-leak-burn〔转场〕:一团琥珀橙柔光从右上角斜扫入画,亮度顶峰时
// 吞掉旧画面约七成(高光溢出、对比度被冲淡),光峰帧后切新页藏切点,
// 光退散时新页已在光下就位——比白闪柔、有方向、有温度。
// 节拍:0–25 建立旧页 hold;25–52 光斜扫入、爬向峰值;52 峰值帧切页;
// 52–95 光沿对角线退出、强度衰减;95–130 新页静止 hold。

const PEAK = 52; // 光峰帧 = 藏切点

export const LightLeakBurn: React.FC = () => {
  const frame = useCurrentFrame();

  // 光团沿对角线的位移进度:右上外 → 左下外,贯穿 25–95
  const sweep = interpolate(frame, [25, 95], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.bezier(0.4, 0, 0.3, 1),
  });

  // 光强包络:25–PEAK 爬升(ease-in 有蓄力感),PEAK–95 收敛(ease-out 余温)
  const rise = interpolate(frame, [25, PEAK], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.in(Easing.quad),
  });
  const fall = interpolate(frame, [PEAK, 95], [1, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  const intensity = frame <= PEAK ? rise : fall;

  // 光团中心:从画面右上外侧斜扫到左下外侧(有方向的漏光)
  const cx = interpolate(sweep, [0, 1], [2350, -650]);
  const cy = interpolate(sweep, [0, 1], [-500, 1500]);

  // 三团琥珀光的偏移与直径(seed 正弦哈希做微差,避免完美同心)
  const blobs = [0, 1, 2].map((i) => {
    const j1 = (Math.sin(i * 127.3) * 43758) % 1; // -1..1 小数
    const j2 = (Math.sin(i * 311.7) * 27183) % 1;
    return {
      dx: i * 260 - 260 + j1 * 90, // 沿扫掠方向拖尾排布
      dy: i * 200 - 200 + j2 * 70,
      d: 1500 + i * 450, // 直径 1500 / 1950 / 2400
      color: ['#f6c878', '#e8a44a', '#d98a2b'][i],
      alpha: [0.95, 0.8, 0.55][i],
    };
  });

  // 峰值时页面被光冲淡:对比度降、亮度抬(旧页被"烧穿"的感觉)
  const pageFilter = `contrast(${1 - intensity * 0.45}) brightness(${1 + intensity * 0.35})`;

  // 全屏暖色罩:峰值时约 8%
  const warmWash = intensity * 0.08;

  return (
    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
      {/* 页面层:光峰帧前是旧页(网格),之后是新页(列表+标题)——切点藏在最亮处 */}
      <AbsoluteFill style={{ filter: pageFilter }}>
        {frame <= PEAK ? (
          <FakeDashboard variant="A" />
        ) : (
          <>
            <FakeDashboard variant="B" />
            <div style={{ position: 'absolute', left: 288, top: 132 }}>
              <TitleBlock text="Next Page" size={64} />
            </div>
          </>
        )}
      </AbsoluteFill>

      {/* 全屏暖色罩:峰值 8%,让高光溢出带温度 */}
      <AbsoluteFill
        style={{
          background: '#e8a44a',
          opacity: warmWash,
          mixBlendMode: 'screen',
          pointerEvents: 'none',
        }}
      />

      {/* 琥珀漏光层:3 团大直径径向渐变,blur 90px,screen 叠加,沿对角线扫过 */}
      <AbsoluteFill style={{ pointerEvents: 'none' }}>
        {blobs.map((b, i) => (
          <div
            key={i}
            style={{
              position: 'absolute',
              left: cx + b.dx - b.d / 2,
              top: cy + b.dy - b.d / 2,
              width: b.d,
              height: b.d,
              borderRadius: '50%',
              background: `radial-gradient(circle, ${b.color} 0%, ${b.color}cc 30%, transparent 68%)`,
              filter: 'blur(90px)',
              mixBlendMode: 'screen',
              opacity: b.alpha * intensity,
            }}
          />
        ))}
        {/* 峰值核心过曝:光心一小团接近白的热核,峰值帧吞掉约七成画面 */}
        <div
          style={{
            position: 'absolute',
            left: cx - 550,
            top: cy - 550,
            width: 1100,
            height: 1100,
            borderRadius: '50%',
            background: 'radial-gradient(circle, #fff3dd 0%, #f6c878aa 45%, transparent 70%)',
            filter: 'blur(80px)',
            mixBlendMode: 'screen',
            opacity: intensity * intensity, // 只在临近峰值时才烧起来
          }}
        />
      </AbsoluteFill>
    </AbsoluteFill>
  );
};


// ===== demos/transition/transition-hidden-cut/VersusSlam.tsx =====
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { FakeDashboard, TitleBlock, G } from '../../_fixtures/Fixtures';

// versus-slam 对撞开屏:左右两个半屏画面(带 78° 斜切边)从画外加速对冲,
// 沿斜缝砰地撞合;撞击帧白闪 + 整机震屏指数衰减 + "VS" 字块盖章压出,结尾静止 hold。
const IMPACT = 30; // 撞击帧(前 20f 建立 hold + 10f ease-in 对冲)

// 斜缝几何:78° 斜边 → 1080 高度上水平偏移 1080/tan(78°) ≈ 230px,中线 x=960 ±115
const SEAM_TOP_X = 1075; // 缝顶端 x
const SEAM_BOT_X = 845; // 缝底端 x
// CSS 旋转顺时针为正:缝顶端偏右(1075 > 845)→ 正角度 ≈ +12°
const SEAM_DEG = (Math.atan2(SEAM_TOP_X - SEAM_BOT_X, 1080) * 180) / Math.PI;

export const VersusSlam: React.FC = () => {
  const frame = useCurrentFrame();

  // 两半屏对冲:ease-in 加速,10f 从 ±1200px 冲到位
  const leftX = interpolate(frame, [20, IMPACT], [-1200, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.in(Easing.cubic),
  });
  const rightX = -leftX;

  // 撞击帧起:整机震屏 12px 指数衰减(约 5f 收干)
  const since = frame - IMPACT;
  const env = since >= 0 ? 12 * Math.exp(-since / 1.6) : 0;
  const shakeX = env * Math.sin(since * 3.4);
  const shakeY = env * 0.6 * Math.sin(since * 4.1 + 0.7);

  // 白闪:撞击帧 0.9 → 0,3f 收掉
  const flash = interpolate(frame, [IMPACT, IMPACT + 3], [0.9, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  // "VS" 盖章:scale 1.6 → 1 带 back overshoot,6f 压出
  const vsScale = interpolate(frame, [IMPACT, IMPACT + 6], [1.6, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.back(2.6)),
  });
  const vsOpacity = interpolate(frame, [IMPACT, IMPACT + 2], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  const impacted = frame >= IMPACT;

  return (
    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
      <div style={{ position: 'absolute', inset: 0, transform: `translate(${shakeX}px, ${shakeY}px)` }}>
        {/* 建立段的斜缝虚线预示(撞合后被实缝替代) */}
        {!impacted && (
          <svg width={1920} height={1080} style={{ position: 'absolute', inset: 0 }}>
            <line
              x1={SEAM_TOP_X} y1={0} x2={SEAM_BOT_X} y2={1080}
              stroke={G.bar} strokeWidth={4} strokeDasharray="18 16"
            />
          </svg>
        )}
        {/* 左半屏:FakeDashboard A 裁左半,斜边 78° */}
        <div style={{
          position: 'absolute', inset: 0,
          transform: `translateX(${leftX}px)`,
          clipPath: `polygon(0px 0px, ${SEAM_TOP_X}px 0px, ${SEAM_BOT_X}px 1080px, 0px 1080px)`,
        }}>
          <FakeDashboard variant="A" />
        </div>
        {/* 右半屏:FakeDashboard B 裁右半 */}
        <div style={{
          position: 'absolute', inset: 0,
          transform: `translateX(${rightX}px)`,
          clipPath: `polygon(${SEAM_TOP_X}px 0px, 1920px 0px, 1920px 1080px, ${SEAM_BOT_X}px 1080px)`,
        }}>
          <FakeDashboard variant="B" />
        </div>
        {/* 撞合后的实体斜缝条 */}
        {impacted && (
          <div style={{
            position: 'absolute', left: 960 - 6, top: 540 - 700,
            width: 12, height: 1400, background: G.ink,
            transform: `rotate(${SEAM_DEG}deg)`,
          }} />
        )}
        {/* "VS" 字块盖章:贴缝、随缝倾斜 */}
        {impacted && (
          <div style={{
            position: 'absolute', left: 960, top: 540,
            transform: `translate(-50%, -50%) rotate(${SEAM_DEG}deg) scale(${vsScale})`,
            opacity: vsOpacity,
            background: G.card, border: `6px solid ${G.ink}`, borderRadius: 20,
            padding: '18px 46px', boxShadow: '0 18px 60px rgba(0,0,0,0.35)',
          }}>
            <TitleBlock text="VS" size={140} />
          </div>
        )}
      </div>
      {/* 撞击白闪(不随震屏位移) */}
      <AbsoluteFill style={{ background: '#ffffff', opacity: flash, pointerEvents: 'none' }} />
    </AbsoluteFill>
  );
};


  1. Read the recipe
    Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
  2. Get the code
    tool: git
    git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install
  3. Register + render
    tool: remotion
    npx remotion render <CompositionId> out.mp4 # component: demos/transition/transition-hidden-cut/InvisibleCut.tsx, demos/transition/transition-hidden-cut/LightLeakBurn.tsx, demos/transition/transition-hidden-cut/VersusSlam.tsx
  4. Or re-implement
    Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
paramtypedefaultdescription
—

Cùng danh mục