talking-head-recut について
Talking Head Recutは、既存のトーク動画にデザインされたグラフィックカードを重ね、MP4にレンダリングするスキルです。インタビューや解説、ソーシャル動画の編集向けで、ローカルのWhisperによる文字起こしに合わせてカードを配置し、元動画はそのまま再生します。
主な機能
- 01動画のメタデータと音声の抽出
- 02ローカルWhisperによる単語単位の文字起こし
- 03文字起こしに基づくカード内容とタイミングの計画
- 04カードHTMLの作成とコンポジションの組み立て
- 05MP4へのレンダリング
活用シーン
- 01トーク動画にタイトルやローワーサードを追加
- 02発話に合わせてデータや引用カードを表示
- 03サイドパネルやピクチャーインピクチャーを重ねる
- 04既存映像をソーシャル動画向けに仕上げる
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ heygen-com/ hyperframes --skill talking-head-recutインストール後、Claude Code で/talking-head-recutを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
media-contract.test.mjsNOTICE.mdassets/vendor/gsap.min.jsreferences/DESIGN_INDEX.mdreferences/frames/clean.htmlhairline.htmlpolaroid.htmlreferences/layouts/overlay.htmlpip.htmlsplit.htmlstack.htmlreferences/styles/academic.htmlaudit.htmleditorial.htmlgeom.htmlminimal.htmlspotlight.htmlswiss.htmlterminal.htmlwhiteboard.htmlxhs.html詳細
プラグインのインストール: このスキルが HyperFrames プラグイン内にある場合、セットアップまたは最新状態の確認コマンドを実行する前に、プラグインの実行ルールに従ってください。スタンドアロンでインストールした場合は、以下の更新手順を使用してください。
まず、このスキルを最新の状態に保ってください。実行前にユーザーに確認します:
npx hyperframes skills update talking-head-recut。すべてが最新ならすぐに何もせず終了します。そうでない場合は、このスキルと依存するコアドメインスキルを更新してから使用します。
Talking Head Recut
Talking Head Recut は、全編を通して再生されるローカル動画に、タイミングを合わせてデザインされた一連のグラフィックカード(タイトル、ローワーサード、データコールアウト、引用、サイドパネル、ピクチャーインピクチャー)を重ねます。エージェントがカードをデザインし(タイミングと内容)、各カードの HTML を会話内に直接記述してから、1 つのコンポジション HTML にまとめ、hyperframes 経由で MP4 にレンダリングします。固定のアーキタイプ一覧や規定のカード構造はなく、トランスクリプトの内容に応じてオーバーレイを作成します。
最初の窓口は
/hyperframesです。 このスキルは、既存のトーキングヘッド動画に、デザインされたグラフィックカード(タイトル、ローワーサード、データコールアウト、引用、サイドパネル、PiP)を組み合わせます。プレーンなキャプション(話した言葉をテキストにしたもの)ではありません。動画クリップは手を加えずに再生されます。 その他の意図(プレーンな字幕、単独のグラフィック、ゼロからの動画制作)または不確かな場合は、まず/hyperframesを読んでください。意図レイヤーがすべてのルート選択を担います。
embedded-captionsと同系列のグラフィック合成スキルです。 キャプションは_話した言葉_を読みやすい字幕として追加しますが、このスキルは再生中の動画に_デザインされたグラフィック_を重ねます。 プレーンな字幕にはembedded-captionsを使用してください。動画をゼロから作成する場合は、動画制作ワークフロー(product-launch-video/faceless-explainer/ …)を使用してください。
/hyperframes を経由する場合、意図レイヤーで確認するのは入力(どのクリップか)のみです。レンダリング戦略に関する質問は、確認事項として案内されます。アスペクト比、レイアウト、スタイルグループ、カード数は Step 7 まで決めません。そこで調査した映像とトランスクリプトをもとに推奨内容を提示します。意図レイヤーの実行形式に関する質問は該当しません。BRIEF.md がある場合は、確認済みの入力とユーザーのメモが記載されているため、最初に読みます。
作業ディレクトリ内にある、内容を確認できる中間ファイル:
metadata.json— 再生時間/幅/高さ/fpsaudio.mp3— 抽出した音声transcript.json— フラットな単語配列[{ text, start, end }, …](Whisper。segmentsもwordsラッパーもありません)storyboard.json— 簡易的なカード概要(エージェントの計画)public/cards/card-XX.html— カードごとの HTML フラグメントpublic/index.html— 組み立てた最終コンポジションoutput.mp4— レンダリング済み動画
CLI の解決
# hyperframes — transcription (local Whisper) + rendering the assembled HTML to MP4
npx hyperframes --help
このスキルはすべて hyperframes CLI と、システムの ffmpeg / ffprobe で動作します。
文字起こしには、ローカルの Whisper を hyperframes transcribe 経由で使用します。サードパーティサービス、API キー、またはレート制限付きプロキシは不要です。
ワークフロー
1. 環境を確認する
npx hyperframes doctor # ffmpeg, headless browser, render deps
# confirm bundled assets:
ls "<SKILL_DIR>/assets/fonts" "<SKILL_DIR>/assets/vendor/gsap.min.js"
必要なもの:
ffmpeg/ffprobe(システム)<SKILL_DIR>/assets/fonts/*.woff2、<SKILL_DIR>/assets/vendor/gsap.min.js(このスキルに同梱。Step 9 で作業ディレクトリに配置)
文字起こしにキーは不要です。hyperframes transcribe で Whisper をローカル実行します(Step 4)。
hyperframes render を実行する場合、macOS では以下を強く推奨します:
export PRODUCER_BROWSER_GPU_MODE=hardware
2. 作業ディレクトリを作成する
すべての成果物は videos/<project-name>/ に保存します。これは、他の動画ワークフロー(product-launch-video / faceless-explainer / pr-to-video)と同じ規則です。カレントディレクトリはワークスペースのルートに保ち、以下のすべてをこのサブディレクトリに書き込みます。
VIDEO_PATH="/absolute/path/input.mp4"
WORK_DIR="videos/$(basename "$VIDEO_PATH" | sed 's/\.[^.]*$//')"
mkdir -p "$WORK_DIR"
3. 音声とメタデータを抽出する
# metadata — duration / width / height / fps
ffprobe -v error -select_streams v:0 \
-show_entries stream=width,height,r_frame_rate \
-show_entries format=duration -of json "$VIDEO_PATH" > "$WORK_DIR/metadata.json"
# audio
ffmpeg -y -i "$VIDEO_PATH" -vn -acodec libmp3lame -q:a 2 "$WORK_DIR/audio.mp3"
出力:metadata.json(width/height/duration を読み取ります。fps は r_frame_rate の分数を評価した値です。例:30000/1001 → 29.97)と audio.mp3。
4. 文字起こし
npx hyperframes transcribe "$WORK_DIR/audio.mp3" -d "$WORK_DIR" --json --model small.en
ローカルの Whisper を使用します。API キーも、プロキシも、レート制限もありません。作業ディレクトリに、単語単位の
transcript.json を書き込みます(単語の text と start / end のタイムスタンプ)。
Step 6 のカードのタイミングを決める単語/文のタイミングを確認するために読み込みます。セグメント単位の
チャンクが必要な場合は、句読点やポーズを基準に自分で単語を文にまとめてください。
メディアの長さに合わせて制限します。 Whisper が最後の単語の end を実際のクリップの長さよりわずかに後の値で返すことがあります。各カードの endSec と composition.durationSeconds を
metadata.json の長さに合わせて制限してください。そうしないと、動画の後に黒い映像が表示されます。
5. 文字起こしを修正する
transcript.json は単語オブジェクトのフラットな配列です — [{ "text": "...", "start": s, "end": s }, …](segments 配列も、words ラッパーもありません。単語ごとのキーは text です)。読み込んで、明らかな ASR の誤りを修正します。
- 同音異義語、製品名、専門用語、句読点
- 単語の
textをその場で編集し、start/endのタイムスタンプは保持します - あらかじめグループ化された
segments配列はありません。カードのタイミングにセグメント単位のチャンクが必要な場合は、終止符やポーズで区切って、自分で単語を文にまとめてください
6. 軽量なストーリーボードを作成する(チャット内で)
CLI は使用しません。 transcript.json と metadata.json を読み込み、カードを直接デザインします。storyboard.json はエージェント内部の計画用アーティファクトです
— これを使用する CLI コマンドはありません。各カードの HTML を作成する前に、タイミングと内容を明確に考えられるように用意されています。同じアウトラインを使って Step 9 で作成するコンポジションを組み立てられるように、以下の例に沿った構成にしてください。
{
"schemaVersion": 3,
"composition": {
"fps": 30,
"width": 1080,
"height": 1920,
"durationSeconds": 121.2,
"layout": "portrait",
"themeId": "noir",
"seed": 42
},
"videoTrack": {
"sourcePath": "input-video.mp4",
"startSec": 0,
"endSec": 121.2,
"bounds": { "x": 0, "y": 0, "width": 1080, "height": 1920 }
},
"subtitles": { "enabled": false },
"cards": [
{
"id": "card-01",
"intent": "Hook with the speaker's anxious midnight question",
"startSec": 0.5,
"endSec": 13.0,
"accentIndex": 0,
"zone": "fullscreen",
"contentHints": {
"kicker": "AN HONEST QUESTION",
"title": "The soul-searching question at 11 PM",
"detail": "Client's 60-second voice message: 'If the RMB appreciates, does that mean my USD policy is a terrible loss?'"
}
}
]
}
必須のカードフィールド:
| フィールド | 型 | 目的 |
|---|---|---|
id | string | カードの HTML と GSAP セレクターで使用する固定 ID |
intent | string | 自然言語による説明。カードの生成に使用されます |
startSec / endSec | number | 秒単位の時間(endSec > startSec) |
accentIndex | 0 | 1 | 2 | 3 | 4 | このカードで使用する 5 色のテーマアクセントカラーの番号 |
zone | enum(下記参照) | キャンバス上でカードを配置する場所 |
contentHints | object | 自由形式の情報を入れるオブジェクト。エージェントがここに kicker/title/detail/data/quote を設定します |
archetype(省略可) | string | カードのパターンを覚えておくために付けられる自由形式のラベル。省略時はデフォルトの自由形式です |
transition(省略可) | enum:cut | fade | slide | wipe | カード間のトランジションを宣言します |
5 つの zone の値:
| ゾーン | 解決後の境界 | 使用する場面 |
|---|---|---|
fullscreen | キャンバス全体を覆います | 印象的なシーン、大きな数字、マントラ |
whiteboard-area | 40px の余白を設けます(または縦長の場合は高さの 45%) | 情報量の多いデータ/注釈付きコンテンツ |
lower-third | 下部 30% の帯 | 表示中の動画に重ねる注釈 |
side-panel | 右側 42%(横長)または下部 40%(縦長) | 片側にデータ、もう片側に動画 |
video-overlay | キャンバス全体。ほぼ透明なカードを想定 | 全画面動画に重ねる注釈 |
ステップ 9 でコンポジションを組み立てるときは、上の表に従って、各カードの
zone をカードホストのラッパー上のピクセル境界に変換してください。
動画の境界はコンポジションレベル(videoTrack.bounds)で一度だけ設定します。
動画が「カード間を移動する」ように見せるには、コンポジションの <script> 内で
#video-wrap に対して GSAP のトゥイーンを記述してください(ステップ 9 を参照)。
**カードの役割や、定められた物語の流れはありません。**カードは動画で実際に語られている内容から生まれます。すべて引用でも、すべてデータでもかまいません。数字から始めても、物語から始めてもかまいません。文字起こしの内容に合わせてリズムを作ってください。
要点はいくつ必要ですか? — 長さと情報密度から自動推定します。固定の上限はありません。まず動画の長さから基本ペースを選び、次に情報密度に応じて調整します。固定されているのは最低数のみで、5 枚です。短い動画でもリズムが生まれるようにしてください。
ステップ 1 — 動画の長さに応じた基本ペース(中程度の密度における、1 カードあたりの自然な秒数):
| 動画の長さ | 基本ペース(1 カードあたりの秒数) | 理由 |
|---|---|---|
| 60 秒未満(短いリール) | 6~8 秒 | 短尺動画では、視聴者は速いカットを期待します |
| 60 秒~3 分 | 8~12 秒 | 一般的なソーシャル動画のペース |
| 3~10 分 | 12~20 秒 | 余裕を持たせ、各カードに多くの内容を含めます |
| 10~30 分 | 20~35 秒 | 長尺の講義/インタビューのリズム |
| 30 分超 | 30~60 秒 | エピソード形式で、章に近い感覚 |
ステップ 2 — 密度の乗数(基本ペースに掛けます):
| 文字起こしに見られる特徴 | 乗数 | 効果 |
|---|---|---|
| 高密度 — 数字や個別の主張が多い、テンポが速い、列挙形式、1~2 文ごとに新しいアイデアが出てくる | × 0.7 | カットを速くし、カード数を増やします |
| 中密度 — データと物語が混在する流れ | × 1.0 | 基本ペース |
| 低密度 — 長い物語が一つ続く、同じ内容を繰り返し捉え直す、ゆっくり内省的に進む、一つの議論が展開していく | × 1.5 | カットを遅くし、カード数を減らします |
ステップ 3 — 計算:
secPerCard = basePace × densityMultiplier
cardCount = max(5, round(videoDurationSec / secPerCard))
例(上限はありません。動画が長ければ、カード数も自然に増えます):
- 30 秒のリール、オチが一つ(低密度) → 7 × 1.5 = 1 カードあたり 10.5 秒 → round(30/10.5)=3 → 最低数の 5 枚にします
- 60 秒の内省的な独白(低密度) → 10 × 1.5 = 1 カードあたり 15 秒 → 4 枚 → 最低数の 5 枚にします
- 121 秒のデータ豊富なトーク動画(高密度) → 10 × 0.7 = 1 カードあたり 7 秒 → 17 枚
- 5 分のインタビュー、中程度の密度 → 16 × 1.0 = 1 カードあたり 16 秒 → 19 枚
- 10 分の詳細解説、高密度 → 16 × 0.7 = 1 カードあたり 11 秒 → 55 枚
- 30 分の講義、中密度 → 28 × 1.0 = 1 カードあたり 28 秒 → 64 枚
- 1 時間のポッドキャスト、低密度 → 45 × 1.5 = 1 カードあたり 67.5 秒 → 53 枚
カードの表示時間が約15秒を超える場合は、より豊かなカード(データブロック、
複数段階の表示、いくつかのサブポイントを段階的な
アニメーションで展開するなど)を検討します。静的な一文だけでは8秒を過ぎると退屈になります。30秒を超えるカードが多い長尺コンテンツでは、タイムラインを複数のサブコンポジションに分割(章ごとに1つの .html を用意し、
data-composition-srcでマウント)することを検討してください。ファイルごとのGSAPタイムラインを管理しやすくできます。timeline_track_too_denseHyperFrames lint 警告を参照してください。
contentHints は自由形式のオブジェクトで、キーやネスト構造はデータを表現できる形なら自由に指定できます。カードごとにエージェントが構造を決定します。
任意のアウトロ。 このスキルには固定のブランドアウトロは含まれていません。ユーザーがクロージングカードを希望する場合は、中立的なデザイン(ワードマーク+1行のタグライン、約1.5~2秒、フェードイン → 短い表示 → フェードアウト)を自分で作成し、cards[] に追加して、composition.durationSeconds をその endSec まで延長します。それ以外の場合は、最後のコンテンツカードで終了します。
7. レンダリング戦略を決める
ユーザーにビジュアルの方向性を確認する(最初に行うこと)
カードのデザインや境界を決める前に、出力比率、レイアウト、スタイル、カード密度のプリセットをユーザーに選んでもらいます。フレームは、選択したレイアウトとスタイルの組み合わせに基づいて自動選択されます(下記の「フレームの自動選択」表を参照)。質問を送る前に、次の2つを事前に計算します。
-
ソース動画のアスペクト比(
metadata.jsonの幅 ÷ 高さ)からrecommendedRatioを算出します。sourceAspect = width / heightsourceAspect ≥ 1.5(横長の約3:2以上)→16:9を推奨sourceAspect ≤ 0.7(縦長の約9:13以下)→9:16を推奨0.7 < sourceAspect < 1.5(ほぼ正方形)→4:5を推奨
推奨する選択肢のラベルに「 (recommended · matches source video X:Y)」を付け、推奨理由がわかるようにします。
-
ステップ6の
autoCount(max(5, round(videoSec / (basePace × densityMultiplier))))を算出し、「auto」オプションのラベルに具体的な数を表示できるようにします。
環境との互換性 — 利用可能な質問チャネルから最適なものを選択します。 ランタイムによって、同じ構造化質問ツールが使えるとは限りません。以下の順に適用します。
- ネイティブの確認ツール — 下記の構造化された4つの質問を呼び出します。
- その他のネイティブ確認ツール(例:
ask_question、request_user_input、IDE固有のプロンプト)— 同じ4つの質問文と選択肢を使って、そのツールを利用します。推奨マーカーと事前計算した値を保持します。 - ネイティブツールがない場合(Codex CLI、テキストのみのランタイム)— 通常の会話で直接質問します。このセクション末尾のプレーンテキスト用テンプレートを使います。1つのメッセージに4つの番号付き質問をまとめます(1ラウンドあたり2~5問という全体上限を超えません)。
すべてのチャネルに適用されるルール:
- 1ラウンドで質問するのは最大2~5問です。ここでは4問なので範囲内です。
- 情報が不足していてもレンダリングを妨げない場合でも、最終出力に大きく影響するパラメーター(比率、レイアウト、スタイル、cardCount)を確認するため、一度は質問します。
- ユーザーがすでにデフォルトを承認している(「デフォルトを使って」「質問しなくていい」「すべて自動選択して」など)、質問しないよう依頼している、または実行中に自律的に判断する指示(「お任せ」「決めて」—
../hyperframes/references/brief-contract.md§ 1)がある場合は、質問を完全に省略し、次を使用します:recommendedRatio、layout="stack"(さまざまな比率で最も安全なデフォルト)、トランスクリプトのトーンに基づき最も中立的なグループ(エディトリアル/データ)から選んだstyle、autoCount。選択内容を1文でユーザーに伝えてから続行します。
チャネルA — ネイティブの AskUserQuestion:
// Precompute before the call:
// recommendedRatio = "16:9" | "9:16" | "4:5"
// autoCount = integer (from Step 6)
AskUserQuestion({
questions: [
{
question: "Output video aspect ratio (canvas):",
header: "Aspect ratio",
multiSelect: false,
// Reorder so the recommended option appears FIRST (per AskUserQuestion convention).
// Append " (recommended · matches source video W×H)" to the recommended option's label.
options: [
{ label: "16:9 (1920×1080) landscape", description: "TV / YouTube / desktop playback. Most natural when the source video is already landscape; widest canvas." },
{ label: "9:16 (1080×1920) portrait", description: "TikTok / Reels / short-form mobile. Most natural for portrait source; native mobile experience." },
{ label: "4:5 (1080×1350) near-portrait", description: "Instagram feed / WeChat Moments. Best when source is near-square or you want to cover both platforms." }
]
},
{
question: "Choose the overall layout: how should the video and cards coexist on the canvas?",
header: "Layout",
multiSelect: false,
options: [
{ label: "side-by-side (split)", description: "Video and card each take half the canvas. Most stable for interview / data side-by-side; clear visual separation." },
{ label: "top-bottom (stack)", description: "Video on top (~52%), card below. Classic combo of speaker face + summary card; works well in portrait too." },
{ label: "picture-in-picture (pip)", description: "Card fills the canvas, video shrinks to a rounded corner window. Use when content is primary and speaker is secondary." },
{ label: "full-screen overlay (overlay)", description: "Video plays full-bleed, card floats as a glass layer on top. Strong cinematic / emotional feel." }
]
},
{
question: "Choose the card visual style (style):",
header: "Style group",
multiSelect: false,
// NOTE: these 3 groups intentionally match the frame auto-pick matrix
// rows below, so picking a group resolves both `style` group AND the
// frame matrix column in one step. Memberships are mutually exclusive.
options: [
{ label: "warm paper (warm-paper)", description: "academic notebook · editorial big-type · whiteboard hand-drawn · xhs social. Best for interview reflections, product launches, lifestyle, emotional stories." },
{ label: "clinical / cold (clinical)", description: "audit magazine · swiss grid · terminal CLI · minimal modern. Best for financial analysis, investigative reports, technical tutorials, serious presentations." },
{ label: "experimental / avant-garde (experimental)", description: "geom color-clash geometry · spotlight dark-background. Best for short-form highlights, product launches, strong emotion, cinematic feel." }
]
},
{
question: "Card count (takeaway pacing): how many cards to cut?",
header: "Card count",
multiSelect: false,
options: [
{ label: "Auto (recommended) · approx N cards", description: "Inferred automatically from video duration and information density (see Step 6 rules). This run estimates approx N cards. Substitute the real N (your autoCount) into the label." },
{ label: "Fewer · approx round(N × 0.6) cards", description: "Sparser cuts, each card holds longer — suits reflective / slow-paced content." },
{ label: "More · approx round(N × 1.5) cards", description: "Tighter cuts, faster rhythm — suits staccato / data-dense / short-form highlight content." }
]
}
]
})
「その他」について — AskUserQuestion はカード数の質問に「その他」の選択肢を自動で追加します。ユーザーはカード数の目標として、数値を直接入力できます(例:「8」「20」)。入力を整数として解析します。解析に成功した場合はその値を使用します(下限は5)。解析に失敗した場合は「auto」に戻します。
チャネル B — プレーンテキストのフォールバック(Codex CLI、ネイティブの質問ツールがないランタイム)。これを通常のメッセージとして 1 通投稿し、返信を待ちます。箇条書き形式の 1/2/3/4 にすると、返信を解析しやすくなります:
I need to confirm four visual decisions with you before I start cutting cards:
1) Output aspect ratio (canvas):
A. 16:9 landscape (1920×1080) — TV / YouTube / desktop playback
B. 9:16 portrait (1080×1920) — TikTok / Reels / short-form mobile
C. 4:5 near-portrait (1080×1350) — Instagram feed / works for both platforms
▸ My recommendation: <recommendedRatio> (matches source video W×H = <sourceW>×<sourceH>)
2) Overall layout (how video & card coexist):
A. split side-by-side (50/50)
B. stack top-bottom (video top, card bottom)
C. pip picture-in-picture (card full canvas, video rounded corner window)
D. overlay full-screen glass overlay (video full-bleed, card glass layer)
3) Card style group (maps to frame auto-pick matrix, pick 1 of 3):
A. warm paper (warm-paper) (academic / editorial / whiteboard / xhs)
B. clinical / cold (clinical) (audit / swiss / terminal / minimal)
C. experimental (experimental) (geom / spotlight)
4) Card count (takeaway pacing):
A. Auto (recommended) — approx <autoCount> cards
B. Fewer — approx round(<autoCount> × 0.6) cards
C. More — approx round(<autoCount> × 1.5) cards
D. Give me a specific number (e.g. "8", "20")
Reply format: "1A 2C 3B 4A" or natural language is fine.
If you want all recommended defaults, reply "default" / "auto" / "use all recommendations".
プレーンテキストの返信を解析する:
- 緩い形式を許容します:
"1A 2C 3B 4A"、"A C B A"、"16:9 / pip / data / auto"、完全な文、またはdefault。 - いずれかの回答が曖昧な場合 → 曖昧なものだけを再度質問します(それでも 2〜5 回の上限内で)。
- ユーザーが「default / auto / use all recommendations」と言った場合 → 再質問せずにスキップします。
ユーザーが回答した後(どのチャネルでも):
-
ratio の回答から出力キャンバスを解決します — これらは書き込むべき正確な
storyboard.composition.width / heightの値です:ユーザーの選択 composition.width × height storyboard.layout フィールド 16:91920 × 1080 "landscape"9:161080 × 1920 "portrait"4:51080 × 1350 "portrait"(スキーマは 4:5 を portrait として扱います — height > width)references/layouts/*.html内の 4:5 の bounds について — これらのファイルは landscape(1920×1080)と portrait(1080×1920)のみを記述しています。4:5(1080×1350)の場合、portrait からの比例スケーリングによって bounds を導出します:水平方向の値はそのままにし、垂直方向の値を1350/1920 ≈ 0.703でスケーリングします。例:overlayの portrait カード ={ x: 24, y: 1280, w: 1032, h: 564 }→ 4:5 カード ={ x: 24, y: round(1280 × 0.703), w: 1032, h: round(564 × 0.703) }={ x: 24, y: 900, w: 1032, h: 397 }。 -
トランスクリプトのトーンを見て、スタイルグループを特定のスタイルにマッピングします — 最も適合するものを選びますが、ユーザーが選択したグループ内にとどまります。グループ内の 2 つの特定のスタイルで迷った場合は、それら 2〜4 個の特定のスタイルオプションを指定して 2 回目の
AskUserQuestionを送信します。 -
density の回答から最終的な cardCount を解決します:
ユーザーの選択 最終的な cardCount Auto(推奨) すでに計算した autoCountFewer max(5, round(autoCount × 0.6))More round(autoCount × 1.5)(上限クランプなし)Other = ""(整数) max(5, parseInt(n))Other = それ以外 autoCountにフォールバック -
この表からビデオフレームを自動選択します(フレームはユーザーに質問しません — layout × style から導かれます):
layout warm-paper スタイル(academic / whiteboard / editorial / xhs) clinical スタイル(audit / swiss / terminal / minimal) experimental スタイル(geom / spotlight) splitpolaroidhairlinecleanstackpolaroidhairlinecleanpipclean(pip ピルにはすでに chrome がある)cleancleanoverlayclean(フルブリードでは装飾フレームは禁止)cleanclean -
選択した内容をユーザーに伝えてください。比率(+キャンバスサイズ)、レイアウト、具体的なスタイル、フレーム、最終的な cardCount を1文で伝えたら、Step 7 の残り(カードごとのレイアウト、モーションパターン)に進みます。
-
5つの値(比率 / レイアウト / スタイル / フレーム / cardCount)を作業メモリに記録してください(スキーマのフィールドは不要です)。Step 8 で各カードの HTML を作成するときと、トークンや構造を確認するために対応する
references/<dim>/<key>.htmlを読むときに参照します。
ユーザーが「Other」を選び、10 種類のスタイルライブラリにないスタイル名を自由入力した場合は、新しいカードのビジュアルを自分でデザインするためのヒントとして扱います。ただし、選択したレイアウトの制約には従ってください。
レンダリング戦略の入力
Step 7.0 で比率 / レイアウト / スタイル / cardCount / フレームを確定したら、残るカードごとの判断は次のとおりです。
- GSAP のターゲット内におけるソース動画のフィット方法:動画要素には
object-fit: coverが設定され、#video-wrapのトゥイーン範囲でクリップされます。クロップを一切行いたくない場合(たとえば、横長のキャンバスに縦長のソース動画を配置し、上下を切り取りたくない場合)は、ソースのアスペクト比に合った矩形をトゥイーンの対象にし、周囲のキャンバスを見せるか、カードまたは背景で埋めてください。 - カードごとの
card.zone:選択した構図レイアウトから導きます(分割 → side-panel、積み重ね → lower-third、ピクチャーインピクチャー → fullscreen、オーバーレイ → video-overlay)。または、単発のバリエーションでは別のゾーンを選んでもかまいません(ヒーロー / 引用では fullscreen、データが密な場合は whiteboard-area)。 - カードごとの
accentIndex:各カードで、テーマのアクセントカラー5色のうち1色を使用します。リズムを出すためにカードごとに変え、同じナラティブのまとまりに属する2枚のカードでは同じインデックスを再利用します。 - モーションの語彙:繰り返し使用するパターンを
data-animの種類から2~3個選び(後述の表を参照)、構図に統一感が出るよう一貫して使用します。
次の themeId パレットから選び、構図の <style> ブロック内で CSS 変数 --accent-N / --bg / --text として使用してください。
| themeId | アクセントパレット(5色) | ボードの背景色 | テキスト |
|---|---|---|---|
| classic | #1971c2 #e03131 #2f9e44 #e8590c #9c36b5 | #FFF9E3(用紙) | #1e1e1e |
| noir | #4cc9f0 #f72585 #4ade80 #fb923c #a78bfa | #1a1a1a | #f1f1f1 |
| mint | #0077b6 #d62828 #2d6a4f #e76f51 #7209b7 | #e8faf0 | #1b4332 |
| craft | #bf5700 #d62728 #6c757d #e9b54a #3d5a80 | #f6efe1 | #2d2d2d |
| slate | #0ea5e9 #ef4444 #22c55e #f97316 #a855f7 | #1e293b | #f1f5f9 |
| mono | #000 #555 #888 #aaa #ccc | #fff | #000 |
使用可能なフォント(<SKILL_DIR>/assets/fonts/ 内の woff2。Step 9 で作業ディレクトリに配置):Caveat(手書き風)、
LXGW WenKai TC(中国語の手書き風)、Inter(モダンなサンセリフ体)、Virgil
(幾何学的な手描き風)。@font-face または font-family で直接指定してください。
ビジュアルパターンの参考として、<SKILL_DIR>/references/styles/
には10種類の自己完結型リファレンスカード(academic / editorial / minimal
/ spotlight / geom / whiteboard / audit / terminal / swiss / xhs)が含まれており、出発点としてコピーできます。ただし、これらに合わせる必要はありません。各カードは独自にデザインしてください。
ビジュアルデザインライブラリ(<SKILL_DIR>/references/)
構成レベルの themeId に加え、このスキルには、自由に組み合わせられる3つの独立したビジュアル要素を網羅した、より充実したリファレンスライブラリが <SKILL_DIR>/references/ に含まれています。
Style × Layout × VideoFrame
(10) (4) (3)
| 要素 | キー | 決定する内容 |
|---|---|---|
| スタイル | academic editorial minimal spotlight geom whiteboard audit terminal swiss xhs | カードのビジュアル表現 — フォント、色、装飾、カード内のレイアウト |
| レイアウト | split stack pip overlay | 元動画とカードをキャンバス上にどう配置するか |
| フレーム | clean hairline polaroid | 動画要素の周囲に施す装飾的な枠 |
全体の一覧と、おおまかな選択ガイド(インタビュー/製品発表/データ分析/ソーシャルクリップ/技術チュートリアル/感情を伝えるストーリーなど)については、<SKILL_DIR>/references/DESIGN_INDEX.md
を参照してください。特定のスタイル/レイアウト/フレームを使う場合は、対応するファイルを参照してください。
references/styles/<key>.html— そのスタイルのCSSトークン(色、フォント、パディング、装飾)とプレースホルダーの要点を含む、自己完結型のカード断片です。.card[data-card-id="ref-<key>"]のスタイルブロックをコピーし、data-card-idをカードのIDに変更して、プレースホルダーの内容を実際の要点に差し替えれば完成です。references/layouts/<key>.html— 横向きと縦向きの両方に対応する、正確なvideoBoundsとcardBoundsです。ストーリーボードにはcard.zoneのみを記録します。references/frames/<key>.html—#video-wrapの兄弟要素として追加する装飾的なHTMLと、構成CSS内での配置手順です。
ユーザーが選択したスタイルグループ内であれば、カードごとにスタイルを変えたり、カードごとに layout × frame を選んだりできます。ただし、トランジションが自然につながるようにしてください。
10種類のスタイルはスキル側のデザイントークンであり、構成レベルのテーマではありません。storyboard.composition に宣言する必要はなく、各カードのHTML内に含めます。themeId フィールドでは引き続き構成レベルのパレットを選択でき、ページ本体の背景と動画の枠線の装飾を制御します。
レイアウト構成(カード+動画)
カードごとに、ソース動画とキャンバスをどのように共有するかを決める、連動した判断が2つあります。
card.zone(storyboard.jsonで宣言)— スキーマで定義された5つの値のいずれかです。ステップ9でカードホストラッパーのインラインstyleを記述する際に、ステップ6の表に従ってピクセル境界に変換します。- このカードの時間ウィンドウにおける
#video-wrapの境界(コンポジションの GSAP タイムラインで命令的に宣言)— レイアウトが切り替わるたびに、エージェントが#video-wrapを目標の矩形にトゥイーンします。
スキーマには、カードごとの動画の境界は保存されません。videoTrack.bounds はコンポジションレベルで一度だけ設定されます(デフォルトはキャンバス全体です)。カード間で動画が「移動」するのは、index.html に記述する GSAP アニメーションのみです。card.layout フィールドはありません。スキーマにあるのは card.zone だけです。
4つのコンポジションレイアウト(references/layouts/)— それぞれ、zone と #video-wrap のトゥイーン目標を組み合わせたレシピです。
| コンポジションレイアウト | 推奨する card.zone | #video-wrap の GSAP 目標(横向き 1920×1080) | #video-wrap の GSAP 目標(縦向き 1080×1920) | 使用する場面 |
|---|---|---|---|---|
split | side-panel | { left: 960, top: 0, width: 960, height: 1080 } | { left: 0, top: 960, width: 1080, height: 960 }(下半分) | 話者とデータを横並びにする/50:50の比率 |
stack | lower-third | { left: 14, top: 14, width: 1892, height: 548 }(上部52%) | { left: 0, top: 0, width: 1080, height: 844 }(上部44%) | 上部に話者、下部に要約カード |
pip | fullscreen | { left: 1480, top: 760, width: 400, height: 300 } + .framed クラスを追加 | { left: 690, top: 28, width: 360, height: 203 } + .framed を追加 | 情報量の多いカード+隅の PIP |
overlay | video-overlay | { left: 0, top: 0, width: 1920, height: 1080 }(全面表示) | { left: 0, top: 0, width: 1080, height: 1920 } | シネマティック/ドラマチックな演出/全画面動画上のグラスカード |
4:5(1080×1350)の場合は、縦向きの y/h の値に 1350/1920 ≈ 0.703 を掛けて縮小します(ステップ7.0の Channel A/Channel B の recommendedRatio 解像度表を参照してください)。
単発のバリエーションで使うその他の zone 値(引き続き card.zone を使用します。「layout」フィールドを偽装して作成しないでください)。
zone | 解決後の境界 | 一般的な用途 |
|---|---|---|
fullscreen | キャンバス全体を覆う | ヒーローカード、動画を非表示またはPIPへトゥイーン |
whiteboard-area | 40pxの余白を挿入(横向き)、または下部45%(縦向き) | 情報量の多いデータカード、余白を確保 |
lower-third | 下部30%の帯 | トーキングヘッドの注釈 |
side-panel | 右側42%(横向き)、または下部40%(縦向き) | サイドバー/「split」レシピ |
video-overlay | キャンバス全体。カードのルートは透明にします | 全面表示の動画に重ねるグラスオーバーレイ |
カードごとにレシピを組み合わせることができます。場面に合った card.zone を選び、カード間で #video-wrap をトゥイーンする GSAP の処理を記述してください。
ストーリーボードのレンダー契約
storyboard.jsonはエージェント内部の計画用アーティファクトです。これを解析するCLIコマンドはありません。各カードのHTMLを書く前に、タイミングやコンテンツに関する決定を明確にしておくためのものです。Step 9で組み立てるコンポジションに同じアウトラインを使えるよう、以下のv3形式に従ってください。
必須の構造(完全な例はStep 6を参照):
schemaVersion: 3composition: { fps, width, height, durationSeconds, layout, themeId, seed }—durationSeconds/fps/themeId/layoutはトップレベルではなく、compositionの内側に置きます。videoTrack: { sourcePath, startSec, endSec, bounds? }— 動画の境界は、既定でキャンバス全体です。subtitles: { enabled, ... }cards[]— 各カードには必須フィールドが6つあります:id、intent、startSec、endSec、accentIndex、zone、contentHints
ルール:
- カードの時間は
composition.durationSecondsの範囲内に収め、意図的な場合を除いて重ならないようにします(重なる場合はdata-track-indexでZ順を制御します)。 - ビジュアルの詳細はカードのHTMLフラグメント(Step 8)に記述し、
contentHintsには記述しません。contentHintsはカードをデザインするための構造化されたプロンプトで、レンダー後の見た目を決めるのはHTMLです。 - ストーリーボードの形式は一貫させてください。これを解析する処理はありませんが、Step 8/9の作成時に読み返します。一貫性を保つことで、カードIDとタイミングの同期が維持されます。
- 「overlay × geom × cleanを選んだ」のようなエージェント側の決定は
storyboard.jsonに含めません。作業メモリに保持し、カードHTMLとGSAPトゥイーンの作成時に使ってください。
動画とキャンバスを共有するカードは、背景を透明にします。
GSAPトゥイーンによってカードの背後または横に動画が見える場合(overlayレシピ、pipレシピ、またはcard.zone = 'lower-third' | 'video-overlay'の場面)、カードの.rootに不透明な背景を全面に描画してはなりません。そうしないと、動画が隠れてしまいます。方法は2つあります。
/* Pattern A: transparent root, page body provides the cream backdrop */
html,
body {
background: var(--bg);
}
.card[data-card-id="card-X"] .root {
background: transparent;
}
/* Pattern B: explicit per-card background ONLY for fullscreen cards */
.card[data-card-id="card-hero"] .root {
background: var(--bg);
}
.card[data-card-id="card-overlay"] .root {
background: transparent;
}
side-panelゾーンのカード(splitレシピ)では、カードホストがすでにキャンバスの半分だけを占めているため、不透明なカード背景を使用できます。背景が覆うのはその半分だけです。
8. 各カードのHTMLを作成する
カードごとに$WORK_DIR/public/cards/{card-id}.htmlを作成します。各ファイルには、以下の契約に従ったルートが1つだけのHTMLフラグメントを記述します。
カードHTML契約
<div class="card" data-card-id="{cardId}">
<style>
/* MUST: every rule starts with .card[data-card-id="{cardId}"] */
.card[data-card-id="card-01"] .root {
width: 100%; height: 100%;
display: flex; ...;
font-family: 'Caveat', 'LXGW WenKai TC', serif;
color: var(--text);
background: var(--bg);
}
.card[data-card-id="card-01"] .title { font-size: 84px; ... }
</style>
<div class="root">
<h1
id="card-01-title"
data-anim="kinetic-chars"
data-anim-at="0.3"
data-anim-duration="0.5"
data-anim-stagger="0.04"
data-anim-pattern="pop"
>
<span class="char">S</span>
<span class="char">u</span>
</h1>
<div
id="card-01-line"
data-anim="grow-x"
data-anim-at="0.65"
data-anim-duration="0.5"
data-anim-target-w="420"
style="width:0;height:8px;background:var(--accent-0);border-radius:4px;"
></div>
</div>
</div>
厳守するルール(hyperframesのlintで違反が検出されます):
- ルートは
<div class="card" data-card-id="{cardId}">の1つだけにします。 - インラインの
<style>ルールには、上記のスコープセレクターを必ず付けます。 <script>タグは禁止です。src=/href=に外部URLを指定しないでください(CDNやリモートフォントは禁止です)。- インラインイベントハンドラーは禁止です(
onclick=など)。 - すべてのアセットは、同じ
public/ディレクトリ内への相対パスで指定します。 - テーマ間の移植性を確保するため、色には
var(--accent-N)などを使用します。
アニメーションはコードではなく宣言で指定します。 data-anim-*属性のみを使用し、アニメーション用の<script>は絶対に書かないでください。すべてのdata-anim-*宣言は、Step 9で1つのマスターGSAPタイムラインにコンパイルします。
カードサイズ — 縦向きではモバイルファースト
references/styles/*.html の10個は、1920×1080の横向き
プレビュー用にサイズが設定されています。storyboard.layout = "portrait"(1080×1920。SNS/モバイルで主流のケース)の場合は、すべてのビジュアルサイズを大きくしてください。スマートフォンは画面を近くで見るため、同じピクセル数でも横向きのテレビ風キャンバスより小さく見えます。
| トークン | 横向きの基準値 | 縦向きの目標値 | 倍率 |
|---|---|---|---|
| タイトル(h1/h2 のヒーロー) | 64–96px | 88–132px | ×1.35 |
| 詳細/本文 | 24–30px | 30–40px | ×1.30 |
| キッカー/チップラベル | 14–16px | 18–22px | ×1.30 |
| タイムコード/メタ情報 | 12–14px | 16–18px | ×1.30 |
| データブロックの主要数値 | 48–60px | 64–88px | ×1.40 |
| 行高の倍率 | 1.05–1.5 | 同じ | (拡大しない) |
目安: portraitPx = round(landscapePx × 1.3) とし、視覚的なリズムを整えるため、近い4pxの倍数に切り下げます。ヒーロー見出しは最大×1.4まで拡大できます。小さなメタテキストは、窮屈にならないよう×1.2に抑えます。
縦向きではパディングを少し小さくします。カードの幅が狭いため、大きな横向き用パディング(40–64px)では幅を取りすぎます。縦向きでは、左右のパディングを24–36pxにしてください。
両方のレイアウトで使える単一のカードを作成する場合は、サイズを固定で指定するより、カードのルート要素に @container クエリを使うことをおすすめします。
.card[data-card-id="X"] .root {
container-type: inline-size;
}
.card[data-card-id="X"] .title {
font-size: clamp(64px, 8.5cqi, 132px);
}
.card[data-card-id="X"] .detail {
font-size: clamp(24px, 3.2cqi, 40px);
}
ただし、ほとんどのカードではレイアウトを1つ選べば十分です。ストーリーボードの layout フィールドに合ったサイズ表の列を選んでください。
使用可能な data-anim の種類
このリストは意図的に限定されています。カードは、モーションを Step 9 の共有オーバーレイタイムラインにコンパイルする HTML フラグメントだからです(その GSAP マッピングテーブルを参照してください)。そのため、このワークフローではコンポジションワークフローのように HyperFrames コンポーネントレジストリを検索しません。npx hyperframes catalog が返すのは独自のタイムラインを持つスタンドアロンのコンポジションであり、カードにはそれをマウントする場所がないためです。カードのスコープ付き <style> 内でプレーンな CSS では表現できない見た目は、以下の種類を使って実現してください。
| 種類 | 用途 | 主なパラメーター |
|---|---|---|
fade-in | 登場 | at、duration、ease? |
fade-out | 退出 | at、duration、ease? |
slide-in | スライドして登場 | at、duration、from=left|right|top|bottom、distance |
kinetic-chars | 文字ごとにポップ | at、duration、stagger、pattern=pop|fade — 要素には <span class="char"> の子要素が必要です |
typewriter | 文字ごとにフェード | kinetic-chars と同じですが、デフォルトの stagger は遅めです |
count-up | 数値をアニメーション | at、duration、from、to、format=.0f|.1f|.2f|,d |
draw-path | SVG パスを表示 | at、duration — 要素は <path> にしてください |
grow-y | バーの高さ | at、duration、target-h(px)— 要素の開始時の height:0 |
grow-x | バーの幅 | at、duration、target-w(px)— 要素の開始時の width:0 |
scale-pop | ポップして登場 | at、duration |
blur-in | ぼかしから鮮明へ | at、duration |
mask-reveal | クリップして表示 | at、duration、direction=left|right|top|bottom |
morph-to | 任意の CSS をトゥイーン | at、duration、props='{...JSON...}' |
data-anim-at はカードの startSec を基準とした秒数です。Step 9 で各宣言を GSAP タイムラインにコンパイルする際、カードの startSec を加算して絶対時間を求め、1/fps に量子化してください。
9. コンポジション HTML を組み立てる
アセットを配置し、$WORK_DIR/public/index.html を作成します。
# SKILL_DIR is injected by the host ("Base directory for this skill: …")
SKILL_DIR="<SKILL_DIR>"
mkdir -p "$WORK_DIR/public/fonts" "$WORK_DIR/public/vendor" "$WORK_DIR/public/cards"
cp -n "$SKILL_DIR/assets/fonts/"* "$WORK_DIR/public/fonts/"
cp -n "$SKILL_DIR/assets/vendor/gsap.min.js" "$WORK_DIR/public/vendor/"
# stage the input video — RE-ENCODE with dense keyframes. Sources with a sparse GOP
# (keyframe interval > ~1s) freeze on seek in the renderer (a frozen frame under the
# overlays); -g / -keyint_min set to your composition fps make every frame seekable.
# (Set both to your fps — 30 shown; use 24/25/60 to match.)
ffmpeg -y -i "$VIDEO_PATH" -c:v libx264 -crf 18 -g 30 -keyint_min 30 \
-pix_fmt yuv420p -movflags +faststart -c:a aac "$WORK_DIR/public/input-video.mp4"
コンポジションテンプレート
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
@font-face {
font-family: "Caveat";
src: url("fonts/Caveat-400-latin.woff2") format("woff2");
font-weight: 400;
font-display: block;
}
@font-face {
font-family: "Caveat";
src: url("fonts/Caveat-700-latin.woff2") format("woff2");
font-weight: 700;
font-display: block;
}
@font-face {
font-family: "LXGW WenKai TC";
src: url("fonts/LXGWWenKaiTC-400-latin.woff2") format("woff2");
font-weight: 400;
font-display: block;
}
@font-face {
font-family: "Inter";
src: url("fonts/Inter-400-latin.woff2") format("woff2");
font-weight: 400;
font-display: block;
}
@font-face {
font-family: "Inter";
src: url("fonts/Inter-700-latin.woff2") format("woff2");
font-weight: 700;
font-display: block;
}
@font-face {
font-family: "Virgil";
src: url("fonts/Virgil.woff2") format("woff2");
font-display: block;
}
:root {
/* Pick from the themeId palette table in Step 7 — example: classic */
--bg: #fff9e3;
--text: #1e1e1e;
--accent-0: #1971c2;
--accent-1: #e03131;
--accent-2: #2f9e44;
--accent-3: #e8590c;
--accent-4: #9c36b5;
--font-family: "Caveat", "LXGW WenKai TC", serif;
}
* {
box-sizing: border-box;
}
/* Body font-family MUST list concrete font names (not just var(--font-family)) —
the HyperFrames renderer's static analyzer doesn't expand CSS variables when
resolving fonts, so a var-only chain triggers `font_family_without_font_face`
lint and falls back to a generic. Use the concrete chain here; cards that
want the theme font can still reference var(--font-family) internally. */
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
font-family: "Inter", "Caveat", "LXGW WenKai TC", ui-sans-serif, system-ui, sans-serif;
}
#stage {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
/* video-wrapper holds the source video. Its position / size are animated
over time by the master timeline (one tween per layout transition). */
.video-wrapper {
position: absolute;
left: 0;
top: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
border-radius: 0;
box-shadow: none;
}
.video-wrapper video {
width: 100%;
height: 100%;
object-fit: cover;
}
.card-host {
position: absolute;
pointer-events: none;
overflow: hidden;
}
.card-host .card {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.card-host .char {
display: inline-block;
visibility: visible;
}
/* Subtle drop shadow + rounded corners for non-fullscreen video framings */
.video-wrapper.framed {
border-radius: 16px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
}
</style>
</head>
<body>
<div
id="stage"
data-composition-id="talking-head-recut"
data-start="0"
data-duration="121.2"
data-fps="30"
data-width="1920"
data-height="1080"
>
<div class="video-wrapper" id="video-wrap">
<video
id="bg-video"
src="input-video.mp4"
playsinline
data-has-audio="true"
data-start="0"
data-duration="121.2"
data-track-index="1"
></video>
</div>
<div
class="card-host clip"
data-card-id="card-01"
data-start="1.0000"
data-duration="6.5000"
data-track-index="2"
style="left:0;top:0;width:1920px;height:1080px;visibility:hidden;opacity:0;"
>
</div>
<div
class="card-host clip"
data-card-id="card-02"
data-start="8.0000"
data-duration="12.0000"
data-track-index="2"
style="left:0;top:0;width:960px;height:1080px;visibility:hidden;opacity:0;"
>
</div>
<script src="vendor/gsap.min.js"></script>
<script>
(function () {
// count-up formatter helper
window.__fmt = function (v, fmt) {
if (typeof fmt === "string" && /^\.[0-9]+f$/.test(fmt)) {
return Number(v).toFixed(Number(fmt.slice(1, -1)));
}
if (fmt === ",d") return Math.round(v).toLocaleString();
return String(Math.round(v));
};
const tl = window.gsap.timeline({ paused: true });
// ── Card lifecycle (one block per card) ──
// Example for card-01 [1.0, 7.5] with kinetic-chars at +0.3, grow-x at +0.65:
// Enter (fade in over 0.4s)
tl.set('.card-host[data-card-id="card-01"]', { visibility: "visible" }, 1.0);
tl.fromTo(
'.card-host[data-card-id="card-01"]',
{ opacity: 0 },
{ opacity: 1, duration: 0.4, ease: "power2.out" },
1.0,
);
// Card-internal anims (compile each data-anim-* declaration here)
tl.from(
'.card[data-card-id="card-01"] #card-01-title .char',
{ opacity: 0, y: 8, scale: 0.8, duration: 0.5, ease: "power2.out", stagger: 0.04 },
1.3,
);
tl.fromTo(
'.card[data-card-id="card-01"] #card-01-line',
{ width: 0 },
{ width: 420, duration: 0.5, ease: "power2.out" },
1.65,
);
// Exit (fade out over 0.35s, ending at endSec)
tl.to(
'.card-host[data-card-id="card-01"]',
{ opacity: 0, duration: 0.35, ease: "power2.in" },
7.15,
);
tl.set('.card-host[data-card-id="card-01"]', { visibility: "hidden" }, 7.5);
// ── Video framing transitions ──
// When the next card uses a different composition layout, animate the
// video-wrapper to its new bounds. Example: card-01 = fullscreen
// (video hidden behind), card-02 = split composition (zone="side-panel"
// → video on right, card on left).
// Card-02 enters at 8.0s with the split composition. Animate video to
// the right half during the card-01 → card-02 gap (between 7.5 and 8.0s).
tl.set("#video-wrap", { className: "video-wrapper framed" }, 7.5);
tl.to(
"#video-wrap",
{ left: 960, top: 0, width: 960, height: 1080, duration: 0.6, ease: "power2.inOut" },
7.5,
);
// Card-02 enter — same pattern as card-01
tl.set('.card-host[data-card-id="card-02"]', { visibility: "visible" }, 8.0);
tl.fromTo(
'.card-host[data-card-id="card-02"]',
{ opacity: 0 },
{ opacity: 1, duration: 0.4, ease: "power2.out" },
8.0,
);
// ...card-02 internal anims...
// ── repeat for each card; if the NEXT card's layout differs,
// insert another tl.to('#video-wrap', ...) tween before its enter ──
window.__timelines = window.__timelines || {};
window.__timelines["talking-head-recut"] = tl;
})();
</script>
</div>
</body>
</html>
GSAP ステートメント早見表
各 data-anim 属性を GSAP ステートメントに変換します。時刻は
絶対秒 = card.startSec + data-anim-at で、1/fps 単位に量子化します。
セレクターは .card[data-card-id="X"] #elementId です。
| data-anim | GSAP ステートメントのテンプレート |
|---|---|
fade-in | tl.fromTo(SEL, { opacity: 0 }, { opacity: 1, duration: D, ease: 'power2.out' }, T); |
fade-out | tl.to(SEL, { opacity: 0, duration: D, ease: 'power2.in' }, T); |
slide-in (from=left, dist=80) | tl.fromTo(SEL, { opacity: 0, x: -80 }, { opacity: 1, x: 0, duration: D, ease: 'power2.out' }, T); |
kinetic-chars (pop) | tl.from(SEL + ' .char', { opacity: 0, y: 8, scale: 0.8, duration: D, ease: 'power2.out', stagger: S }, T); |
count-up | (function(){const o={v:FROM};tl.to(o,{v:TO,duration:D,ease:'power2.out',onUpdate:function(){const el=document.querySelector(SEL);if(el)el.textContent=__fmt(o.v,'FMT');}},T);})(); |
draw-path | (function(){const el=document.querySelector(SEL);if(el){const L=el.getTotalLength();tl.set(SEL,{strokeDasharray:L,strokeDashoffset:L},T);tl.to(SEL,{strokeDashoffset:0,duration:D,ease:'power2.inOut'},T);}})(); |
grow-x (target-w=W) | tl.fromTo(SEL, { width: 0 }, { width: W, duration: D, ease: 'power2.out' }, T); |
grow-y (target-h=H) | tl.fromTo(SEL, { height: 0 }, { height: H, duration: D, ease: 'power2.out' }, T); |
scale-pop | tl.fromTo(SEL, { opacity: 0, scale: 0.6 }, { opacity: 1, scale: 1, duration: D, ease: 'back.out(1.6)' }, T); |
mask-reveal (direction=left) | tl.fromTo(SEL, { clipPath: 'inset(0 100% 0 0)' }, { clipPath: 'inset(0 0 0 0)', duration: D, ease: 'power2.inOut' }, T); |
量子化:T = Math.round(absSec * fps) / fps。30fps の場合、最小
ステップは 1/30 ≈ 0.0333s です。JS リテラル内では小数点以下4桁への丸め(.toFixed(4))で問題ありません。
動画フレーミングのリファレンス(layout の値ごと)
動画コンテナのセレクターは #video-wrap です。カード間で境界をアニメーションさせるには、tl.to('#video-wrap', { ...bounds }, T) を使用します。初期の境界は、card-01 のレイアウトに合うよう要素にインラインで設定してください。トランジションの長さは 0.5~0.7 秒とし、ease: 'power2.inOut' を指定します。
装飾フレーム(clean / hairline / polaroid)は #video-wrap の兄弟要素として配置し、レイアウトのトランジションに合わせて追従させます。各フレームの配置用 HTML、推奨 CSS、対応するレイアウトについては、references/frames/ を参照してください。簡単なルールとして、overlay レイアウトでは装飾フレームを使用しません(フルブリード動画とフレームが干渉するため)。PiP レイアウトにはすでに独自のピル型スタイル(border-radius + 白いリング + シャドウ)があるため、装飾フレームは split / stack の上に重ねる場合にのみ追加します。
レイアウトごとの #video-wrap の GSAP ターゲット対応表(横向き 1920×1080。縦向きは references/layouts/*.html を参照してください。4:5 は、上記の比例スケーリングで縦向きから算出します):
| コンポジションのレイアウト | 典型的な card.zone | #video-wrap の GSAP ターゲット | 追加の CSS クラス |
|---|---|---|---|
split | side-panel | { left: 960, top: 0, width: 960, height: 1080 } | — |
stack | lower-third | { left: 14, top: 14, width: 1892, height: 548 }(top 52%) | — |
pip(右下) | fullscreen | { left: 1480, top: 760, width: 400, height: 300 } | pip-pill(border-radius + リング + シャドウ) |
pip(左上) | fullscreen | { left: 40, top: 40, width: 400, height: 300 } | pip-pill |
overlay(動画を全面表示) | video-overlay | { left: 0, top: 0, width: 1920, height: 1080 }(デフォルトから変更なし) | — |
| 動画を非表示(グラフィックのみの場面) | fullscreen | { opacity: 0 }(またはキャンバス外に移動) | — |
PiP の場面に入るとき、または PiP の場面から出るときに、pip-pill の装飾(border-radius + 白いリング + ドロップシャドウ)を切り替えるには:
// Enter pip — add chrome
tl.set("#video-wrap", { className: "video-wrapper pip-pill" }, T);
tl.to(
"#video-wrap",
{ left: 1480, top: 760, width: 400, height: 300, duration: 0.6, ease: "power2.inOut" },
T,
);
// Leave pip — back to clean full-bleed
tl.set("#video-wrap", { className: "video-wrapper" }, T_NEXT);
tl.to(
"#video-wrap",
{ left: 0, top: 0, width: 1920, height: 1080, duration: 0.6, ease: "power2.inOut" },
T_NEXT,
);
カードホストの境界はゾーンに合わせます。Step 6 の冒頭にある表を使ってカードの zone をピクセル境界に変換し、その値をカードホストのインライン style="left:Xpx;top:Ypx;width:Wpx; height:Hpx;..." に記述します。video-overlay ゾーン(overlay レシピ)の場合、カードホストはキャンバス全体に広がります。実際に表示されるカードの位置は、.card .root 内の CSS で決まります。
HyperFrames のレイアウト/アニメーションの QA ルール
- まず各カードの静止ヒーローフレームを作成します。カード全体が見えて読みやすい状態のフレームです。
- 動画、カード、字幕、図が意図せず重なっていないことを確認します。
- 非表示の動画領域がフレームでクリップされ、意図した境界の外に表示されていないことを確認します。
- 一時停止状態のマスタータイムラインを
window.__timelines["talking-head-recut"]として登録します。 - ページ読み込み時にタイムラインを同期的に作成します。
async、setTimeout、Promises、メディアのplay()呼び出しは使用しません。 - レンダリング処理では
Math.random()やDate.now()を使用しません。 repeat: -1は有限のルートdata-durationの下でのみ使用できます。それより前に終了する必要があるループでは、動画の長さを基に有限の繰り返し回数を計算します。- モーションには、レイアウトプロパティ(
top、left、width、height)よりも、GSAP の変換と不透明度(x、y、scale、rotation、opacity)を優先します。 - 動画要素の寸法を直接アニメーションさせず、
#video-wrapなどのラッパーをアニメーションさせます。 - 同じ要素の同じプロパティを、複数のタイムラインから同時にアニメーションさせないでください。
data-layerではなくdata-track-indexを、data-endではなくdata-durationを使用します。- 時間指定のある各要素(
card-host、サブコンポジションなど)には、独自のクラスとともにclass="clip"を指定します。例:class="card-host clip"。表示状態はdata-start/data-durationによって制御されます。ランタイムは、このクラスの有無にかかわらず、すべての[data-start]要素の表示を指定された時間枠に制限します。.clipは Studio と GSAP のクリップ所有権ルールがクリップを認識するためのマーカーなので、省略すると編集や lint が難しくなります(lint:timed_element_missing_clip_class、警告)。 - body/グローバルの
font-familyには、CSS 変数(var(--font-family))ではなく、具体的なフォント名('Inter', 'Caveat', …)を列挙します。HyperFrames のフォントリゾルバーは静的解析中に CSS 変数を展開しません(lint:font_family_without_font_face)。カード内では、@font-face宣言が読み込まれるため、引き続きvar(--font-family)を使用できます。
10. MP4 にレンダリングする
cd "$WORK_DIR"
PRODUCER_BROWSER_GPU_MODE=hardware npx hyperframes render public \
--skill=talking-head-recut \
-o output.mp4 \
--fps 30
hyperframes render <dir> は <dir>/index.html を読み込み、MP4 を生成します。
元のプログラム音声は #bg-video に保持されるため、手動でリマックスしなくても、
レンダリングされた MP4 にはトーキングヘッドの音声が残ります。
macOS では、フラグ PRODUCER_BROWSER_GPU_MODE=hardware(または --browser-gpu)の使用を
強く推奨します。ソフトウェアのみの Chrome レンダリングは、ほとんどのノートパソコンで
タイムアウトするためです。
本番のレンダリング前に問題がないか確認するには、指定したタイムスタンプで 1 フレームだけキャプチャします。
npx hyperframes snapshot public --at 5 # → public/.hyperframes/artifacts/snapshots/<id>/frame-00-at-5s.png (a single --at ignores --out)
11. 結果を報告する
ユーザーに次の内容を伝えます。
- 作業ディレクトリのパス
storyboard.json(作成したカードの概要)public/cards/*.html(カードごとに 1 つの HTML)public/index.html(組み立てたコンポジション)output.mp4(完成した動画)- 使用した ASR プロバイダー
- カード数と選定方法(1 文で)
- 不足しているキーや品質に関する注意点
ライブプレビューはリクエストがあった場合のみ任意で提供します。 オーバーレイを重ねた状態で、クリップは public/index.html 内で変更されずに再生されるため、忠実にプレビューできます。実行中は開かないでください。 ユーザーから依頼された場合は、レンダリングの後に長時間稼働するサーバーを起動し、URL を報告します。
(cd "$WORK_DIR/public" && npx hyperframes preview --background) # or `npx hyperframes play` for a shareable link
ユーザーから依頼がない限り、作業ディレクトリを削除しないでください。