关于 synthetic-ui-demo
Synthetic UI Demos 是一套用于制作产品演示视频的技能,面向能读取产品源代码并需要可重复演示的场景。它用静态 React 重建镜头中的界面,并以 Remotion 按帧控制光标、镜头和点击,无需录制屏幕或运行产品。
主要功能
- 01从产品源代码重建静态界面
- 02用 Remotion 按帧编排演示
- 03控制光标、点击和镜头移动
- 04按场景组织语音与时间线
- 05通过静帧联系表校准点击位置
适用场景
- 01制作每次发布都要更新的功能演示
- 02呈现无法实际访问的数据或界面
- 03为演示精确安排点击和镜头
- 04制作跨多个应用的发布说明视频
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ digitalsamba/ claude-code-video-toolkit --skill synthetic-ui-demo安装后在 Claude Code 中输入/synthetic-ui-demo确认已加载。
详细介绍
合成 UI 演示
不会录制屏幕。Claude Code 会读取产品代码仓库,只用静态 React 重建场景中展示的屏幕,并在 Remotion 中通过脚本控制的光标和摄像机演绎整个流程。每次点击都对应一个帧编号,因此场景每次渲染的结果都完全一致,不需要预置账号或实时环境;产品变更时,只需编辑代码即可更新。
参考构建(请结合本技能阅读;两者都不依赖产品代码仓库即可构建):
examples/ds-free-schedule-demo:一个 26 秒的流程(安排通话),带有旁白图块。最简单的完整示例。其src/Demo.tsx是一条包含所有光标关键帧、点击和摄像机移动的时间线。examples/ds-release-649:一段 80 秒的版本说明视频,涵盖三个应用中的八个场景。src/Release.tsx用于排列场景,src/scenes/中每个版本更新项各有一个文件,src/room/和src/ac/则是屏幕组件套件。
何时使用此方法,而不是 Playwright
| 合成演示(本技能) | Playwright 录制(/record-demo) |
|---|---|
| 可以读取应用源码 | 只能使用正在运行的应用 |
| 每次发布都要重新制作的版本说明和功能导览 | 一次性的真实行为或真实数据录制 |
| 流程需要你没有的数据(完整通话、其他用户、管理员视图) | 实际操作很容易完成,而且现有外观就符合要求 |
| 摄像机移动、精确的点击时机、语音变更后的重新渲染 | 重建成本很高的长流程复杂交互 |
构建模块(lib/components)
import { Stage, typed, cursorAt, near } from '../../../../lib/components';
<Stage
url="app.example.com/settings" // address bar; host darker than path
camera={CAMERA} // [{ f, z, x, y }] in canvas px, eased, clamped to the window
path={PATH} // [{ f, x, y }] cursor keyframes in canvas px
clicks={CLICKS} // frames that squash the cursor and throw a ripple
aiNote // "People shown are AI-generated" (or pass your own text)
canvasWidth={1440} canvasHeight={810} // the defaults
>
<SettingsScreen field={typed(frame, 'Q4 Product Review', 128)} />
</Stage>
Stage会在固定尺寸的应用画布周围绘制浏览器边框,应用摄像机效果,并将光标置于最上层,zIndex 为 100。缩放为 1 时,窗口宽度占画面宽度的fit(0.8625)。关键帧重复某个值即可保持该值。Cursor和cursorAt可独立用于非浏览器场景;near(cursor, target)用于驱动悬停状态。- 产品专属的屏幕组件套件(主题、图标、布局)应保留在项目中,不属于库的内容。
方法
1. 使用子 Agent 从应用代码仓库提取视觉规格
每个应用或区域并行分配一个子 Agent,让每个子 Agent 返回精简规格,而不是大段文件内容:
- **主题:**调色板、字体栈、圆角、间距、阴影(从主题文件或 CSS 变量中获取)。
- **文案:**场景中每个标签、菜单项、对话框和提示的准确英文语言字符串(
public/locales/en/*.json、src/i18n/locales/en/*)。切勿改写 UI 文本。 - **图标:**如果应用使用图标字体,其 icomoon/fontello
selection.json中包含每个字形的 SVG 路径。提取所需图标到一个小型 JSON 文件中,并将其渲染为<svg><path d=...>。否则,直接复制 SVG 组件。 - **布局和行为:**展示屏幕的组件树、工具栏顺序、默认值,以及每个控件的作用。
从远程默认分支读取内容(git show origin/main:path,或先运行 git fetch,再读取 origin/<default>)。
本地检出通常已经过时,显示的会是上一季度的 UI。
对于版本说明,追踪每个更新项对应的提交和最初发布该功能的组件。这样就能获得准确的字符串和要展示的默认值。
2. 在固定画布上只重建场景展示的内容
- 将每个元素以绝对定位放置在固定画布上(1440x810 是不错的默认尺寸)。将重要的矩形区域导出为常量(
SAVE_BTN = { x, y, w, h }),让点击目标通过计算得出,而不是凭猜测。 - 只构建摄像机画面中的内容。永远不会入镜的面板可以用纯色块代替。
- 使用静态 React:不使用状态,也不使用副作用。每处视觉变化都由
frame决定。 - 将组件套件放在独立文件夹中(
src/ui/、src/room/),并用theme.ts存放常量。
3. 先录制语音,再按帧安排时间线
- 每个场景写一句台词,并先生成各场景的音频(
voiceover.py --scene-dir ... --json,添加--max-wpm 165)。读取测得的时长。 - 每个场景的时长设为台词长度再加一拍,并保留一条标题注释,列出每句台词的开始时间和时长。该 注释是唯一可信来源(参见 CLAUDE.md 中的“音频锚定时间线”)。
- 场景文件顶部的所有内容都设为帧常量:光标关键帧、
CLICKS、摄像机关键帧、UI 状态切换(frame >= C.save时显示对话框)。导出点击帧,让父级可以放置点击音效。 - 每次点击都要与提到该对象的词同步。
4. 在密集列举台词的停顿处分句
一条列出多个事项的台词(“日程安排、联系人、录音和转录文本”)太快,无法用单个镜头呈现。
在停顿处分割音频,生成短语文件(07a、07b 等),为每个短语安排自己的镜头,并将每个
短语放在各自的节拍上。ds-release-649 的 moreVo 字段就是这样处理的。
5. 完整渲染前,先用联系表测量点击目标
摄像机放大后,猜测的坐标通常会偏差 20-50px。在每个点击帧及其前几帧渲染静帧,将它们拼成图块,并检查光标尖端是否落在目标上:
i=1; for f in 46 78 122 212; do npx remotion still MyComp out/stills/s$i.png --frame=$f; i=$((i+1)); done
ffmpeg -start_number 1 -i out/stills/s%d.png -vf "scale=640:-1,tile=2x2" -frames:v 1 out/contact.png
修正常量,重新渲染静帧,然后再渲染视频。
6. 为画面中的 AI 生成角色添加标注
任何非真人角色(图块中的 FLUX.2 肖像、虚拟形象、旁白)都要添加可见标注:在 Stage 上使用
aiNote,或在旁白图块上添加“AI”徽章。姓名、联系人和通话数据也都是虚构的,因此要明确标示为虚构内容。
7. 渲染,然后标准化音量
渲染前运行 tools/qc.py --project ...,然后运行 qc.py --render out/video.mp4 --fix,以达到 -14 LUFS。
注意事项
- 单色 SVG 标志渲染为黑色。
logo_mono.svg通常使用黑色填充;在深色背景上使用filter: brightness(0) invert(1)。 - 本地分支可能过时。 读取
origin/<default>,而不是本地检出所在的分支。 rsync --exclude src也会排除node_modules/*/src。 复制项目或工具包时,在传输根目录前加斜杠来限定排除路径:--exclude /src。- 摄像机位置会被限制。 如果摄像机中心靠近画布边缘,就会被往内拉,避免窗口边缘出现在画面外。如果缩放位置出乎意料,原因就在这里;应瞄准更靠内的位置。
- 菜单和对话框必须放在画布内。 画布会裁剪其子元素。菜单的位置应让它向内展开,与真实应用的行为一致。
- 悬停状态由光标路径决定。 使用
near(cursorAt(frame, PATH), target),而不是另一组帧常量,这样移动关键帧时,悬停状态也会随之移动。 - 更改语音台词会让后续所有内容移位。 重新测量,更新标题注释和场景时长,然后重新检查点击静帧。如果口型同步的旁白由合成后的语音轨驱动,也要重新生成。