graphic-overlays について
既存のトーク動画に、元映像を変更せずタイミング付きのグラフィックカードを重ねるスキルです。文字起こしに基づいてカードを設計し、hyperframes で MP4 にレンダリングします。
主な機能
- 01Whisper による音声のローカル文字起こし
- 02文字起こし内容に合わせたカードの配置
- 03柔軟な HTML カードデザインの作成
- 04合成コンポジションの MP4 レンダリング
- 05中間ファイルと構成案の確認
活用シーン
- 01トーク動画にタイトルやローワーサードを追加
- 02動画に引用やデータの注釈を表示
- 03インタビューにタイミング付きのグラフィックパネルを配置
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ heygen-com/ hyperframes --skill graphic-overlaysインストール後、Claude Code で/graphic-overlaysを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
NOTICE.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詳細
グラフィックオーバーレイ
Graphic Overlays は、最初から最後まで再生されるローカル動画に、タイミングを合わせたデザイン済みのグラフィックカード(タイトル、ローワーサード、データコールアウト、引用、サイドパネル、ピクチャーインピクチャーなど)を重ねます。カード(タイミングと内容)はエージェントがデザインし、各カードの HTML を会話内に直接記述した後、1 つの合成用 HTML にまとめ、hyperframes で MP4 にレンダリングします。固定のアーキタイプ一覧や、決められたカード構成はありません。オーバーレイは、実際にトランスクリプトで語られている内容をもとに作られます。
作成前に適切なワークフローを確認してください。 このスキルは、既存のトーキングヘッド動画にデザイン済みのグラフィックカード(タイトル、ローワーサード、データコールアウト、引用、サイドパネル、PiP)を追加します。ユーザーが通常のキャプション/字幕(話された言葉をテキストにしたもの)を求めている場合は →
/embedded-captions、ナレーションのない短い単一要素(ロゴの短い演出/ローワーサードなど)を求めている場合は →/motion-graphicsを使います。動画クリップには手を加えません。タイミング変更、色調変更、リフレーミング、順序変更、音声編集は NLE 編集にあたり、対象範囲外です。URL/トピック/PR をもとに動画を作成する場合は、作成ワークフローを使います。オーバーレイとキャプションのどちらがよいか迷った場合は、先に/hyperframesを読んでください。
embedded-captionsと同系統のグラフィック追加スキルです。 キャプションは 話された言葉 を読みやすい字幕として追加します。このスキルは、再生中の動画に デザイン済みのグラフィック を重ねます。 通常の字幕 →embedded-captions。動画をゼロから作成 → 作成ワークフロー(product-launch-video/faceless-explainer/ …)。
作業ディレクトリ内で確認できる中間ファイル:
metadata.json— 長さ/幅/高さ/fpsaudio.mp3— 抽出した音声transcript.json— フラットな単語配列[{ text, start, end }, …](Whisper。segmentsもwordsラッパーもありません)storyboard.json— 軽量なカード概要(エージェントの計画)public/cards/card-XX.html— カードごとに 1 つの HTML フラグメントpublic/index.html— 最終的に組み立てられた合成用 HTMLoutput.mp4— レンダリングされた動画
CLI の解決
# hyperframes — transcription (local Whisper) + rendering the assembled HTML to MP4
npx hyperframes --help
このスキルはすべて、hyperframes CLI とシステムの ffmpeg/ffprobe で実行します。
文字起こしには、hyperframes transcribe を使ったローカルの Whisper を使用します。サードパーティサービス、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(このスキルに同梱。手順 9 で作業ディレクトリに配置)
文字起こしにキーは必要ありません。hyperframes transcribe がローカルで Whisper を実行します(手順 4)。
hyperframes render を実行する場合、macOS では次の設定を強く推奨します。
export PRODUCER_BROWSER_GPU_MODE=hardware
2. 作業ディレクトリを作成する
すべての成果物は videos/<project-name>/ に保存します。他の動画ワークフロー(product-launch-video/faceless-explainer/pr-to-video)と同じ規則です。作業ディレクトリはワークスペースのルートに置き、以下のすべてをこの 1 つのサブディレクトリに出力します。
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 のタイムスタンプを含みます)。
手順 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?'"
}
}
]
}
必須のカードフィールド:
| field | type | purpose |
|---|---|---|
id | string | カード HTML と GSAP セレクターで使用する安定した ID |
intent | string | 自然言語による説明。カードの合成に使用されます |
startSec / endSec | number | 秒単位の時間(endSec > startSec) |
accentIndex | 0 | 1 | 2 | 3 | 4 | このカードで使用する5つのテーマアクセントカラーの番号 |
zone | enum (see below) | カードを配置するキャンバス上の位置 |
contentHints | object | 自由形式のデータ。エージェントが kicker/title/detail/data/quote をここに配置します |
archetype (optional) | string | カードのパターンを記録しておくために付けられる自由形式のラベル。省略時はデフォルトの自由形式になります |
transition (optional) | enum: cut | fade | slide | wipe | カード間のトランジションを宣言します |
5つの zone 値:
| zone | resolved bounds | when to use |
|---|---|---|
fullscreen | キャンバス全体を覆います | ヒーローシーン、大きな数字、マントラ |
whiteboard-area | 40px の余白(または縦向きの高さの45%)を空けます | データが密集した内容や注釈付きコンテンツ |
lower-third | 下部30%の帯状領域 | 表示中の動画に注釈を重ねる場合 |
side-panel | 右側42%(横向き)または下部40%(縦向き) | 片側にデータ、もう片側に動画を配置する場合 |
video-overlay | キャンバス全体。カードは主に透明であることを想定 | 全画面動画に注釈を重ねる場合 |
Step 9 でコンポジションを組み立てる際は、上の表に従って各カードの zone
をカードホストのラッパー上のピクセル範囲に変換します。
動画の範囲は、コンポジションレベルで一度だけ設定します(videoTrack.bounds)。
動画を「カード間で移動」しているように見せるには、コンポジションの <script> 内で
#video-wrap を対象とした GSAP トゥイーンを作成します(Step 9 を参照)。
カードの役割も、決められた物語の展開もありません。 カードは動画で実際に語られている内容から生まれます。引用ばかりでも、データばかりでもかまいません。数字から始めても、ストーリーから始めてもかまいません。トランスクリプトに合わせてリズムを決めてください。
要点はいくつにするか? — 尺と密度から自動推定します。 固定の上限はありません。動画の長さから基本ペースを選び、情報密度に応じて調整します。固定するのは下限のみです。短い動画にもリズムを持たせるため、カードは最低5枚にします。
ステップ1 — 尺に応じた基本ペース(中程度の密度での、カードあたりの自然な秒数):
| 動画の長さ | 基本ペース(カードあたりの秒数) | 理由 |
|---|---|---|
| < 60s(ショートリール) | 6–8s | 短尺動画では、視聴者はテンポの速いカットを期待します |
| 60s – 3 min | 8–12s | ソーシャル動画の標準的なペース |
| 3 – 10 min | 12–20s | 余裕を持たせ、各カードにより多くの内容を盛り込みます |
| 10 – 30 min | 20–35s | 長尺の講義/インタビューに適したリズム |
| > 30 min | 30–60s | エピソード形式で、章に近い感覚です |
ステップ2 — 密度の倍率(基本ペースに乗算します):
| トランスクリプト内のシグナル | 倍率 | 効果 |
|---|---|---|
| 高密度 — 数字や明確な主張が多い、テンポが速い、列挙形式、1~2文ごとに新しいアイデアが出てくる | × 0.7 | カットを速くし、カードを増やします |
| 中密度 — データと物語が混在する流れ | × 1.0 | 基本ペース |
| 低密度 — 長いストーリーが一つ続く、同じ内容を何度も捉え直す、ゆっくりと内省するペース、一つの主張が展開していく | × 1.5 | カットを遅くし、カードを減らします |
ステップ3 — 計算:
secPerCard = basePace × densityMultiplier
cardCount = max(5, round(videoDurationSec / secPerCard))
例(上限を設けない点に注目してください。長い動画では自然にカードが増えます):
- 30sのリール、短いオチが一つ(低密度) → 7 × 1.5 = 10.5秒/カード → round(30/10.5)=3 → 下限を適用して5枚のカード
- 60sの内省的な独白(低密度) → 10 × 1.5 = 15秒/カード → 4枚 → 下限を適用して5枚のカード
- 121sの、データが豊富なトーキングヘッド動画(高密度) → 10 × 0.7 = 7秒/カード → 17枚のカード
- 5 minのインタビュー、中密度 → 16 × 1.0 = 16秒/カード → 19枚のカード
- 10 minの詳細な解説、高密度 → 16 × 0.7 = 11秒/カード → 55枚のカード
- 30 minの講義、中密度 → 28 × 1.0 = 28秒/カード → 64枚のカード
- 1 hrのポッドキャスト、低密度 → 45 × 1.5 = 67.5秒/カード → 53枚のカード
1枚のカードが約15秒を超える場合は、より内容の豊かなカード(データブロック、複数段階での表示、段階的なアニメーションで展開する複数のサブポイント)を計画してください。静的な一行だけでは、8秒を超えると退屈になります。カードが30秒を超える長尺の作品では、タイムラインをサブコンポジションに分割することを検討してください(各章に1つの .html を用意し、data-composition-src でマウントします)。こうすると、ファイルごとの GSAP タイムラインを管理しやすくできます。HyperFrames の lint 警告 timeline_track_too_dense を参照してください。
content はプレーンな文字列("Title: annualized 5.69%\nNotes: ...")でも、データを表現できる任意の JSON 構造でもかまいません。カードごとに、エージェントが構造を決めます。
任意のアウトロ。 このスキルには固定のブランドアウトロはありません。ユーザーが締めのカードを希望する場合は、中立的なもの(ワードマーク+1行のタグライン、約1.5~2秒、フェードイン → 短く表示 → フェードアウト)を自分でデザインし、cards[] に追加して、composition.durationSeconds をその endSec まで延長してください。それ以外の場合は、最後のコンテンツカードで終了します。
7. レンダリング戦略を決める
ビジュアルの方向性をユーザーに確認する(最初に実行してください)
カードのデザインや境界を決める前に、出力比率、レイアウト、スタイル、カード密度のプリセットを選ぶよう、ユーザーに尋ねてください。フレームは、選択されたレイアウト × スタイルの組み合わせから自動選択されます(下の「フレームの自動選択」テーブルを参照)。質問を送る前に、次の2つを事前に計算してください:
-
ソース動画のアスペクト比(
metadata.jsonの width / height)から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)」を付け、推奨する理由がユーザーにわかるようにします。
-
Step 6 の式(
max(5, round(videoSec / (basePace × densityMultiplier))))からautoCountを計算し、「auto」選択肢のラベルに具体的な数を表示できるようにします。
環境との互換性 — 利用可能な最適な質問手段を選んでください。 すべてのランタイムで同じ構造化質問ツールを使えるとは限りません。以下の順序で適用してください:
AskUserQuestion(Claude Code、Anthropic Console)— 下記の構造化された4つの質問の呼び出しを使用します。- その他のネイティブな確認ツール(例:
ask_question、request_user_input、IDE 固有のプロンプト)— 同じ4つの質問文と選択肢リストを使って、そのツールで尋ねます。推奨マーカーと事前計算した値はそのまま保持します。 - ネイティブツールがない場合(Codex CLI、通常のテキストのみのランタイム)— 通常の会話で直接尋ねます。このセクション末尾のプレーンテキスト用テンプレートを使用します。1つのメッセージに4つの番号付き質問を収めてください(1回のやり取りでの上限は2~5問です。ここでは上限内に収まります)。
すべての手段に適用されるルール:
- 1回のやり取りで尋ねるのは最大2~5問です。ここでは4問なので範囲内です。
- 不足情報がレンダリングの妨げにならない場合でも、最終出力に実質的な影響を与えるパラメーター(ratio、layout、style、cardCount)を確認するため、1回は質問してください。
- ユーザーがデフォルトをすでに承認している(「just use defaults」「no need to ask」「auto-pick everything」など)、または質問しないよう求めている場合は、質問を完全に省略し、次を使用します:
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." }
]
}
]
})
「Other」について — AskUserQuestion はカード数の質問に「Other」選択肢を自動的に追加します。ユーザーはカード数の目標値として数値を直接入力できます(例:「8」「20」)。入力を整数として解析します。解析できた場合は、その値を使用します(最低値は5)。解析できない場合は「auto」に戻します。
チャンネル B — プレーンテキストによる代替手段(Codex CLI、ネイティブの質問ツールがないランタイム)。通常のメッセージとしてこれを投稿し、返信を待ちます。箇条書き形式の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」と答えた場合は、再質問せずに省略します。
ユーザーの回答を受け取った後(どの手段でも):
-
比率の回答から出力キャンバスを決定し、書き込む
storyboard.composition.width / heightの正確な値を指定します:ユーザーの選択 composition.width × height storyboard.layout フィールド 16:91920 × 1080 "landscape"9:161080 × 1920 "portrait"4:51080 × 1350 "portrait"(スキーマは 4:5 を portrait として扱います — 高さ > 幅)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を送信してください。 -
密度の回答から最終的な cardCount を決定 します:
ユーザーの選択 最終的な cardCount Auto (推奨) すでに計算済みの autoCountFewer max(5, round(autoCount × 0.6))More round(autoCount × 1.5)(上限クランプなし)Other = "" (整数) max(5, parseInt(n))Other = それ以外 autoCountにフォールバック -
この表からビデオフレームを自動選択 します (フレームはユーザーに 確認しません — レイアウト × スタイルから導出されます):
レイアウト warm-paper スタイル (academic / whiteboard / editorial / xhs) clinical スタイル (audit / swiss / terminal / minimal) experimental スタイル (geom / spotlight) splitpolaroidhairlinecleanstackpolaroidhairlinecleanpipclean(pip ピルにはすでに chrome があります)cleancleanoverlayclean(フルブリードでは装飾フレームが禁止されています)cleanclean -
選択した内容を 1 文でユーザーに伝えます — 比率 (+ キャンバス サイズ)、レイアウト、特定のスタイル、フレーム、最終的な cardCount — その後、 Step 7 の残りの処理 (カードごとのレイアウト、モーションパターン) を進めます。
-
5 つの値 (ratio / layout / style / frame / cardCount) をワーキングメモリに 記録します (スキーマフィールドは不要です)。Step 8 で各カードの HTML を 書くときや、トークンと構造のために一致する
references/<dim>/<key>.htmlを 読むときに、これらを参照します。
ユーザーが「Other」で 10 スタイルライブラリにない自由記述のスタイル名を 選択した場合は、新しいカードビジュアルを自分でデザインするためのヒントとして 扱いますが、選択されたレイアウトの bounds には引き続き準拠してください。
レンダリング戦略の入力
Step 7.0 で比率/レイアウト/スタイル/カード数/フレームが固定されたら、 カードごとに決める項目は次のとおりです。
- GSAP のターゲット内におけるソース動画のフィット方法:動画要素には
object-fit: coverが設定され、#video-wrapのトゥイーン範囲でクリップされます。 トリミングしたくない場合(たとえば、横長のキャンバスに縦長のソース動画を配置し、 上下を切りたくない場合)は、ソースのアスペクト比に合った矩形をトゥイーンの ターゲットにし、周囲のキャンバスを表示させます(またはカード/背景で塗りつぶします)。 - カードごとの
card.zone:選択した構成レイアウトから導きます (split → side-panel、stack → lower-third、pip → fullscreen、 overlay → video-overlay)。または、単発のバリエーションには別の zone を選びます (ヒーロー/引用には fullscreen、データ量が多い場合は whiteboard-area)。 - カードごとの
accentIndex:各カードには 5 色のテーマアクセントカラーから 1 色が 割り当てられます。リズムを生むためカードごとに変え、同じナラティブのまとまりに属する 2 枚のカードでは同じインデックスを再利用します。 - モーションの語彙:
data-animの種類(後述の表を参照)から、繰り返し使う パターンを 2〜3 個選び、一貫した構成になるよう統一します。
次の themeId パレットから選び、構成の <style> ブロックで
--accent-N/--bg/--text の CSS 変数として使用します。
| 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)
| 要素 | キー | 決定する内容 |
|---|---|---|
| style | academic editorial minimal spotlight geom whiteboard audit terminal swiss xhs | カードのビジュアル表現 — フォント、色、装飾、カード内のレイアウト |
| layout | split stack pip overlay | ソース動画とカードをキャンバス上でどのように配置するか |
| frame | 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が含まれており、storyboard.jsonのカードごとのlayoutフィールドに貼り付けて使える JSON スニペットもあります。references/frames/<key>.html—#video-wrapの兄弟要素として追加する装飾的な HTML と、構成 CSS への配置手順が含まれています。
カードごとに style × layout × frame を選択できます — トランジションが自然であれば、カード間ですべてを変更できます。よくある展開例:editorial × overlay × clean で始め、データカードでは audit × split × hairline に切り替え、最後は whiteboard × pip × polaroid で締めくくります。
10種類のスタイルはスキル側のデザイントークンであり、構成レベルのテーマではありません — storyboard.composition で宣言する必要はなく、各カードの HTML 内で使います。themeId フィールドでは引き続き、ページ本体の背景と動画の枠線を制御する構成レベルのパレットを選択できます(上表参照)。
レイアウト構成 (カード + 動画)
カードごとに 2 つの連携した決定が、そのカードがソース動画とどのように キャンバスを共有するかを定義します:
card.zone(storyboard.jsonで宣言) — 5 つのスキーマ値のうちの 1 つ。 ステップ 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 チャンネル A / チャンネル 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 の種類
| 種類 | 用途 | 主なパラメーター |
|---|---|---|
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 と同じですが、デフォルトのスタッガーはより遅くなります |
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 を基準とした秒数です。ステップ 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="graphic-overlays"
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"
muted
playsinline
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["graphic-overlays"] = 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.7s とし、ease: 'power2.inOut' を指定します。
装飾フレーム(clean / hairline / polaroid)は #video-wrap の兄弟要素として配置し、レイアウトのトランジションに合わせて動かします。各フレームの配置 HTML、推奨 CSS、および組み合わせるレイアウトについては、references/frames/ を参照してください。簡単なルールとして、overlay レイアウトでは装飾フレームを表示しません(フルブリード動画とクロームがぶつかるため)。PiP レイアウトには独自のピル型スタイル(border-radius + 白いリング + シャドウ)がすでにあるため、装飾フレームは split / stack の上にのみ追加します。
#video-wrap の GSAP ターゲット対応表(コンポジションレイアウトごと)
(横向き 1920×1080 — 縦向きと 4:5 については、3 種類の比率すべてを記載した references/layouts/*.html を参照してください):
| コンポジションレイアウト | 一般的な 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 }(上部 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-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["graphic-overlays"]として登録します。 - ページ読み込み時にタイムラインを同期的に作成します。
async、setTimeout、Promises、メディアのplay()呼び出しは使用しません。 - レンダリングパスで
Math.random()やDate.now()を使用しないでください。 repeat: -1は使用せず、動画の長さから有限の繰り返し回数を計算します。- モーションには、レイアウトプロパティ(
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"。HyperFrames ランタイムは.clipを使って、data-start … data-start+data-durationの範囲内で表示を制御します。これがない場合、要素は動画全体で表示されます(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=graphic-overlays \
-o output.mp4 \
--fps 30
hyperframes render <dir> は <dir>/index.html を読み込み、MP4 を生成します。
macOS では PRODUCER_BROWSER_GPU_MODE=hardware(または --browser-gpu)フラグの使用を強く推奨します。ソフトウェアのみで Chrome をレンダリングすると、ほとんどのノートパソコンでタイムアウトします。
全体をレンダリングする前に問題がないか確認するには、特定のタイムスタンプのフレームを 1 枚キャプチャします。
npx hyperframes snapshot public --at 5 # → public/snapshots/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) # or `npx hyperframes play` for a shareable link
ユーザーから依頼がない限り、作業ディレクトリを削除しないでください。