{
  "slug": "row-embed",
  "title": "Row embed",
  "tagline": "Content rows drop from above like cards, level out via rotateX, and an accent-color seam lights up along the bottom edge as they embed.",
  "description": "Content rows drop from above like cards, level out via rotateX, and an accent-color seam lights up along the bottom edge as they embed.",
  "category": "ui-entrance",
  "tags": [
    "shotcraft",
    "remotion",
    "ui-entrance"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 960,
    "height": 540,
    "fps": 30,
    "duration": 3.33,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/row-embed/poster-29094d7fbef2.jpg",
    "video": "https://gimgs.net/motion/media/row-embed/video-6bbf9be7faef.mp4"
  },
  "variants": [
    {
      "key": "row-embed",
      "label": "row-embed",
      "description": "Content rows drop from above like cards, level out via rotateX, and an accent-color seam lights up along the bottom edge the instant they embed.",
      "use": "",
      "video": "https://gimgs.net/motion/media/row-embed/video-6bbf9be7faef.mp4",
      "poster": "https://gimgs.net/motion/media/row-embed/poster-29094d7fbef2.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/row-embed.html",
  "json_url": "https://gimgs.net/motion/row-embed.json",
  "template_url": "https://gimgs.net/motion/row-embed/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/row-embed.md",
  "updated_at": "2026-09-24T07:40:18.163Z",
  "prompt": "## When to use\n\nA detail-page/list shot for \"structured data growing into the page\"; a batch entrance for row-level content\n\n## Intention\n\nA detail page's rows don't \"appear,\" they \"grow in\" — each row drops from above and embeds seamlessly into the page layout, and the accent-color seam that lights up at the moment of embedding is the visual sound effect of a \"click\" snapping into place.\n\n## Duration & energy\n\n- Duration: About 2s (12–68f)\n- Energy: Medium\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n详情页的行不是\"显示出来\"而是\"长进去\"——每一行从空中降下并严丝合缝嵌入页面布局，嵌入瞬间的强调色缝是\"咔哒\"扣上的视觉拟音。\n\n## 动效核心\n- 行按节拍逐条从上空飞入，rotateX 从仰角收平，scale 略过冲后 press 回弹\n- 行位先盖页面底色补丁占位，落地后补丁消失让纹理透出\n- 嵌入瞬间行底边一道 2px 强调色缝从中心向两侧展开后淡出\n- 相机同时匀速下摇，行雨与运镜并行\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 节拍 | 第 i 行 cue = 12 + i·9，飞行 12f，最后一行 60f 落地、强调色缝 68f 收尾 ≤ 镜头 70f 预算 | 先算\"最后一行何时落地\"再定 cue 间隔——节拍×行数要对预算（同 document-typewriter-reveal 卡的核心算术） |\n| 飞行姿态 | `perspective(900px) translateY(−120·air) rotateX(16°·air)`，scale 1.06→0.995 再 4f press-bounce 回 1 | rotateX 收平是\"嵌入\"的关键读感；纯垂直下落读作贴纸 |\n| 飞行体 | backgroundImage 取整页截图、backgroundPosition 负偏移裁出该行 | 纹理裁片不重绘内容（Q1）——重绘的行和页面字体渲染有肉眼可见差异 |\n| 空槽补丁 | 行位先盖页面底色 patch，落地后 2f 消失 | 无补丁时纹理里烤入的行会先透出来，飞入变成\"重影\" |\n| 嵌入闪光 | 底边 2px 强调色缝从中心向两侧 5f 展开（Easing.out cubic）、8f 淡出，带 6px 辉光 | 缝只在底边、只闪一次；四边都闪读作选中框 |\n| 相机 | cy 300→760 匀速 75f 下摇 | 运镜与行雨并行制造\"边看边长\"的感觉；相机若等行落完再动，镜头会拖 |\n\n## 声音\n段落切入钉 transition-soft（模板片钉在 f475）；行落地不逐条配音——行数多、间隔匀时逐条 pop 会机枪化，靠画面的强调色缝做\"视觉拟音\"即可（S2 反面自检）。\n\n## 已知坑\n- 飞入终点必须是页面布局真实槽位、落地即嵌入（Q9）——要\"从空中飞入，嵌入 dashboard\"；悬浮其上不落地会显得假\n- 强调色缝等光效必须裁进承载元素圆角边界内（Q4 判例），光溢出圆角是廉价感典型来源\n\n## 参考实现\ndemos/ui-entrance/row-embed/RowEmbed.tsx（原 template/src/aifl/live/SceneDetail.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/ui-entrance/row-embed/RowEmbed.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/row-embed/RowEmbed.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/ui-entrance/row-embed/RowEmbed.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/ui-entrance/row-embed/RowEmbed.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/row-embed/RowEmbed.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": 100,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/row-embed/RowEmbed.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/ui-entrance/row-embed/RowEmbed.tsx =====\n// row-embed —— 内容行从空中降下、rotateX 收平、嵌入瞬间底边亮强调色缝\n// \"结构化数据长进页面\"的详情页/列表镜头。参考实现从 template SceneDetail 剥离：\n// 行按节拍逐条从上空飞入（perspective translateY(−120·air) + rotateX(16°·air) +\n// scale 略过冲后 press 回弹），行位先盖页面底色补丁占位、落地后消失让纹理透出，\n// 嵌入瞬间行底边 2px 强调色缝从中心向两侧展开后淡出，相机同时匀速下摇。\n// 节拍：第 i 行 cue = 12 + i·9，飞行 12f，最后一行 60f 落地、强调色缝 68f 收尾。\n// 飞行体 = 整页截图 backgroundImage 负偏移裁片（Q1：裁片不重绘内容）。\nimport React from 'react';\nimport { interpolate, staticFile, useCurrentFrame, Easing } from 'remotion';\nimport { PageCam2D, CamKey2D } from '../../_fixtures/PageCam2D';\nimport layout from '../../_textures/live-layout.json';\n\nexport const ROW_EMBED_DURATION = 68;\n\nconst DETAIL_H = layout.detail.pageH;\nconst rows = layout.detail.rows;\n\nconst DETAIL_CAM: CamKey2D[] = [\n  { frame: 0, cx: 960, cy: 300, zoom: 1.1 },\n  { frame: 68, cx: 960, cy: 760, zoom: 1.0 },\n];\n\nconst FLY_EASE = Easing.bezier(0.3, 0, 0.25, 1);\nconst detailSrc = staticFile('textures/live/detail-full.png');\n\nexport const RowEmbed: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  return (\n    <PageCam2D src=\"textures/live/detail-full.png\" pageH={DETAIL_H} keys={DETAIL_CAM}>\n      {rows.map((r, i) => {\n        const cue = 12 + i * 9;\n        const land = cue + 12;\n\n        // empty-slot paper patch (under the flying row), gone 2f after landing\n        const patchOpacity = interpolate(frame, [land, land + 2], [1, 0], {\n          extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n        });\n\n        // flying row: texture-crop dropping in from the air\n        let flyer: React.ReactNode = null;\n        if (frame >= cue && frame < cue + 16) {\n          const p = interpolate(frame, [cue, cue + 12], [0, 1], {\n            extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: FLY_EASE,\n          });\n          const appear = interpolate(frame, [cue, cue + 3], [0, 1], {\n            extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n          });\n          const scale =\n            frame < land\n              ? 1.06 - 0.065 * p\n              : interpolate(frame, [land, land + 4], [0.995, 1], {\n                  extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.quad),\n                });\n          const air = 1 - p;\n          flyer = (\n            <div\n              key={`row-${i}`}\n              style={{\n                position: 'absolute', left: r.x, top: r.y, width: r.w, height: r.h,\n                borderRadius: 8, backgroundColor: '#fff',\n                backgroundImage: `url(${detailSrc})`,\n                backgroundSize: `1920px ${DETAIL_H}px`,\n                backgroundPosition: `-${r.x}px -${r.y}px`,\n                opacity: appear,\n                transform: `perspective(900px) translateY(${-120 * air}px) rotateX(${16 * air}deg) scale(${scale})`,\n                boxShadow: `0 ${30 * air}px ${60 * air}px rgba(30,25,18,${0.22 * air}), 0 ${8 * air}px ${16 * air}px rgba(30,25,18,${0.12 * air})`,\n                zIndex: 3, pointerEvents: 'none',\n              }}\n            />\n          );\n        }\n\n        // embed flash: 2px amber seam at the row's bottom edge, expanding from\n        // the center on touchdown, then fading out\n        let seam: React.ReactNode = null;\n        if (frame >= land && frame < land + 8) {\n          const spread = interpolate(frame, [land, land + 5], [0, 1], {\n            extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic),\n          });\n          const seamOpacity = interpolate(frame, [land, land + 2, land + 8], [1, 1, 0], {\n            extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n          });\n          const seamW = r.w * spread;\n          seam = (\n            <div\n              key={`seam-${i}`}\n              style={{\n                position: 'absolute',\n                left: r.x + (r.w - seamW) / 2,\n                top: r.y + r.h - 2,\n                width: seamW, height: 2,\n                background: 'oklch(58% 0.13 65)',\n                boxShadow: '0 0 6px rgba(180,120,50,0.35)',\n                opacity: seamOpacity, zIndex: 4, pointerEvents: 'none',\n              }}\n            />\n          );\n        }\n\n        return (\n          <React.Fragment key={i}>\n            {patchOpacity > 0 ? (\n              <div\n                key={`patch-${i}`}\n                style={{\n                  position: 'absolute', left: r.x - 8, top: r.y - 4,\n                  width: r.w + 24, height: r.h + 8, background: '#fdfcfa',\n                  opacity: patchOpacity, zIndex: 1, pointerEvents: 'none',\n                }}\n              />\n            ) : null}\n            {flyer}\n            {seam}\n          </React.Fragment>\n        );\n      })}\n    </PageCam2D>\n  );\n};\n\n\n"
}