Synthetic UI Demos(シンセティックUIデモ)
画面収録は一切行われません。Claude Codeがプロダクトのリポジトリを読み取り、シーンに表示する画面のみを静的なReactとして再構築し、スクリプト化されたカーソルとカメラがRemotion上でフローを実行します。すべてのクリックはフレーム番号に紐づけられているため、シーンは常に同一にレンダリングされ、テストアカウントやライブ環境は不要で、プロダクトの変更時にはコードを編集するだけで更新できます。
参考ビルド(このスキルと一緒に参照してください。どちらもプロダクトのリポジトリなしでビルドできます):
examples/ds-free-schedule-demo:1つの26秒フロー(通話スケジュール)とナレータータイル。最もシンプルな完全な例です。src/Demo.tsxは、すべてのカーソルキーフレーム、クリック、カメラ移動を含む単一のタイムラインです。examples/ds-release-649:80秒のリリースノート動画。3つのアプリにわたる8つのシーンで構成されています。templates/ds-release-notesはその再利用可能な形状です(リリース650として提供)。src/Release.tsxがシーンを順序付け、src/scenes/には各リリース項目ごとに1ファイル、src/room/とsrc/ac/は画面キットです。
Playwrightとの使い分け
| Synthetic(このスキル) | Playwright録画(/record-demo) |
|---|---|
| アプリのソースコードを読める | 実行中のアプリしかない |
| リリースごとに作り直すリリースノートや機能ツアー | 実際の動作やデータの1回限りのキャプチャ |
| フローに手元にないデータが必要(完全な通話、他のユーザー、管理画面など) | 実際のものに簡単にアクセスでき、そのままで見栄えが良い |
| カメラ移動、正確なクリックタイミング、音声変更後の再レンダリングが必要 | 再構築コストが高い長く複雑なインタラクション |
ビルディングブロック(lib/components)
import { Stage, typed, cursorAt, near } from '../../../../lib/components';
<Stage
url="app.example.com/settings" // アドレスバー;ホストはパスより暗く
camera={CAMERA} // [{ f, z, x, y }] キャンバスピクセル単位、イージング付き、ウィンドウ内にクランプ
path={PATH} // [{ f, x, y }] カーソルキーフレーム、キャンバスピクセル単位
clicks={CLICKS} // カーソルを押しつぶし、波紋を発生させるフレーム
aiNote // "表示されている人物はAI生成です"(または独自テキストを渡す)
canvasWidth={1440} canvasHeight={810} // デフォルト値
>
<SettingsScreen field={typed(frame, 'Q4 Product Review', 128)} />
</Stage>
Stageは固定サイズのアプリキャンバスの周りにブラウザの枠を描画し、カメラを適用し、カーソルをzIndex 100で上部に配置します。ズーム1ではウィンドウはフレーム幅のfit(0.8625)になります。キーフレームは値を繰り返すことでその値を保持します。CursorとcursorAtは、ブラウザ以外のシーンで単独で機能します。near(cursor, target)はホバー状態を駆動します。- プロダクト固有の画面キット(テーマ、アイコン、レイアウト)はプロジェクト内に保持します。これらはライブラリの対象ではありません。
方法
1. サブエージェントを使用してアプリのリポジトリからビジュアル仕様を取得する
アプリまたは領域ごとに1つのサブエージェントを並列で送信し、各エージェントにファイルダンプではなくコンパクトな仕様を返させるようにします。
- テーマ: パレット、フォントスタック、角丸、スペーシング、シャドウ(テーマファイルまたは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. 音声を先に作成し、タイムラインをフレームで配置する
- シーンごとに1行書き、文で分割します(フレーズごとに1つの
.txt)。最初に音声を生成します(voiceover.py --scene-dir ...)。3テイクとtools/pick_takes.pyは、1テイクと--max-wpmよりも優れています。クローンは参照クリップのペースと平坦さをコピーするため、ラインが速すぎたり単調になったりした場合は、出力を引き伸ばすのではなく、tools/voice_prosody.py expandでクリップを修正します(CLAUDE.mdの「Clone Prosody」を参照)。測定された継続時間を読み取り、tools/captions.pyで単語のタイミングを取得します。 - 各シーンをそのラインとビートに合わせてサイズ設定し、各ラインの開始位置と長さをリストしたヘッダーコメントを保持します。そのコメントが信頼できる情報源です(CLAUDE.mdの「Audio-Anchored Timelines」を参照)。
- すべてはシーンファイルの先頭にあるフレーム定数です。カーソルキーフレーム、
CLICKS、カメラキーフレーム、UI状態の切り替え(frame >= C.saveでダイアログ表示)。クリックフレームをエクスポートして、親がクリック効果音を配置できるようにします。 - フレームはナレーションからルックアップし、手動で設定しないでください。
voice()(lib/components/cues.ts;ds-release-notesテンプレートにコピーがあります)は、シーンのフレーズとその.captions.jsonおよび開始フレームを受け取り、cue('save')はその単語が始まるフレームを返します。すべてのアクションをそれが示す単語にアンカーし、オフセットを付け、すべてのカーソルとカメラのキーフレームをそれらのアンカーにアンカーします:フレーズを再生成または再タイミングすると、クリック、タイピング、カーソル、カメラがそれに追随します。キュー内の単語がもう発声されない場合、その単語でバンドルに失敗します。オプション:import line06a from '../../public/audio/scenes/06a_link.captions.json'; export const LINK_VOICE = voice([{ file: '06a_link', at: 10, captions: line06a }, ...]); const { cue } = LINK_VOICE; const C = { field: cue('before', { offset: -3 }), save: cue('save') }; const PATH = [{ f: C.field - 6, ...field }, { f: C.save - 24, ...saveBtn }];offset、edge: 'end'、after(繰り返される単語のフレーム)、nth、in(1つのフレーズ)。親はLINK_VOICE.linesからオーディオを配置するため、フレーズの開始フレームは1回だけ記述されます。templates/ds-release-notes/src/scenes/FreeCustomLink.tsxに動作例があります。
4. 密集したリスト行をそのポーズで分割する
複数の項目をリストする行(「スケジューリング、連絡先、録音、文字起こし」)は、1つのショットには速すぎます。ポーズで音声をフレーズファイル(07a、07b、...)に分割し、各フレーズに独自のショットを与え、各フレーズを独自のビートに配置します。ds-release-649のmoreVoフィールドでこれを行っています。
5. フルレンダリング前にコンタクトシートからクリックターゲットを測定する
推測された座標は、カメラがズームインすると通常20~50ピクセルずれます。すべてのクリックフレームとその数フレーム前の静止画をレンダリングし、それらをタイル状に並べて、カーソルチップがターゲット上にあるか確認します。
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に調整します。qc.py --project ... --layoutは、動画自体のテキスト(キャプション、カード、AI注意書き)のサイズとコントラストをチェックし、プロダクトUIのテキストを個別にリストします。Stageはそのウィンドウにdata-qc="product"をマークします。外部に作成した電話やタブレットのモックアップにも同じ属性を付けてください。そうしないと、そのUIテキストが動画のテキストとして判断されます。
注意点
- モノクロSVGロゴは黒くレンダリングされます。
logo_mono.svgは通常、黒い塗りつぶしを持っています。暗い背景ではfilter: brightness(0) invert(1)を使用します。 - ローカルブランチが古い。 ローカルチェックアウトがどのブランチにあるかに関係なく、
origin/<default>を読み取ります。 rsync --exclude srcはnode_modules/*/srcも除外します。 プロジェクトやキットをコピーするときは、先頭にスラッシュを付けて転送ルートに除外をアンカーします:--exclude /src。- カメラはクランプされます。 キャンバス端付近のカメラ中心は、ウィンドウ端がフレームの外に表示されないように引き寄せられます。ズームが予期しない場所に移動した場合、それが原因です。さらに内側のポイントを狙ってください。
- メニューとダイアログはキャンバス内に配置する必要があります。 キャンバスはその子要素をクリップします。実際のアプリと同様に、メニューが内側に開くように配置します。
- ホバー状態はカーソルパスから取得します。 フレーム定数の2番目のセットではなく、
near(cursorAt(frame, PATH), target)を使用します。これにより、キーフレームを移動するとホバーも一緒に移動します。 - 音声ラインを変更すると、それ以降のすべてが移動します。 キュー付きシーンでは、クリック、カーソル、カメラは自動的に追随しますが、シーンの長さと後続のフレーズの
atは確認が必要です。手動設定のシーンでは、すべてのフレームを再測定します。いずれの場合も、ヘッダーコメントとシーンの長さを更新し、クリック静止画を再確認します。リップシンクするナレーターが合成された音声トラックによって駆動されている場合、それも再生成します。 - キットはデモより長持ちします。 再構築された画面キットは、他の動画をホストできます。
ds-release-notesのルームは、パロディ短編のブランドエディションのステージになり、タイルにアニメーション化された顔が表示され、新しい画面作業はありませんでした。キットを新しいプロジェクトにコピーし、その論理キャンバスをフレームにスケーリングします(.claude/skills/parody-short/formats.md)。