{
  "slug": "list-reveal",
  "title": "List reveal with drift",
  "tagline": "A 6-item vertical menu settles into place in sequence at a 0.09 stagger with a light overshoot on landing, while the whole list container drifts linearly upward throughout — two unrelated layers of motion.",
  "description": "A 6-item vertical menu settles into place in sequence at a 0.09 stagger with a light overshoot on landing, while the whole list container drifts linearly upwa",
  "category": "ui-entrance",
  "tags": [
    "shotcraft",
    "remotion",
    "ui-entrance"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 4.1,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/list-reveal/poster-c0d0c65ea398.jpg",
    "video": "https://gimgs.net/motion/media/list-reveal/video-65808a22d9de.mp4"
  },
  "variants": [
    {
      "key": "list-reveal",
      "label": "list-reveal · List reveal with drift",
      "description": "A 6-item vertical menu settles into place in sequence at a 0.09 stagger with a light outBack overshoot on landing, while the whole list container drifts linearly upward 32px throughout — item-by-item entrance and the overall drift are two unrelated layers of motion.",
      "use": "",
      "video": "https://gimgs.net/motion/media/list-reveal/video-65808a22d9de.mp4",
      "poster": "https://gimgs.net/motion/media/list-reveal/poster-c0d0c65ea398.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/list-reveal.html",
  "json_url": "https://gimgs.net/motion/list-reveal.json",
  "template_url": "https://gimgs.net/motion/list-reveal/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/list-reveal.md",
  "updated_at": "2026-09-24T07:40:05.414Z",
  "prompt": "## When to use\n\nEntrances for navigation/sidebars/settings panels; any UI segment that should feel \"self-assembling\", also works as a low-energy backdrop under narration.\n\n## Intention\n\nA list fading in item by item is one of the easiest motions to make feel dead — each item stops moving and the frame sits completely still between items. This card's solution is to add a slow drift on the container that's unrelated to the item-by-item entrance — locally items are \"finding their place\", globally the frame is \"breathing\". The audience reads every item clearly, while not a single frame from start to finish is pinned dead still.\n\n## Duration & energy\n\n- Duration: About 3.6s (108f@30fps)\n- Energy: Low (a steady pulse, no peak; kept alive by the drift)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n列表逐项淡入是最容易做得死板的一类动效：每项动完就停，画面在两项之间是完全静止的。\n这张卡的解法是给容器加一层与逐项入场无关的慢速漂移——局部在\"找位\"，整体在\"呼吸\"。\n观众读得清每一项，同时画面从头到尾没有一帧是钉死的。\n\n## 动效核心\n- 两层运动完全解耦：容器 `list.style.transform = translateY(lerp(t, 16, -16))` 只吃全局 t，\n  是贯穿全片的线性漂移；每项自己吃 `seg(...)` 的局部窗口，互不参与对方的计算\n- 逐项入场：`seg(t, 0.06+i*0.09, +0.24, E.outBack)`，stagger 0.09（约 10f）比常规\n  UI stagger 慢一档——这是\"读得完\"的间隔，不是\"快速铺开\"的间隔\n- 找位动作是 scale 主导：`scale(0.78 + p*0.22)` 配 `translateY(14→0)`，位移只有 14px，\n  观众感知到的是\"从小长到实\",而不是\"从下面飞上来\"\n- `E.outBack` 的过冲让 scale 短暂越过 1（约 1.002 量级的软过冲）——量很小，作用是把\n  落位的那一帧从\"停\"变成\"扣住\"\n- 透明度 `min(1, p*2.2)`：行程走到 45% 就已全实，避免半透明的列表项和背景卡片色\n  （`#161a26` + 1px `#262c40` 描边）糊在一起\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 项数 | 6（Dashboard…Sign out） | 末项窗口 0.51→0.75，尾部留 0.25 静置给观众收尾；>8 项必须压 stagger，否则末项吃到收尾 |\n| stagger | 0.09/项（≈10f） | 10f 是\"逐项读得完\"；压到 0.04 变成一次铺开（另一种手法），拉到 0.15 以上节拍会散 |\n| 单项行程 | 窗 0.24（≈26f） | 行程比 stagger 长 2.6 倍 → 相邻三项同时在动，队列是连绵的；两者相等会变成一项一项硬接 |\n| 起始 scale | 0.78 | 0.78 是\"已经能认出是个条目\"的尺度；<0.5 读作从零弹出，和 pop 类手法混淆 |\n| 起始位移 | 14px | 位移必须远小于行高（9px gap + 约 33px 项高），否则项与项之间穿插 |\n| 过冲 | `E.outBack` | 这里的过冲要小到几乎读不出来；换 spring 或加大 back 系数，6 项连续抖会显得廉价 |\n| 容器漂移 | `lerp(t, 16, -16)`，全程线性 | 总位移 32px 摊在 3.6s 上（约 0.3px/f）——刚好在\"感觉画面活着\"和\"看得出在动\"之间；>60px 会与逐项位移量级相当，两层运动混为一件事 |\n| 透明度斜率 | `min(1, p*2.2)` | 2.2 决定多早变实；降到 1 会看到半透明项叠在背景上，深色主题下尤其脏 |\n\n## 已知坑\n- 容器漂移是线性且贯穿全程，t=1 时停在 -16px 而不是 0——这个动效**末帧不回位**。\n  要循环播放或与后一镜对齐位置，得把漂移换成 `seg(t, 0, 1, ...)` 之外的收束曲线，\n  或在末段补一个反向缓收\n- 项数变化必须重算 stagger：末项起点 `0.06+(n-1)*0.09`，10 项时起点已到 0.87，行程会\n  被 t=1 截断，最后一项永远进不来\n- 文案没设 `white-space:nowrap`，容器宽度写死 240px——中文或更长的英文标签会换行，\n  行高一变整列节奏全乱\n- 漂移方向（向上）与逐项位移方向（14px→0，也是向上）同向。这是有意的（同向叠加更\n  顺），但两者量级必须保持 2 倍以上差距，否则读作\"一起往上飘\"而非两层\n- 图标是 hue 色环渐变方块的占位（6 个色相区分条目），落地要换成真实图标；条目底色\n  `#161a26` 属深色主题，浅色界面下描边和阴影都要重调\n\n## 参考实现\ndemos/ui-entrance/list-reveal/\n（ListReveal.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/ui-entrance/list-reveal/ListReveal.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/list-reveal/ListReveal.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/list-reveal/ListReveal.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/list-reveal/ListReveal.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/list-reveal/ListReveal.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": 123,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/list-reveal/ListReveal.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/list-reveal/ListReveal.tsx =====\n// list-reveal — List Reveal 菜单逐项找位（motion-lab 定稿转原生 Remotion）\n// 垂直菜单列表项依次 scale 找位入场（outBack 轻微过冲），同时整个列表容器\n// 全程线性缓慢上移——\"整体漂移\"与\"逐项入场\"两层运动分离叠加。\n// 设计坐标 480×270（DesignStage 等比放大），参数表数值以此坐标系标定。\nimport React from 'react';\nimport { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';\n\nexport const LIST_REVEAL_DURATION = 108; // 3600ms @30fps\n\nconst LABELS = ['Dashboard', 'Projects', 'Analytics', 'Messages', 'Settings', 'Sign out'];\nconst HUES = [225, 250, 275, 300, 210, 340];\n\nexport const ListReveal: React.FC = () => {\n  const t = useT();\n  return (\n    <DesignStage bg=\"#0a0b10\">\n      {/* 居中容器：flex 撑起列表垂直水平居中 */}\n      <div\n        style={{\n          position: 'absolute',\n          inset: 0,\n          background: '#0a0b10',\n          display: 'flex',\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}\n      >\n        {/* 整体漂移层：全程线性缓慢上移 */}\n        <div\n          style={{\n            position: 'relative',\n            width: 240,\n            display: 'flex',\n            flexDirection: 'column',\n            gap: 9,\n            // -0.5px：原样片浏览器 flex 居中落在整数位（列表高 255 → 顶部 7.0），\n            // Remotion 渲染落在 7.5，补回半个设计像素对齐原片\n            transform: `translateY(${lerp(t, 16, -16) - 0.5}px)`,\n          }}\n        >\n          {LABELS.map((s, i) => {\n            // 逐项入场：outBack 轻微过冲找位\n            const p = seg(t, 0.06 + i * 0.09, 0.06 + i * 0.09 + 0.24, E.outBack);\n            return (\n              <div\n                key={i}\n                style={{\n                  display: 'flex',\n                  alignItems: 'center',\n                  gap: 11,\n                  padding: '9px 13px',\n                  borderRadius: 10,\n                  background: '#161a26',\n                  border: '1px solid #262c40',\n                  opacity: Math.min(1, p * 2.2),\n                  transform: `scale(${0.78 + Math.max(0, p) * 0.22}) translateY(${lerp(Math.max(0, p), 14, 0)}px)`,\n                }}\n              >\n                <div\n                  style={{\n                    width: 15,\n                    height: 15,\n                    borderRadius: 5,\n                    flex: 'none',\n                    background: `linear-gradient(140deg,hsl(${HUES[i]},75%,64%),hsl(${HUES[i]},70%,46%))`,\n                  }}\n                />\n                <div\n                  style={{\n                    fontFamily: '-apple-system,sans-serif',\n                    fontWeight: 500,\n                    fontSize: 13,\n                    lineHeight: 1,\n                    color: '#c6cde2',\n                  }}\n                >\n                  {s}\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    </DesignStage>\n  );\n};\n\n\n"
}