{
  "slug": "scanline-assemble-flyin",
  "title": "Scanline assemble fly-in",
  "tagline": "A scan line sweeps down an empty grid, flying each region's component in from off-screen — finishing as the scan ends.",
  "description": "A scan line sweeps down an empty grid, flying each region's component in from off-screen — finishing as the scan ends.",
  "category": "effects",
  "tags": [
    "shotcraft",
    "remotion",
    "effects"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 5.1,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/scanline-assemble-flyin/poster-6aa95a9ebe73.jpg",
    "video": "https://gimgs.net/motion/media/scanline-assemble-flyin/video-823f7676de9c.mp4"
  },
  "variants": [
    {
      "key": "scanline-assemble-flyin",
      "label": "scanline-assemble-flyin",
      "description": "The page opens as an empty dark grid, and a bright scan line sweeps top to bottom; wherever it reaches each region's landing point, the component there flies in from off-screen and snaps into place with motion-blur trailing and a flash-edge on landing — the whole page finishes assembling exactly as the scan completes.",
      "use": "",
      "video": "https://gimgs.net/motion/media/scanline-assemble-flyin/video-823f7676de9c.mp4",
      "poster": "https://gimgs.net/motion/media/scanline-assemble-flyin/poster-6aa95a9ebe73.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/scanline-assemble-flyin.html",
  "json_url": "https://gimgs.net/motion/scanline-assemble-flyin.json",
  "template_url": "https://gimgs.net/motion/scanline-assemble-flyin/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/scanline-assemble-flyin.md",
  "updated_at": "2026-09-24T07:40:20.117Z",
  "prompt": "## When to use\n\nAn opening for \"the page builds itself\"; the core demo for AI website-builder/auto-layout products; a blank-to-finished capability narrative\n\n## Intention\n\nGive \"the page being generated\" a believable construction order: the scan line is the progress bar, the components flying in from off-screen are the construction actions. The critical thing is that **the last piece finishes assembling exactly as the scan finishes** — if the scan line finishes early or a component trails behind, the causal link of \"scan drives assembly\" falls apart.\n\n## Duration & energy\n\n- Duration: About 4.6s (138f@30fps)\n- Energy: Medium-high (the scan line is steady, but every component fly-in is a burst, with rising density)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n让\"页面被生成\"这件事有一个可信的施工顺序：扫描线是施工进度条，\n组件从画外飞进来贴合是施工动作。关键是**扫完那一刻恰好装完最后一块**\n——扫描线提前收工或组件拖尾都会让\"扫描驱动装配\"的因果关系散掉。\n\n## 动效核心\n- 空页底只有暗网格（24px 双向 `repeating-linear-gradient`，\n  `rgba(255,255,255,.025)`、整层 opacity .5）——组件飞入前唯一可见的东西\n- 扫描线 `t 0.05→0.72` 匀速走 y −30→300，\n  0.03→0.08 淡入、0.72→0.77 淡出\n- 触发时刻由组件落点 y 反算：`ft = 0.05 + ((y+30)/330)*0.67 − 0.02`，\n  再钳制最小间隔 0.045；提前 0.02 起飞让组件与扫描线**同时**到位\n- 7 个组件各有独立飞入方案 `from:[dx,dy]` + `rot`：顶栏自上（0,−70），\n  logo 自左（−160,−30, −6°），模块卡自右（230,40, 5°），H1 自左下\n  （−260,60, −4°），CTA 自下（−60,130, 3°），页脚/社交自下方两侧\n- 飞入 `seg(ft, ft+0.15, outBack)` 过冲贴合；运动中按 `1−a` 加\n  `blur(≤2.2px)` 残影，`a>0.97` 即清零\n- 落位瞬间闪一道细亮边：`inset:-3px` 的 1px 强调色描边，\n  `ft+0.11→+0.15` 亮起、`+0.15→+0.26` 收掉\n- 状态行统计 `a≥0.99` 的组件数显示 `BUILD · 0n/07`，\n  `0.80→0.86` 后切 `ASSEMBLY · COMPLETE`\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 扫描线 | t 0.05→0.72 匀速，y −30→300 | 收工时刻要略晚于最后一块落位，才读作\"扫完即装完\" |\n| 触发换算 | ft = 0.05 + ((y+30)/330)·0.67 − 0.02 | 那个 −0.02 是提前起飞量：去掉就变成\"扫过之后才动\"，慢半拍 |\n| 最小间隔 | prev + 0.045 | y 接近的页脚/社交会同时飞；钳制后才有\"一块一块\"的密度 |\n| 飞入位移 | 画外 60–260px，按组件所在方位定 | 位移小于 60px 读作\"抖一下就位\"，不是从画外来 |\n| 飞入旋转 | ±3–8° | 超过 12° 就读作\"翻滚\"，贴合时不服帖 |\n| 飞入缓动 | outBack，0.15 时长 | outBack 的过冲是\"吸附贴合\"的手感；outCubic 就成了滑入 |\n| 残影模糊 | blur ≤2.2px，随 1−a 衰减，a>0.97 清零 | 不清零会留一层永久糊；阈值必须留裕量 |\n| 落位闪边 | inset −3px、1px 强调色，0.04 亮 / 0.11 落 | 闪边是\"咬合\"的听觉替代品，去掉后落位没有实感 |\n\n## 已知坑\n- **触发时刻与 plan 里的落点 y 绑定**：换页面模板后每个组件的 y 要重测，\n  否则组件在扫描线到达前就飞入\n- `filter` 与 `transform` 同时作用于组件节点，落位后必须把 filter 设回\n  `none`（不是 `blur(0)`）——留着合成层会持续吃 GPU\n- 闪边层是 append 到组件内部的子节点，组件本身若有 `overflow:hidden`\n  会把 `inset:-3px` 裁掉，需改成外层兄弟节点\n- 与 `scanline-annotate-focus` 共用同一套页面模板与扫描线；两张卡可以\n  串联（先装配、后分析），但扫描线要换向或留空档，否则读作重复\n- 页面内容为中性占位模板，ACCENT（`#9fb6e8`）可按项目替换\n\n## 参考实现\ndemos/effects/scanline-assemble-flyin/\n（ScanlineAssembleFlyin.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.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/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.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/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.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": 153,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.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/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx =====\n// scanline-assemble-flyin — Scanline Assemble 扫描装配组件飞入（motion-lab 定稿转原生 Remotion）\n// 页面开场为空的暗底网格，一条亮扫描线自上而下掠过；扫过每个区块的落点后，该处组件从画外\n// 四面八方飞入（左上 logo 自左、右侧模块卡自右、H1 自左下、CTA 自下、页脚社交自下方两侧），\n// 带轻微过冲和残影模糊，贴合落位瞬间闪一道细亮边。扫完整页恰好装配完成。\n// 页面内容为中性占位模板，强调色可按项目替换。设计坐标 480×270（DesignStage 等比放大）。\nimport React from 'react';\nimport { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';\n\nexport const SCANLINE_ASSEMBLE_FLYIN_DURATION = 138; // 4600ms @30fps\n\nconst MONO = \"'SF Mono',Menlo,Consolas,monospace\";\nconst SERIF = \"Georgia,'Times New Roman',serif\";\nconst ACCENT = '#9fb6e8'; // 模板强调色，可按项目替换\nconst A_RGB = '159,182,232';\n\n/* ---- 每个组件的飞入方案：y=扫描落点（触发用），from=画外起点位移，rot=起始旋转 ---- */\ntype Plan = { key: string; y: number; from: [number, number]; rot: number; ft: number };\nconst PLAN: Plan[] = (() => {\n  const ps: Plan[] = [\n    { key: 'topbar', y: 30, from: [0, -70], rot: 0, ft: 0 },\n    { key: 'logo', y: 53, from: [-160, -30], rot: -6, ft: 0 },\n    { key: 'module', y: 100, from: [230, 40], rot: 5, ft: 0 },\n    { key: 'h1', y: 148, from: [-260, 60], rot: -4, ft: 0 },\n    { key: 'cta', y: 192, from: [-60, 130], rot: 3, ft: 0 },\n    { key: 'footer', y: 257, from: [-140, 70], rot: 2, ft: 0 },\n    { key: 'social', y: 262, from: [150, 70], rot: -3, ft: 0 },\n  ];\n  // 触发时刻：扫描线（0.05→0.72 纵扫 -30→300）到达组件落点 y，依序钳制最小间隔\n  let prev = -1;\n  for (const pl of ps) {\n    pl.ft = Math.max(0.05 + ((pl.y + 30) / 330) * 0.67 - 0.02, prev + 0.045);\n    prev = pl.ft;\n  }\n  return ps;\n})();\nconst FT: Record<string, number> = Object.fromEntries(PLAN.map((pl) => [pl.key, pl.ft]));\n\nexport const ScanlineAssembleFlyin: React.FC = () => {\n  const t = useT();\n\n  // 扫描线纵扫 + 首尾淡入淡出\n  const ly = lerp(seg(t, 0.05, 0.72), -30, 300);\n  const lineOpacity = seg(t, 0.03, 0.08) * (1 - seg(t, 0.72, 0.77));\n\n  // 组件飞入样式：outBack 过冲贴合 + 运动残影 blur（落位即清晰）\n  const fly = ({ from, rot, ft }: Plan): React.CSSProperties => {\n    const a = seg(t, ft, ft + 0.15, E.outBack);\n    const vis = t >= ft ? 1 : 0;\n    const speed = a > 0 && a < 0.97 ? 1 - a : 0;\n    return {\n      position: 'absolute',\n      opacity: vis * Math.min(1, seg(t, ft, ft + 0.06) * 1.4),\n      transform: `translate(${lerp(a, from[0], 0)}px,${lerp(a, from[1], 0)}px) rotate(${lerp(a, rot, 0)}deg)`,\n      filter: speed > 0.03 ? `blur(${speed * 2.2}px)` : 'none',\n    };\n  };\n  // 贴合瞬间闪一道细亮边（组件内层 inset:-3px 描边）\n  const flash = (ft: number): React.CSSProperties => ({\n    position: 'absolute',\n    inset: -3,\n    border: `1px solid rgba(${A_RGB},.9)`,\n    borderRadius: 4,\n    opacity: seg(t, ft + 0.11, ft + 0.15) * (1 - seg(t, ft + 0.15, ft + 0.26)),\n    pointerEvents: 'none',\n  });\n\n  // 已落位组件计数（a≥0.99 视为贴合完成）\n  const placed = PLAN.reduce((acc, pl) => acc + (seg(t, pl.ft, pl.ft + 0.15, E.outBack) >= 0.99 ? 1 : 0), 0);\n  const done = seg(t, 0.8, 0.86);\n\n  return (\n    <DesignStage bg=\"#0a0b0e\">\n      {/* 页面底渐变：原 CSS 为 #101116→#0c0d11，样片 x264 近黑量化后实测为\n          rgb(15,17,20)→rgb(10,12,16)，按对照帧校准取 #0f1114→#0a0c10 */}\n      <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(180deg,#0f1114,#0a0c10)' }}>\n        {/* 空页底：暗网格（组件飞入前唯一可见的东西） */}\n        <div\n          style={{\n            position: 'absolute',\n            inset: 0,\n            opacity: 0.5,\n            background:\n              'repeating-linear-gradient(0deg,transparent 0 23px,rgba(255,255,255,.025) 23px 24px),' +\n              'repeating-linear-gradient(90deg,transparent 0 23px,rgba(255,255,255,.025) 23px 24px)',\n          }}\n        />\n        {/* 顶栏（url pill + 状态）——整组自上方落下 */}\n        <div style={{ ...fly(PLAN[0]), left: 0, top: 0, width: 480, height: 30 }}>\n          <div style={{ position: 'absolute', left: 18, top: 11, padding: '3px 10px', border: '1px solid #2a2c33', borderRadius: 9, font: `500 7px ${MONO}`, color: '#8d93a0', letterSpacing: 1 }}>app.example.com</div>\n          <div style={{ position: 'absolute', right: 18, top: 14, font: `500 7px ${MONO}`, color: '#565b66', letterSpacing: 1.5 }}>200 OK · TLS</div>\n          <div style={flash(FT.topbar)} />\n        </div>\n        {/* logo：点阵 mark + 衬线字标——自左飞入 */}\n        <div style={{ ...fly(PLAN[1]), left: 24, top: 35, width: 100, height: 18 }}>\n          {[0, 1, 2, 3].map((i) => (\n            <div key={i} style={{ position: 'absolute', width: 4, height: 4, borderRadius: '50%', background: '#e8e9ee', left: (i % 2) * 6, top: 2 + (i >> 1) * 6 }} />\n          ))}\n          <div style={{ position: 'absolute', left: 17, top: 0, font: `400 13px ${SERIF}`, color: '#eceef2' }}>\n            Acme <i>Studio</i>\n          </div>\n          <div style={flash(FT.logo)} />\n        </div>\n        {/* H1 两行 + 注脚——自左下飞入 */}\n        <div style={{ ...fly(PLAN[3]), left: 22, top: 64, width: 242, height: 84 }}>\n          <div style={{ position: 'absolute', left: 0, top: 0, font: `400 29px ${SERIF}`, color: '#f2f3f6', letterSpacing: 0.3 }}>The headline for</div>\n          <div style={{ position: 'absolute', left: 0, top: 36, font: `italic 400 29px ${SERIF}`, color: '#f2f3f6', letterSpacing: 0.3 }}>your product here</div>\n          <div style={{ position: 'absolute', left: 1, top: 79, font: `500 6.5px ${MONO}`, color: '#565b66', letterSpacing: 1.5 }}>H1 · UI-SERIF / GEORGIA</div>\n          <div style={flash(FT.h1)} />\n        </div>\n        {/* CTA 行——自下飞入 */}\n        <div style={{ ...fly(PLAN[4]), left: 22, top: 166, width: 136, height: 26 }}>\n          <div style={{ position: 'absolute', left: 0, top: 0, padding: '6px 13px', border: '1px solid #3a3d46', borderRadius: 12, font: `600 7.5px ${MONO}`, color: '#e8e9ee', letterSpacing: 1.5 }}>GET STARTED</div>\n          <div style={{ position: 'absolute', left: 100, top: 7, font: `500 7.5px ${MONO}`, color: '#6a707c', letterSpacing: 1.5 }}>DOCS</div>\n          <div style={flash(FT.cta)} />\n        </div>\n        {/* 右侧模块卡——自右飞入 */}\n        {/* 原采集页为 content-box：162×150 外加 1px 边框（总 164×152），补 boxSizing 对齐几何 */}\n        <div style={{ ...fly(PLAN[2]), left: 296, top: 52, width: 162, height: 150, boxSizing: 'content-box', background: '#121319', border: '1px solid #23252d', borderRadius: 5 }}>\n          <div style={{ position: 'absolute', left: 10, top: 9, font: `500 6.5px ${MONO}`, color: '#7c828e', letterSpacing: 1.5 }}>WORK</div>\n          <div style={{ position: 'absolute', right: 10, top: 9, font: `500 6.5px ${MONO}`, color: '#565b66', letterSpacing: 1.5 }}>04 / 08</div>\n          <div style={{ position: 'absolute', left: 0, top: 24, width: '100%', height: 1, background: '#1e2028' }} />\n          <div style={{ position: 'absolute', left: 0, top: 44, width: '100%', textAlign: 'center', font: `italic 400 36px ${SERIF}`, color: '#f4f5f8' }}>sample</div>\n          <div style={{ position: 'absolute', left: 0, top: 104, width: '100%', height: 1, background: '#1e2028' }} />\n          <div style={{ position: 'absolute', left: 10, top: 112, font: `500 6px ${MONO}`, color: '#6a707c', letterSpacing: 1.5 }}>KINETIC TYPE · 04</div>\n          {[0, 1, 2, 3].map((i) => (\n            <div key={i} style={{ position: 'absolute', left: 10 + i * 30, top: 124, width: 24, height: 16, background: '#1a1c23', borderRadius: 2 }} />\n          ))}\n          <div style={{ position: 'absolute', right: 8, top: 129, font: `500 6px ${MONO}`, color: '#565b66' }}>00:30</div>\n          <div style={flash(FT.module)} />\n        </div>\n        {/* 页脚——自左下飞入 */}\n        <div style={{ ...fly(PLAN[5]), left: 18, top: 243, width: 220, height: 14 }}>\n          <div style={{ position: 'absolute', left: 0, top: 3, font: `500 6.5px ${MONO}`, color: '#4c515c', letterSpacing: 1.5 }}>A PRODUCT OF ACME · ACME LABS, INC.</div>\n          <div style={flash(FT.footer)} />\n        </div>\n        {/* 社交 chip——自右下飞入 */}\n        <div style={{ ...fly(PLAN[6]), left: 352, top: 239, width: 108, height: 16 }}>\n          {/* 同为 content-box：11×11 外加 1px 边框（总 13×13） */}\n          <div style={{ position: 'absolute', left: 0, top: 2, width: 11, height: 11, boxSizing: 'content-box', border: '1px solid #3a3d46', borderRadius: 2, font: `600 7px ${MONO}`, color: '#c9cdd6', textAlign: 'center', lineHeight: '11px' }}>x</div>\n          <div style={{ position: 'absolute', left: 17, top: 3, font: `600 7.5px ${MONO}`, color: '#c9cdd6', letterSpacing: 1.5 }}>@USERNAME</div>\n          <div style={flash(FT.social)} />\n        </div>\n      </div>\n\n      {/* ---- 扫描线（渐变拖尾 + 亮芯） ---- */}\n      <div\n        style={{\n          position: 'absolute',\n          left: 0,\n          top: 0,\n          width: '100%',\n          height: 40,\n          background: `linear-gradient(180deg,transparent,rgba(${A_RGB},.07) 55%,rgba(${A_RGB},.02) 96%,transparent)`,\n          transform: `translateY(${ly - 40}px)`,\n          opacity: lineOpacity,\n        }}\n      >\n        <div\n          style={{\n            position: 'absolute',\n            bottom: 0,\n            left: 0,\n            width: '100%',\n            height: 1.5,\n            background: 'rgba(238,244,255,.9)',\n            boxShadow: `0 0 7px ${ACCENT},0 0 18px rgba(${A_RGB},.35)`,\n          }}\n        />\n      </div>\n\n      {/* ---- 顶部状态行：BUILD 计数 → ASSEMBLY · COMPLETE ---- */}\n      <div\n        style={{\n          position: 'absolute',\n          left: '50%',\n          top: 14,\n          transform: 'translateX(-50%)',\n          font: `600 7px ${MONO}`,\n          letterSpacing: 2,\n          color: done >= 1 ? ACCENT : '#8d93a0',\n          opacity: seg(t, 0.02, 0.07),\n          zIndex: 60,\n        }}\n      >\n        {done >= 1 ? 'ASSEMBLY · COMPLETE' : `BUILD · 0${placed}/0${PLAN.length}`}\n      </div>\n    </DesignStage>\n  );\n};\n\n\n"
}