Motion · Intro

Mở đầu địa hình dòng dữ liệu

Mở đầu địa hình chùm đường luồng trên nền tối — nhiều đường luồng hội tụ về một dòng chính, nhãn ID hư cấu nổi trên đường luồng, camera bay chậm qua với độ sâu trường ảnh lớn.

1920×108016:930 fps5.55s · 167 frameremotionGiấy phép: Apache-2.0#shotcraft#remotion#intro#opening#data#camera

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

Khi nào dùng

Mở đầu trừu tượng cấp thương hiệu (ẩn dụ "vũ trụ dữ liệu"), dẫn vào đoạn sản phẩm tông sáng hoặc chữ logo; phân công với glow-flyline-moves: thẻ đó là tường thuật nối đường giữa các thẻ trong một đoạn, thẻ này là địa hình toàn khung chuyên dùng cho mở đầu.

Ý đồ

Thể loại mở đầu cấp thương hiệu đã được kiểm chứng trong phim ra mắt Linear Releases: quay thế giới dữ liệu đằng sau sản phẩm thành một địa hình nền tối — chùm đường luồng từ sâu trong khung hội tụ về một dòng chính (ẩn dụ "vô số workflow hội tụ thành một sản phẩm"). Cảm giác chân thực đến từ ba yếu tố: 1) nhãn ID issue/task hư cấu nổi trên đường luồng (người xem nhận ra "đây là thứ tôi thấy hằng ngày"); 2) độ sâu trường ảnh lớn — cận cảnh mờ lớn trôi qua, trung cảnh nhãn rõ nét đọc được, viễn cảnh mờ dần, ba lớp chiều sâu khiến SVG phẳng đọc thành không gian; 3) camera bay chậm đều — đây là đoạn không khí chứ không phải đoạn phô diễn kỹ thuật, tốc độ để dành cho các cảnh sau. Căn cứ tuân thủ UI tự dựng: không phải cảnh tái hiện thực (màn hình này không tồn tại trên trang nào), nên áp dụng ngưỡng chất lượng cộng độ rõ ràng biểu đạt theo Nguyên tắc cốt lõi 1 (bản sửa đổi).

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

  • Thời lượng: 5–8s (đoạn không khí mở đầu, tối đa 1 lần mỗi phim).
  • Năng lượng: Bắt đầu thấp, tăng dần chậm rãi (vị trí khởi đầu, để dành khoảng leo thang cho các cảnh sau).

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)

意图

Linear Releases 发布片开场实证的品牌级开场品类:把产品背后的数据世界 拍成一片暗场地景——支流线束从画面深处汇入一条主干("无数工作流 汇成一个产品"的隐喻),真实感来自三样东西:①虚构 issue/任务 ID 标签 浮在线上(观众认出"这是我每天看到的那种东西");②重景深——近景大虚焦 流过、中景标签清晰可读、远景渐隐,三层纵深让平面 SVG 读作空间; ③相机低速匀稳飞越——这是氛围段不是炫技段,速度感交给后面的镜头。 手搓 UI 合规依据:非复刻场景(页面上不存在此画面),按核心理念 1 修订版走质量+表达明确性门槛。

动作阶段

| 阶段 | 帧段(@30fps 参考) | 内容 | |------|------|------| | 1 生长 | f0–45 | 流线自远端向汇点 draw-on 生长(out-cubic),主干先行、支流错峰跟进 | | 2 飞越 | f30–150 | 相机沿主干方向低速推移(与生长期跨骑),标签非均匀错峰浮现 | | 3 交棒 | 末 30–45f | 汇点方向留亮部引导视线,接 shot-transitions 藏切/B 式穿暗场,或减速停稳接字标 |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 线束规模 | 支流 8–14 条汇入 1 条主干;分层 近 2–3 / 中 5–8 / 远 4–6 | 超过 ~16 条读作屏保/科技模板;主干必须唯一(单主角原则的地景版) | | 汇流拓扑 | 支流以 bezier 切向汇入主干(切线连续),汇点偏画面一侧 1/3 处 | 垂直相交读作电路图不是水系;汇点居中构图死板 | | 景深三层 | 近景 blur 10–16px + 线宽 6–10px + 亮度 0.15–0.25;中景清晰 2–3px 亮度 0.5–0.8;远景 1–1.5px 亮度 0.2–0.35 | 近景不虚焦则三层塌成一张平面图;远景过亮抢中景 | | 相机飞越 | 世界画布 ≥2× 视口,横移速度 2–5px/f 匀稳 + 视差(近 1.4×/中 1×/远 0.6×),可叠极缓 zoom 1.0→1.06 | 快了变追逐镜头(那是 crash-zoom 的活);无视差立刻穿帮为平面滚动 | | 视差可见性 | 近/远层线条必须与运动方向成角(斜率明显)或带画内可见特征点(端点/交叉/节点标记穿过画面) | 平行于运动方向的线横移时沿自身滑动,视差机制再对也不可见(L2 评估判例:0.6×/1×/1.4× 全写对,正常播放只有中景在动) | | 流动感 | draw-on 完成后线条保持"活":沿线明暗脉冲/虚线相位低速漂向汇点(速度 1–2px/f,透明度 ≤0.3 叠加) | 生长期结束后无流动元素,后半段退化成"路线图飞越"不是"数据流" | | 标签 | 中景层 5–9 枚:方块图钉 + 等宽字 ID(如 APP-1843),错峰 6–10f 间隔淡入 + 沿线微漂 | ID 一律虚构(合规底线);标签挂近景/远景层会因虚焦/过暗不可读;图钉方块要落在线上(钉线分离读作浮尘不是锚点) | | 生长节奏 | 主干 30–40f out-cubic 先行,支流每条错峰 3–6f | 全部同帧生长读作 loading 动画 | | 收尾 | 交棒帧前 ≥15f 标签停止新增、相机斜率连续(不急刹) | 氛围段急刹是一次可见的顿挫(glow-orb 收敛判例同源) |

已知坑

  • 借鉴来源:Linear Releases 开场(进化轮 #3 breakdown #7),单例样本

——参数给区间不给教条,首次实战以真实素材回验

  • 与 glow-flyline-moves 别混用在相邻段落:两卡都是"暗场发光线",

背靠背出现读作全片只会一招;同片都要用时中间隔 ≥2 个亮场镜头

  • 暗场进出成本同 glow 卡:接亮场产品段用 shot-transitions 藏切族垫,

别生切明暗

  • 线条纯白全亮会糊成蛛网——亮度按层拉开(见参数表),中景才是主角层;

主干/支流给 1–2px 微辉光(soft glow)压出"发光线"氛围,全无辉光读作线框图(Q4 单点光效许可内)

  • 标签字号在目标画幅下 ≥18px 等效,否则"可读"沦为装饰噪点(Q6 同源)
  • 声音:铺 riser/低频 pad 底噪,标签浮现不逐枚打点(批量不逐个发声,

S2 同源);交棒帧一次 whoosh/impact 交给下一镜头

参考实现

demos/opening/dataviz-landscape-open/ (DatavizLandscapeOpen.tsx)

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

  • demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.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/dataviz-landscape-open.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("dataviz-landscape-open")

Prompt

Use the video-shotcraft shot recipe "Mở đầu địa hình dòng dữ liệu" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/dataviz-landscape-open.json
Raw TSX: https://gimgs.net/motion/dataviz-landscape-open/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 5.55s

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

13,839 chars · Apache-2.0Mở template gốc ↗
// ===== demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx =====
import React from 'react';
import {AbsoluteFill, interpolate, useCurrentFrame} from 'remotion';

/**
 * DatavizLandscapeOpen — 暗场支流线束地景开场
 * 配方卡: references/shots/opening/dataviz-landscape-open.md
 *
 * 隐喻: 无数团队工作流(支流)汇成一个产品(主干)。
 * 三层景深: 近景大虚焦流过 / 中景标签清晰可读 / 远景渐隐。
 * 相机: 低速匀稳横移 + 视差 + 极缓 zoom, 收尾不急刹。
 */

const W = 1920;
const H = 1080;
const DUR = 165; // 5.5s @30fps
const WORLD_W = 4200;

// ---------- easing ----------
const outCubic = (t: number) => 1 - Math.pow(1 - t, 3);

const growth = (frame: number, start: number, dur: number) => {
  const t = Math.min(1, Math.max(0, (frame - start) / dur));
  return outCubic(t);
};

// ---------- geometry ----------
type Pt = {x: number; y: number};

// 主干: 缓和的水系曲线 (世界坐标)
const trunkY = (x: number) => 480 + 55 * Math.sin((x - 300) / 1050);
const trunkSlope = (x: number) => (55 / 1050) * Math.cos((x - 300) / 1050);

const trunkPath = () => {
  const pts: string[] = [];
  for (let x = -300; x <= WORLD_W + 100; x += 50) {
    pts.push(`${x === -300 ? 'M' : 'L'} ${x} ${trunkY(x).toFixed(1)}`);
  }
  return pts.join(' ');
};

// 三次贝塞尔求值 (用于把标签钉在线上)
const cubicAt = (p0: Pt, p1: Pt, p2: Pt, p3: Pt, t: number): Pt => {
  const u = 1 - t;
  return {
    x: u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x,
    y: u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y,
  };
};

// 支流: 起点在深处四散, 以切向汇入主干 (切线连续)
type Trib = {p0: Pt; p1: Pt; p2: Pt; p3: Pt; opacity: number; growStart: number; growDur: number};

const makeTrib = (
  start: Pt,
  mergeX: number,
  opacity: number,
  growStart: number,
  growDur: number,
): Trib => {
  const end: Pt = {x: mergeX, y: trunkY(mergeX)};
  const slope = trunkSlope(mergeX);
  const len = Math.hypot(1, slope);
  // P2 沿主干切线反向后退 => 到达时切线连续
  const p2: Pt = {x: end.x - (330 * 1) / len, y: end.y - (330 * slope) / len};
  const p1: Pt = {
    x: start.x + (end.x - start.x) * 0.35,
    y: start.y + (end.y - start.y) * 0.12,
  };
  return {p0: start, p1, p2, p3: end, opacity, growStart, growDur};
};

// 中景支流 6 条 (主角层), 错峰 4–6f
const MID_TRIBS: Trib[] = [
  makeTrib({x: -380, y: 130}, 1020, 0.62, 6, 32),
  makeTrib({x: -260, y: 880}, 1180, 0.55, 10, 32),
  makeTrib({x: -60, y: 40}, 1330, 0.7, 15, 32),
  makeTrib({x: 60, y: 960}, 1500, 0.5, 19, 32),
  makeTrib({x: 320, y: 210}, 1680, 0.6, 24, 32),
  makeTrib({x: 420, y: 790}, 1840, 0.55, 30, 32),
];

const tribPath = (t: Trib) =>
  `M ${t.p0.x} ${t.p0.y} C ${t.p1.x} ${t.p1.y}, ${t.p2.x} ${t.p2.y}, ${t.p3.x} ${t.p3.y}`;

// 远景 4 条: 更细更暗, 向更深处的远点收拢
// (控制点形式, 供节点标记取样——卡"视差可见性": 特征点穿过画面)
type FarLine = {p0: Pt; p1: Pt; p2: Pt; p3: Pt; growStart: number; growDur: number; op: number};
const FAR_LINES: FarLine[] = [
  {p0: {x: -300, y: 260}, p1: {x: 700, y: 250}, p2: {x: 2400, y: 330}, p3: {x: 3400, y: 490}, growStart: 12, growDur: 36, op: 0.3},
  {p0: {x: -200, y: 700}, p1: {x: 800, y: 690}, p2: {x: 2500, y: 610}, p3: {x: 3450, y: 492}, growStart: 16, growDur: 36, op: 0.26},
  {p0: {x: -350, y: 400}, p1: {x: 900, y: 390}, p2: {x: 2600, y: 420}, p3: {x: 3500, y: 493}, growStart: 22, growDur: 36, op: 0.33},
  {p0: {x: -250, y: 590}, p1: {x: 850, y: 600}, p2: {x: 2650, y: 560}, p3: {x: 3520, y: 494}, growStart: 27, growDur: 36, op: 0.24},
];
const farPath = (l: FarLine) =>
  `M ${l.p0.x} ${l.p0.y} C ${l.p1.x} ${l.p1.y}, ${l.p2.x} ${l.p2.y}, ${l.p3.x} ${l.p3.y}`;
// 远景节点标记: 每条线 3 枚暗点, 随层平移穿过画面 → 视差可辨
const FAR_NODE_TS = [0.28, 0.55, 0.82];

// 近景: 大虚焦斜向流过前景 (与横移运动方向成明显角度——
// 卡"视差可见性": 平行于运动方向的线沿自身滑动不可见)
const NEAR_LINES = [
  {d: 'M 130 1270 C 480 830, 880 360, 1290 -130', growStart: 2, growDur: 40, op: 0.22, w: 9},
  {d: 'M 1480 1240 C 1830 800, 2180 330, 2540 -110', growStart: 10, growDur: 40, op: 0.18, w: 7},
];

// ---------- 标签 (中景层, 全部虚构 ID) ----------
type LabelSpec = {trib: number; t: number; id: string; appear: number; above: boolean};

// 错峰 6–10f 间隔; 最后一枚 f103, 距交棒帧 (f120) ≥15f 无新增
const LABELS: LabelSpec[] = [
  {trib: 0, t: 0.62, id: 'OKR-1024', appear: 48, above: true},
  {trib: 1, t: 0.58, id: 'TEAM-4417', appear: 56, above: false},
  {trib: 2, t: 0.66, id: 'KR-2093', appear: 66, above: true},
  {trib: 3, t: 0.6, id: 'SYNC-3308', appear: 74, above: false},
  {trib: 4, t: 0.68, id: 'OBJ-2471', appear: 84, above: true},
  {trib: 5, t: 0.64, id: 'PLAN-9124', appear: 93, above: false},
  {trib: 4, t: 0.86, id: 'GOAL-7752', appear: 103, above: true},
];

// 汇点 (偏画面一侧, 收尾亮部引导视线)
const CONV: Pt = {x: 1850, y: trunkY(1850)};

// ---------- component ----------
export const DatavizLandscapeOpen: React.FC = () => {
  const frame = useCurrentFrame();

  // 相机: 匀稳横移 3.2px/f (2–5 区间), 全程斜率恒定 (不急刹)
  const camX = frame * 3.2;
  // 极缓 zoom 1.0 → 1.06
  const zoom = interpolate(frame, [0, DUR - 1], [1, 1.06]);

  // 视差: 近 1.4× / 中 1× / 远 0.6×
  const farX = -camX * 0.6;
  const midX = -camX * 1.0;
  const nearX = -camX * 1.4;

  // 主干先行: f0–38 out-cubic
  const trunkGrow = growth(frame, 0, 38);

  // 流动感 (卡"流动感"行): draw-on 完成后, 虚线相位低速漂向汇点
  // 速度 1.5px/f (卡 1–2), 叠加透明度 ≤0.3; 生长完成后才淡入
  const flowOffset = -frame * 1.5; // dashoffset 递减 = 相位沿画线方向(向汇点)漂移
  const flowDash = '14 56'; // 世界像素单位
  const flowIn = (gEnd: number) =>
    interpolate(frame, [gEnd, gEnd + 20], [0, 1], {
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp',
    });

  // 收尾亮部 (交棒段 f120+ 缓升, 引导视线向汇点/右侧)
  const handoffGlow = interpolate(frame, [118, 160], [0, 0.4], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  const layerStyle = (tx: number): React.CSSProperties => ({
    position: 'absolute',
    left: 0,
    top: 0,
    width: WORLD_W,
    height: H,
    transform: `translateX(${tx}px)`,
  });

  return (
    <AbsoluteFill style={{backgroundColor: '#050505', overflow: 'hidden'}}>
      {/* zoom 容器: 以画面中心为原点 */}
      <AbsoluteFill style={{transform: `scale(${zoom})`, transformOrigin: '50% 50%'}}>
        {/* 底色微渐变, 避免纯黑死平 */}
        <AbsoluteFill
          style={{
            background:
              'radial-gradient(120% 90% at 62% 45%, #0b0b0d 0%, #060607 55%, #040404 100%)',
          }}
        />

        {/* ---- 远景层 (视差 0.6×) ---- */}
        <div style={layerStyle(farX)}>
          <svg width={WORLD_W} height={H} style={{position: 'absolute'}}>
            {FAR_LINES.map((l, i) => {
              const g = growth(frame, l.growStart, l.growDur);
              return (
                <g key={i}>
                  <path
                    d={farPath(l)}
                    fill="none"
                    stroke="#ffffff"
                    strokeWidth={1.3}
                    strokeLinecap="round"
                    opacity={l.op}
                    pathLength={1}
                    strokeDasharray={1}
                    strokeDashoffset={1 - g}
                  />
                  {/* 节点标记: 可见特征点穿过画面, 让 0.6× 层位移可辨 */}
                  {FAR_NODE_TS.map((t, j) => {
                    const p = cubicAt(l.p0, l.p1, l.p2, l.p3, t);
                    return (
                      <circle
                        key={j}
                        cx={p.x}
                        cy={p.y}
                        r={3}
                        fill="#ffffff"
                        opacity={t <= g ? l.op + 0.08 : 0}
                      />
                    );
                  })}
                </g>
              );
            })}
          </svg>
        </div>

        {/* ---- 中景层 (主角层, 视差 1×) ---- */}
        <div style={layerStyle(midX)}>
          <svg width={WORLD_W} height={H} style={{position: 'absolute'}}>
            {/* 微辉光垫层: 1–2px soft glow 压出"发光线"(卡坑注, Q4 单点许可内) */}
            <g style={{filter: 'blur(3px)'}}>
              <path
                d={trunkPath()}
                fill="none"
                stroke="#ffffff"
                strokeWidth={7}
                strokeLinecap="round"
                opacity={0.16}
                pathLength={1}
                strokeDasharray={1}
                strokeDashoffset={1 - trunkGrow}
              />
              {MID_TRIBS.map((t, i) => (
                <path
                  key={i}
                  d={tribPath(t)}
                  fill="none"
                  stroke="#ffffff"
                  strokeWidth={5.5}
                  strokeLinecap="round"
                  opacity={t.opacity * 0.18}
                  pathLength={1}
                  strokeDasharray={1}
                  strokeDashoffset={1 - growth(frame, t.growStart, t.growDur)}
                />
              ))}
            </g>
            {/* 主干 (唯一) */}
            <path
              d={trunkPath()}
              fill="none"
              stroke="#ffffff"
              strokeWidth={2.8}
              strokeLinecap="round"
              opacity={0.8}
              pathLength={1}
              strokeDasharray={1}
              strokeDashoffset={1 - trunkGrow}
            />
            {/* 支流, 错峰生长 */}
            {MID_TRIBS.map((t, i) => (
              <path
                key={i}
                d={tribPath(t)}
                fill="none"
                stroke="#ffffff"
                strokeWidth={2.2}
                strokeLinecap="round"
                opacity={t.opacity}
                pathLength={1}
                strokeDasharray={1}
                strokeDashoffset={1 - growth(frame, t.growStart, t.growDur)}
              />
            ))}
            {/* 流动感: 虚线相位沿线漂向汇点 (1.5px/f, 叠加透明度 ≤0.3) */}
            <path
              d={trunkPath()}
              fill="none"
              stroke="#ffffff"
              strokeWidth={2.8}
              strokeLinecap="round"
              opacity={0.3 * flowIn(38)}
              strokeDasharray={flowDash}
              strokeDashoffset={flowOffset}
            />
            {MID_TRIBS.map((t, i) => (
              <path
                key={`flow-${i}`}
                d={tribPath(t)}
                fill="none"
                stroke="#ffffff"
                strokeWidth={2.2}
                strokeLinecap="round"
                opacity={0.26 * flowIn(t.growStart + t.growDur)}
                strokeDasharray={flowDash}
                strokeDashoffset={flowOffset}
              />
            ))}
          </svg>

          {/* 标签: 方块图钉 + 等宽字虚构 ID, 错峰淡入 + 沿线微漂 */}
          {LABELS.map((l) => {
            const trib = MID_TRIBS[l.trib];
            const base = cubicAt(trib.p0, trib.p1, trib.p2, trib.p3, l.t);
            const fadeIn = interpolate(frame, [l.appear, l.appear + 12], [0, 1], {
              extrapolateLeft: 'clamp',
              extrapolateRight: 'clamp',
            });
            // 沿线微漂: 出现后沿切线方向缓移 ~6px
            const drift = interpolate(frame, [l.appear, DUR - 1], [0, 6], {
              extrapolateLeft: 'clamp',
              extrapolateRight: 'clamp',
            });
            const ahead = cubicAt(trib.p0, trib.p1, trib.p2, trib.p3, Math.min(1, l.t + 0.02));
            const dx = ahead.x - base.x;
            const dy = ahead.y - base.y;
            const dl = Math.hypot(dx, dy) || 1;
            const px = base.x + (dx / dl) * drift;
            const py = base.y + (dy / dl) * drift;
            // 图钉锚在线上 (中心 = 线上取样点, 卡坑注: 钉线分离读作浮尘);
            // 文字沿垂直方向偏移, 避开线体
            const textOff = l.above ? -38 : 20;
            return (
              <React.Fragment key={l.id}>
                <div
                  style={{
                    position: 'absolute',
                    left: px - 8,
                    top: py - 8,
                    width: 16,
                    height: 16,
                    backgroundColor: '#ffffff',
                    opacity: fadeIn * 0.9,
                  }}
                />
                <span
                  style={{
                    position: 'absolute',
                    left: px + 14,
                    top: py + textOff,
                    fontFamily: 'Menlo, "SF Mono", Consolas, monospace',
                    fontSize: 22,
                    letterSpacing: 3,
                    color: '#e8e8e8',
                    whiteSpace: 'nowrap',
                    opacity: fadeIn * 0.92,
                  }}
                >
                  {l.id}
                </span>
              </React.Fragment>
            );
          })}

          {/* 交棒亮部: 汇点方向留亮, 引导视线接下一镜头 */}
          <div
            style={{
              position: 'absolute',
              left: CONV.x - 420,
              top: CONV.y - 260,
              width: 840,
              height: 520,
              background:
                'radial-gradient(50% 50% at 50% 50%, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0.12) 45%, rgba(255,255,255,0) 72%)',
              opacity: handoffGlow,
              filter: 'blur(18px)',
              pointerEvents: 'none',
            }}
          />
        </div>

        {/* ---- 近景层 (大虚焦, 视差 1.4×) ---- */}
        <div style={{...layerStyle(nearX), filter: 'blur(13px)'}}>
          <svg width={WORLD_W} height={H} style={{position: 'absolute'}}>
            {NEAR_LINES.map((l, i) => (
              <path
                key={i}
                d={l.d}
                fill="none"
                stroke="#ffffff"
                strokeWidth={l.w}
                strokeLinecap="round"
                opacity={l.op}
                pathLength={1}
                strokeDasharray={1}
                strokeDashoffset={1 - growth(frame, l.growStart, l.growDur)}
              />
            ))}
          </svg>
        </div>

        {/* 轻 vignette 收住四角 */}
        <AbsoluteFill
          style={{
            background:
              'radial-gradient(115% 95% at 50% 50%, rgba(0,0,0,0) 62%, rgba(0,0,0,0.5) 100%)',
            pointerEvents: 'none',
          }}
        />
      </AbsoluteFill>
    </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/opening/dataviz-landscape-open/DatavizLandscapeOpen.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