{
  "slug": "overhead-camera-moves",
  "title": "Overhead camera moves",
  "tagline": "Two overhead reveal techniques: tilt-reveal pitches up to level, and overhead-tabletop-drop slides across a card array then dives in.",
  "description": "Two overhead reveal techniques: tilt-reveal pitches up to level, and overhead-tabletop-drop slides across a card array then dives in.",
  "category": "camera",
  "tags": [
    "shotcraft",
    "remotion",
    "camera",
    "opening",
    "transition"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 4.89,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/overhead-camera-moves/poster-1633c91f0b99.jpg",
    "video": "https://gimgs.net/motion/media/overhead-camera-moves/video-32ca50e446a3.mp4"
  },
  "variants": [
    {
      "key": "tilt-reveal",
      "label": "tilt-reveal",
      "description": "The whole page lies flat at rotateX -80° inside a perspective container, pitching up to level over ~43f; rotateX/scale/translateY all share an out-cubic ease with a slight overshoot at the end.",
      "use": "Opening establishing shot; single subject",
      "video": "https://gimgs.net/motion/media/overhead-camera-moves/video-32ca50e446a3.mp4",
      "poster": "https://gimgs.net/motion/media/overhead-camera-moves/poster-1633c91f0b99.jpg"
    },
    {
      "key": "overhead-tabletop-drop",
      "label": "overhead-tabletop-drop",
      "description": "The card array lies flat at rotateX 62°; the pan segment only moves translateX to slide across, then the drop segment runs angle/scale/position together to dive in and land on the final layout.",
      "use": "Picking one after surveying multiple pages; works as both transition and opening",
      "video": "https://gimgs.net/motion/media/overhead-camera-moves/video-392e46c13884.mp4",
      "poster": "https://gimgs.net/motion/media/overhead-camera-moves/poster-overhead-tabletop-drop-d0e977451ab9.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/overhead-camera-moves.html",
  "json_url": "https://gimgs.net/motion/overhead-camera-moves.json",
  "template_url": "https://gimgs.net/motion/overhead-camera-moves/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/overhead-camera-moves.md",
  "updated_at": "2026-09-24T07:40:10.778Z",
  "prompt": "## When to use\n\nOpenings/transitions that tell the story through pitch angle: use A for a single-page establishing shot, B for surveying multiple pages and diving into one\n\n## Intention\n\nThe library has never had an overhead angle: crane-rise is a \"pan + zoom\" rise (pitch stays fixed), and space-camera style C, drone-dive, is a one-take dive descent. These two styles let **the pitch angle itself tell the story**: A is a reveal — the whole page lies flat via rotateX, opening on just a narrow perspective sliver along the top edge, then the camera \"looks up\" back to level as content floods in row by row, like unrolling a drawing on a desk; B is a selection — three page cards lie flat as a tabletop array, the camera pans overhead across them (Wes Anderson tabletop), then suddenly dives into the target page and pitches up to fill the frame — \"survey them all, then pick this one.\" Choose by use case: single-page opening uses A, multi-page transition/opening uses B.\n\n## Duration & energy\n\n- Duration: A ~4.8s / B ~4.7s\n- Energy: A Medium / B Medium-high\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n库内俯拍视角一直空缺——crane-rise 是\"平移+缩放\"的升起（俯仰角\n不动），space-camera C 式 drone-dive 是俯冲降落一镜到底；这两式让\n**俯仰角本身讲故事**：A 是揭示——整页 rotateX 平躺、开场只见上缘\n一条透视窄带，机位\"抬头\"回正，内容一排排涌入视野，像掀开桌上的\n图纸；B 是选择——三张页面卡平躺成桌面卡阵，相机俯拍横向滑过依次\n掠过（韦斯·安德森 tabletop），滑到目标页猛地俯冲扎入、抬正成满屏，\n\"巡视一圈，就选这张\"。选型：单页开场用 A，多页转场/开场用 B。\n\n## 两式选型\n| 式 | 做法 | 适用 |\n|----|------|------|\n| A tilt-reveal | perspective 容器内整页 rotateX -80° 平躺，~43f 抬正，rotateX/scale/translateY 共用 out-cubic，末端轻过冲 | 开场 establishing；单主体 |\n| B overhead-tabletop-drop | 卡阵平躺 rotateX 62°，pan 段只动 translateX 横滑掠过，drop 段角度/缩放/位移三通道同跑扎入落版 | 多页巡视后择一；转场/开场两用 |\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| A 俯角 | rotateX 关键帧 [-80, 2.6, -0.9, 0]（f25→68→72→76），transformOrigin 50% 0% | 首版 -55° 太弱：f0 仍见半页，无\"窄带\"悬念（实渲判例） |\n| A 联动 | perspective 600→1200、perspectiveOrigin 5%→40% 随进度插值；scale 3.2→1 + translateY 200→0 | perspective 不联动则俯角初始见不到\"只露顶栏\" |\n| A 收尾 | 全部动画 f76 结束，145f 留 69f 真静止 | 过冲幅度 ≤3°，再大读作弹簧不是机位 |\n| B pan 段 | 0–55f 只改 translateX +700→-650（in-out cubic），角度不动 | pan 段动角度 = 两段职责糊掉（判例：pan 巡视、drop 表态） |\n| B drop 段 | 55–85f 三通道同跑：rotateX 62→-1.8→0.6→0（out-cubic 过冲）、scale 1→2.04→2.0、translateX -650→0 | 三通道必须同跑；分先后读作两个动作 |\n| B 卡阵 | 卡片 996×560（16:9 精确比），落版 scale 2.0 恰满屏正视；transform 顺序 translateX→rotateX→scale | 顺序错了 pan 会带出弧线轨迹 |\n| B 地板 | 浅灰网格地板 | 无地板参照，横滑段读不出\"相机在动\" |\n| B 收尾 | 全部动画 f93 结束，140f 留 47f 真静止 | — |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- 大角度透视下卡片必须 backfaceVisibility hidden + translateZ(4px)\n  兜底，否则个别帧背面/深度穿帮\n- A 与 crane-rise-reveal 同片二选一——都是开场揭示，两个\"揭示开场\"\n  等于没有揭示\n- B pan 段角度锁死是命门（两段职责分离判例）：巡视归巡视、扎入归\n  扎入，混着动观众不知道相机想干嘛\n- 两式都是\"相机大动作\"，与 space-camera/crane-rise 同屏段落互斥，\n  一个段落只能有一个持镜人\n\n## 参考实现\ndemos/camera/overhead-camera-moves/\n（OverheadTabletopDrop.tsx / TiltReveal.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx\n- demos/camera/overhead-camera-moves/TiltReveal.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/overhead-camera-moves/TiltReveal.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/camera/overhead-camera-moves/OverheadTabletopDrop.tsx, demos/camera/overhead-camera-moves/TiltReveal.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/camera/overhead-camera-moves/OverheadTabletopDrop.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx"
    },
    {
      "path": "demos/camera/overhead-camera-moves/TiltReveal.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/overhead-camera-moves/TiltReveal.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": 147,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx",
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/camera/overhead-camera-moves/TiltReveal.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/camera/overhead-camera-moves/OverheadTabletopDrop.tsx =====\n// overhead-tabletop-drop｜上帝视角桌面滑降\n// perspective(1600px) 内三张\"页面卡\"rotateX(62°) 平躺成桌面卡阵。\n// 0–55f 相机横向滑过（只动 translateX，缓入缓出）；55–85f 骤降扎入：\n// rotateX 62→0 + 世界 scale 1→2.0（2% 过冲）+ translateX -650→0 三者同跑，\n// 中间卡对位落成正视满屏 dashboard。全部动画 f=93 结束，收尾真静止 52f。\n// 卡片 996×560（spec 900→996，使 FakeDashboard 0.5185 缩放严丝合缝、\n// 落版 16:9 精确对位），间距 140px。\nimport React from 'react';\nimport { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';\nimport { FakeDashboard, Card, G } from '../../_fixtures/Fixtures';\n\nconst CARD_W = 996;\nconst CARD_H = 560;\nconst GAP = 140;\nconst PITCH = CARD_W + GAP; // 1136\nconst INNER = CARD_H / 1080; // 0.5185：FakeDashboard 缩到卡内正好铺满\n\nconst PAN_END = 55;\nconst DROP_END = 85;\n\n// 单张\"页面卡\"：白底圆角卡，内容按 INNER 缩放塞满\nconst PageCard: React.FC<{ x: number; children: React.ReactNode }> = ({ x, children }) => (\n  <div\n    style={{\n      position: 'absolute',\n      left: x - CARD_W / 2,\n      top: -CARD_H / 2,\n      width: CARD_W,\n      height: CARD_H,\n      background: G.card,\n      border: `2px solid ${G.border}`,\n      borderRadius: 14,\n      overflow: 'hidden',\n      boxSizing: 'border-box',\n      boxShadow: '0 24px 48px rgba(0,0,0,0.22)',\n      backfaceVisibility: 'hidden',\n      transform: 'translateZ(4px)',\n    }}\n  >\n    <div style={{ width: 1920, height: 1080, transform: `scale(${INNER})`, transformOrigin: '0 0' }}>\n      {children}\n    </div>\n  </div>\n);\n\nexport const OverheadTabletopDrop: React.FC = () => {\n  const f = useCurrentFrame();\n\n  // 0–55f 横滑：只动 translateX，缓入缓出\n  const panX = interpolate(f, [0, PAN_END], [700, -650], {\n    easing: Easing.inOut(Easing.cubic),\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n\n  // 55–85f 骤降：角度 / 缩放 / 位移三者同跑，out-cubic + 2% 过冲\n  const drop = interpolate(f, [PAN_END, DROP_END], [0, 1], {\n    easing: Easing.out(Easing.cubic),\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const rotX = interpolate(f, [PAN_END, DROP_END, DROP_END + 4, DROP_END + 8], [62, -1.8, 0.6, 0], {\n    easing: Easing.out(Easing.cubic),\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const scale = interpolate(f, [PAN_END, DROP_END, DROP_END + 7], [1, 2.04, 2.0], {\n    easing: Easing.out(Easing.cubic),\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const dropX = interpolate(drop, [0, 1], [panX, 0]); // drop 段把 -650 收回 0\n\n  const tx = f <= PAN_END ? panX : dropX;\n\n  return (\n    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>\n      <div style={{ position: 'absolute', inset: 0, perspective: 1600, perspectiveOrigin: '50% 42%' }}>\n        {/* 世界原点 = 屏心；先 in-plane scale，再 rotateX，最后 translateX */}\n        <div\n          style={{\n            position: 'absolute',\n            left: '50%',\n            top: '50%',\n            width: 0,\n            height: 0,\n            transformStyle: 'preserve-3d',\n            transform: `translateX(${tx}px) rotateX(${rotX}deg) scale(${scale})`,\n          }}\n        >\n          {/* 桌面：浅灰底 + 网格线，给横滑段提供运动参照 */}\n          <div\n            style={{\n              position: 'absolute',\n              left: -2800,\n              top: -760,\n              width: 5600,\n              height: 1520,\n              background: '#e6e6e4',\n              backgroundImage: `repeating-linear-gradient(90deg, ${G.line} 0px, ${G.line} 2px, transparent 2px, transparent 160px), repeating-linear-gradient(0deg, ${G.line} 0px, ${G.line} 2px, transparent 2px, transparent 160px)`,\n              backfaceVisibility: 'hidden',\n            }}\n          />\n          <PageCard x={-PITCH}>\n            <FakeDashboard variant=\"B\" />\n          </PageCard>\n          {/* 目标页：落版满屏 */}\n          <PageCard x={0}>\n            <FakeDashboard variant=\"A\" />\n          </PageCard>\n          <PageCard x={PITCH}>\n            <div\n              style={{\n                width: 1920,\n                height: 1080,\n                background: G.panel,\n                display: 'flex',\n                alignItems: 'center',\n                justifyContent: 'center',\n              }}\n            >\n              <Card w={760} h={520} seed={4} />\n            </div>\n          </PageCard>\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n\n// ===== demos/camera/overhead-camera-moves/TiltReveal.tsx =====\n// tilt-reveal｜俯仰揭示\n// 开场俯视 dashboard 顶部（rotateX 平躺、只露顶栏），~43f 机位抬头回正，\n// 内容一排排涌入视野。out-cubic + 末端轻微过冲回正，落定真静止 ≥35f。\nimport React from 'react';\nimport { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';\nimport { FakeDashboard, G } from '../../_fixtures/Fixtures';\n\nconst HOLD = 25; // 俯角定格\nconst MOVE = 43; // 主抬升\n// 过冲：-55° → +2.6° → -0.9° → 0°，全部动画在 f=76 结束（145-76=69f 真静止）\n\nexport const TiltReveal: React.FC = () => {\n  const f = useCurrentFrame();\n\n  const rotX = interpolate(\n    f,\n    [HOLD, HOLD + MOVE, HOLD + MOVE + 4, HOLD + MOVE + 8],\n    [-80, 2.6, -0.9, 0],\n    { easing: Easing.out(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp' },\n  );\n\n  const p = interpolate(f, [HOLD, HOLD + MOVE], [0, 1], {\n    easing: Easing.out(Easing.cubic),\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const scale = interpolate(p, [0, 1], [3.2, 1]);\n  const ty = interpolate(p, [0, 1], [200, 0]);\n  const persp = interpolate(p, [0, 1], [600, 1200]);\n  const perspY = interpolate(p, [0, 1], [5, 40]);\n\n  return (\n    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>\n      <div\n        style={{\n          position: 'absolute',\n          inset: 0,\n          perspective: persp,\n          perspectiveOrigin: `50% ${perspY}%`,\n        }}\n      >\n        <div\n          style={{\n            width: 1920,\n            height: 1080,\n            transformOrigin: '50% 0%', // 画面上缘\n            transform: `translateY(${ty}px) scale(${scale}) rotateX(${rotX}deg)`,\n          }}\n        >\n          <FakeDashboard variant=\"A\" />\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n\n"
}