mcpField
G
graphic-overlays

graphic-overlays

github.com/heygen-comクリエイティブ・メディア74.7k29.1k 回インストール今日更新
リポジトリ

graphic-overlays について

既存のトーク動画に、元映像を変更せずタイミング付きのグラフィックカードを重ねるスキルです。文字起こしに基づいてカードを設計し、hyperframes で MP4 にレンダリングします。

主な機能

  1. 01Whisper による音声のローカル文字起こし
  2. 02文字起こし内容に合わせたカードの配置
  3. 03柔軟な HTML カードデザインの作成
  4. 04合成コンポジションの MP4 レンダリング
  5. 05中間ファイルと構成案の確認

活用シーン

  1. 01トーク動画にタイトルやローワーサードを追加
  2. 02動画に引用やデータの注釈を表示
  3. 03インタビューにタイミング付きのグラフィックパネルを配置

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/heygen-com/hyperframes --skill graphic-overlays

インストール後、Claude Code で/graphic-overlaysを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

NOTICE.md
assets/vendor/
gsap.min.js
references/
DESIGN_INDEX.md
references/frames/
clean.html
hairline.html
polaroid.html
references/layouts/
overlay.html
pip.html
split.html
stack.html
references/styles/
academic.html
audit.html
editorial.html
geom.html
minimal.html
spotlight.html
swiss.html
terminal.html
whiteboard.html
xhs.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 — 長さ/幅/高さ/fps
  • audio.mp3 — 抽出した音声
  • transcript.json — フラットな単語配列 [{ text, start, end }, …](Whisper。segments も words ラッパーもありません)
  • storyboard.json — 軽量なカード概要(エージェントの計画)
  • public/cards/card-XX.html — カードごとに 1 つの HTML フラグメント
  • public/index.html — 最終的に組み立てられた合成用 HTML
  • output.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?'"
      }
    }
  ]
}

必須のカードフィールド:

fieldtypepurpose
idstringカード HTML と GSAP セレクターで使用する安定した ID
intentstring自然言語による説明。カードの合成に使用されます
startSec / endSecnumber秒単位の時間(endSec > startSec)
accentIndex0 | 1 | 2 | 3 | 4このカードで使用する5つのテーマアクセントカラーの番号
zoneenum (see below)カードを配置するキャンバス上の位置
contentHintsobject自由形式のデータ。エージェントが kicker/title/detail/data/quote をここに配置します
archetype (optional)stringカードのパターンを記録しておくために付けられる自由形式のラベル。省略時はデフォルトの自由形式になります
transition (optional)enum: cut | fade | slide | wipeカード間のトランジションを宣言します

5つの zone 値:

zoneresolved boundswhen to use
fullscreenキャンバス全体を覆いますヒーローシーン、大きな数字、マントラ
whiteboard-area40px の余白(または縦向きの高さの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 min8–12sソーシャル動画の標準的なペース
3 – 10 min12–20s余裕を持たせ、各カードにより多くの内容を盛り込みます
10 – 30 min20–35s長尺の講義/インタビューに適したリズム
> 30 min30–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つを事前に計算してください:

  1. ソース動画のアスペクト比(metadata.json の width / height)から recommendedRatio を計算します:

    • sourceAspect = width / height
    • sourceAspect ≥ 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)」を付け、推奨する理由がユーザーにわかるようにします。

  2. Step 6 の式(max(5, round(videoSec / (basePace × densityMultiplier))))から autoCount を計算し、「auto」選択肢のラベルに具体的な数を表示できるようにします。

環境との互換性 — 利用可能な最適な質問手段を選んでください。 すべてのランタイムで同じ構造化質問ツールを使えるとは限りません。以下の順序で適用してください:

  1. AskUserQuestion(Claude Code、Anthropic Console)— 下記の構造化された4つの質問の呼び出しを使用します。
  2. その他のネイティブな確認ツール(例:ask_question、request_user_input、IDE 固有のプロンプト)— 同じ4つの質問文と選択肢リストを使って、そのツールで尋ねます。推奨マーカーと事前計算した値はそのまま保持します。
  3. ネイティブツールがない場合(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」と答えた場合は、再質問せずに省略します。

ユーザーの回答を受け取った後(どの手段でも):

  1. 比率の回答から出力キャンバスを決定し、書き込む storyboard.composition.width / height の正確な値を指定します:

    ユーザーの選択composition.width × heightstoryboard.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 つの特定のスタイルで迷った場合は、それら 2〜4 個の 特定のスタイルオプションを指定して、2 回目の AskUserQuestion を送信してください。

  3. 密度の回答から最終的な cardCount を決定 します:

    ユーザーの選択最終的な cardCount
    Auto (推奨)すでに計算済みの autoCount
    Fewermax(5, round(autoCount × 0.6))
    Moreround(autoCount × 1.5) (上限クランプなし)
    Other = "" (整数)max(5, parseInt(n))
    Other = それ以外autoCount にフォールバック
  4. この表からビデオフレームを自動選択 します (フレームはユーザーに 確認しません — レイアウト × スタイルから導出されます):

    レイアウトwarm-paper スタイル (academic / whiteboard / editorial / xhs)clinical スタイル (audit / swiss / terminal / minimal)experimental スタイル (geom / spotlight)
    splitpolaroidhairlineclean
    stackpolaroidhairlineclean
    pipclean (pip ピルにはすでに chrome があります)cleanclean
    overlayclean (フルブリードでは装飾フレームが禁止されています)cleanclean
  5. 選択した内容を 1 文でユーザーに伝えます — 比率 (+ キャンバス サイズ)、レイアウト、特定のスタイル、フレーム、最終的な cardCount — その後、 Step 7 の残りの処理 (カードごとのレイアウト、モーションパターン) を進めます。

  6. 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)
要素キー決定する内容
styleacademic editorial minimal spotlight geom whiteboard audit terminal swiss xhsカードのビジュアル表現 — フォント、色、装飾、カード内のレイアウト
layoutsplit stack pip overlayソース動画とカードをキャンバス上でどのように配置するか
frameclean 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)使用する場面
splitside-panel{ left: 960, top: 0, width: 960, height: 1080 }{ left: 0, top: 960, width: 1080, height: 960 } (下半分)話者 + データの横並び / 50:50 の比重
stacklower-third{ left: 14, top: 14, width: 1892, height: 548 } (上部 52%){ left: 0, top: 0, width: 1080, height: 844 } (上部 44%)話者が上部 + その下に要約カード
pipfullscreen{ left: 1480, top: 760, width: 400, height: 300 } + .framed クラスを追加{ left: 690, top: 28, width: 360, height: 203 } + .framed を追加コンテンツ重視のカード + コーナー pip
overlayvideo-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-area40px のインセットマージン (ランドスケープ) または下部 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: 3
  • composition: { 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–96px88–132px×1.35
詳細/本文24–30px30–40px×1.30
キッカー/チップラベル14–16px18–22px×1.30
タイムコード/メタ情報12–14px16–18px×1.30
データブロックの主要数値48–60px64–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-pathSVG パスの表示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-animGSAP ステートメントのテンプレート
fade-intl.fromTo(SEL, { opacity: 0 }, { opacity: 1, duration: D, ease: 'power2.out' }, T);
fade-outtl.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-poptl.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 クラス
splitside-panel{ left: 960, top: 0, width: 960, height: 1080 }—
stacklower-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

ユーザーから依頼がない限り、作業ディレクトリを削除しないでください。

コメント