HyperFrames
HTML 是影片的真相來源。一個合成就是一個 HTML 檔案,搭配 data-* 屬性來控制時間、GSAP 時間軸處理動畫,以及 CSS 負責外觀。此框架會處理片段的可見性、媒體播放和時間軸同步。
Open Design 整合(此操作面的關鍵)
當此技能在 Open Design 內部執行(即已設定 $OD_PROJECT_DIR)時,輸出流程是固定的:只有渲染好的 .mp4 應該放到專案根目錄。合成原始檔案(hyperframes.json、meta.json、index.html、素材)則屬於一個隱藏的快取目錄,以避免弄亂使用者的檔案檢視器或聊天室的「產出檔案」標籤。
OD 內的渲染工作流程 – 快速路徑:
對於大多數 OD 請求(「測試影片」、「5 秒產品展示」、「示範片段」),請不要從零開始撰寫合成 HTML。使用 HyperFrames 內建的模板並僅編輯提示實際要求更改的部分。「從頭撰寫」路徑會耗費數分鐘的模型輸出和無聲的聊天工具時間;模板路徑只需幾秒。
# 1. 選擇一個隱藏的快取槽位。點號前綴 → OD 的專案檔案清單會跳過它,
# 因此原始檔案不會弄亂聊天室。
COMP_REL=".hyperframes-cache/$(date +%s)-$(openssl rand -hex 2)"
COMP="$OD_PROJECT_DIR/$COMP_REL"
# 2. 取得一個立即可渲染的模板(hyperframes.json、
# meta.json、index.html,已帶有 GSAP CDN 及
# window.__timelines.main 註冊)。這在你的 shell 中執行 — 純檔案複製,
# 不需要 Chrome,除了 npx 快取外不需網路。
npx hyperframes init "$COMP" --example blank --skip-skills --non-interactive
# 3. 僅編輯 $COMP/index.html — 如果需要非預設長度,更改根元素的
# `data-duration`;在 <style> 中替換佔位色盤;加入 1–3 個用於
# 文字/圖像的片段 <div>;以及將對應的 GSAP 補間附加到現有的
# `window.__timelines["main"] = gsap.timeline({paused:true})` 區塊內。
# 保持最小編輯;模板已經是有效的 HF。
# 4. 透過 OD daemon 派發渲染。不要在目前 shell 中執行 `npx hyperframes
# render` — daemon 會在一個無沙箱的行程中幫你執行它。(許多代理
# CLI,特別是 Claude Code,會將 Bash 包裝在 macOS 的 sandbox-exec 中,
# 導致 puppeteer 的 Chrome 子行程在幀捕捉過程中掛起。Daemon 行程
# 是無沙箱的,因此渲染可以可靠完成。)
#
# 派發器在約 1 秒內返回一個 {taskId};透過迴圈呼叫
# `"$OD_NODE_BIN" "$OD_BIN" media wait <taskId>` 來驅動渲染完成。
# 每次呼叫會長輪詢最多 25 秒(遠低於你 shell 工具的預設 30 秒上限),
# 並以 0/2/5 的退出碼分別表示完成/執行中/失敗。
out=$("$OD_NODE_BIN" "$OD_BIN" media generate \
--project "$OD_PROJECT_ID" \
--surface video \
--model hyperframes-html \
--output "<descriptive-name>.mp4" \
--composition-dir "$COMP_REL")
ec=$?
task_id=$(printf '%s\n' "$out" | tail -1 | jq -r '.taskId // empty')
since=$(printf '%s\n' "$out" | tail -1 | jq -r '.nextSince // 0')
while [ "$ec" -eq 2 ] && [ -n "$task_id" ]; do
out=$("$OD_NODE_BIN" "$OD_BIN" media wait "$task_id" --since "$since")
ec=$?
since=$(printf '%s\n' "$out" | tail -1 | jq -r '.nextSince // '"$since")
done
[ "$ec" -ne 0 ] && { echo "$out" >&2; exit "$ec"; }
每次 generate 和每次 wait 呼叫最多持續約 25 秒,因此代理 shell 工具的預設約 30 秒上限永遠不會觸發。在整個迴圈期間,HF 的進度行(Capturing frame N/M)會即時串流到 stderr。當渲染完成時,最後一行 stdout 是 {"file": { "name": "<output>", "size": …, "kind": "video", … }} — 在你的回覆中引用 file.name,讓使用者知道產出了什麼。
在 OD 內跳過視覺識別閘門。 下方「方法」章節中的 HARD-GATE 部分指示你在撰寫任何合成前要讀取 DESIGN.md / visual-style.md,否則停止並問 3 個心情問題。該閘門適用於獨立的 HF 專案。OD 專案已有自己的設計系統層 — 使用者在建立專案時已選擇了視覺方向。對於 OD 測試渲染,預設為:深色畫布(#0b0b0f)、一個暖色強調色(#ffb76b)、一個冷色強調色(#7da4ff)、節制運動。僅在使用者提示過於模糊以致無法選定主題(非常罕見)時才詢問風格意見。
何時跳過模板並從頭撰寫:僅當使用者明確要求空白模板顯然無法容納的內容時(例如多合成時間軸、音訊反應式疊加層、與已產生的 TTS 音軌同步的字幕)。對於其他所有情況,init + edit 是預設路徑。
你仍然可以在自己的 shell 中執行的較輕量 HF 子指令(它們不需要啟動 Chrome):
npx hyperframes lint "$COMP"— 在派發前驗證合成npx hyperframes transcribe <audio>— 產生字幕npx hyperframes tts <text>— 產生旁白
保留 daemon 派發給 render/inspect/preview(任何需要 Chrome 的操作)。
請勿呼叫 "$OD_NODE_BIN" "$OD_BIN" media generate --model hyperframes-html — 該派發路徑會刻意返回 400(AGENT_RENDERED)。HyperFrames 是由你透過 npx 直接渲染的。
請勿將 hyperframes.json / meta.json / index.html 放在專案根目錄;OD 的檔案清單會遞迴掃描,使用者會在聊天室中看到三個不相關的檔案出現。
對於 render 以外的 CLI 選項(lint、preview、transcribe、tts、inspect、benchmark),當任務需要時,直接從你的 shell 工具呼叫它們(例如,在合成中引用之前,先在快取中產生 TTS 音訊)。
方法
在撰寫 HTML 之前,從高層次思考:
- 什麼 — 觀看者應該體驗到什麼?識別敘事弧線、關鍵時刻和情感節拍。
- 結構 — 有多少個合成,哪些是子合成 vs 內聯,哪些音軌承載什麼(影片、音訊、疊加層、字幕)。
- 時間 — 哪些片段驅動持續時間,轉場落在何處,節奏如何。
- 佈局 — 先建立最終狀態。參見下方的「先佈局後動畫」。
- 動畫 — 然後使用下面的規則加入運動。
對於小型編輯(修正顏色、調整時間、加入一個元素),直接跳到規則。
視覺識別閘門
按此順序檢查:
- 專案中存在 DESIGN.md? → 讀取它。使用其確切的顏色、字體、運動規則以及「禁止事項」限制。
- 存在 visual-style.md? → 讀取它。套用其
style_prompt_full和結構化欄位。(注意:visual-style.md是專案特定的檔案。visual-styles.md是包含 8 個命名預設的樣式庫 — 是不同的檔案。) - 使用者指定了樣式(例如「Swiss Pulse」、「dark and techy」、「luxury brand」)?→ 讀取 visual-styles.md 以取得 8 個命名預設。產生一個最小化的 DESIGN.md,包含:
## Style Prompt(一個段落)、## Colors(3-5 個十六進位值及其角色)、## Typography(1-2 個字體家族)、## What NOT to Do(3-5 個反模式)。 - 以上皆非? → 在撰寫任何 HTML 之前,先問 3 個問題:
- 情緒是什麼?(爆炸性 / 電影感 / 流暢 / 技術感 / 混亂 / 溫暖)
- 亮色還是深色畫布?
- 有沒有特定的品牌顏色、字體或視覺參考? 然後根據答案產生一個最小化的 DESIGN.md。
每個合成都必須將其色盤和字體追蹤回 DESIGN.md、visual-style.md 或使用者的明確指示。如果你正想用 #333、#3b82f6 或 Roboto — 你跳過了這一步。
對於運動預設值、尺寸、進入模式和緩動 — 請遵循 house-style.md。House style 處理事物如何移動。DESIGN.md 處理事物看起來如何。
先佈局後動畫
將每個元素定位在其最顯眼的時刻應處的位置 — 即它已完全進入、正確放置且尚未退出的影格。先將此寫為靜態 HTML+CSS。尚不需要 GSAP。
為何這很重要: 如果你將元素定位在它們的動畫起始狀態(螢幕外、縮放到 0、透明度 0),然後補間到你認為它們應該落地的位置,你只是在猜測最終佈局。重疊問題在影片渲染之前是不可見的。透過先建立最終狀態,你可以在加入任何運動之前看到並修正佈局問題。
流程
- 為每個場景識別主影格 — 大多數元素同時可見的時刻。這就是你要建立的佈局。
- 為該影格撰寫靜態 CSS。
.scene-content容器必須使用width: 100%; height: 100%; padding: Npx;搭配display: flex; flex-direction: column; gap: Npx; box-sizing: border-box來填滿整個場景。使用 padding 將內容向內推 — 絕不要在內容容器上使用position: absolute; top: Npx。絕對定位的內容容器在內容高於剩餘空間時會溢出。僅為裝飾元素保留position: absolute。 - 使用
gsap.from()加入進入動畫 — 從螢幕外/不可見動畫到 CSS 位置。CSS 位置是真相基礎;補間描述了到達那裡的旅程。 - 使用
gsap.to()加入退出動畫 — 從 CSS 位置動畫到螢幕外/不可見。
範例
/* scene-content 填滿場景,padding 定位內容 */
.scene-content {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
padding: 120px 160px;
gap: 24px;
box-sizing: border-box;
}
.title {
font-size: 120px;
}
.subtitle {
font-size: 42px;
}
/* 容器填滿任何場景大小(1920x1080、1080x1920 等)。
Padding 定位內容。Flex + gap 處理間距。 */
錯誤示範 — 硬編碼尺寸和絕對定位:
.scene-content {
position: absolute;
top: 200px;
left: 160px;
width: 1920px;
height: 1080px;
display: flex; /* ... */
}
// 步驟 3:動畫進入那些位置
tl.from(".title", { y: 60, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
tl.from(".subtitle", { y: 40, opacity: 0, duration: 0.5, ease: "power3.out" }, 0.2);
tl.from(".logo", { scale: 0.8, opacity: 0, duration: 0.4, ease: "power2.out" }, 0.3);
// 步驟 4:從那些位置動畫退出
tl.to(".title", { y: -40, opacity: 0, duration: 0.4, ease: "power2.in" }, 3);
tl.to(".subtitle", { y: -30, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.1);
tl.to(".logo", { scale: 0.9, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.2);
當元素在時間上共享空間時
如果元素 A 在元素 B 進入同一區域之前退出,兩者都應在其各自的主影格中具有正確的 CSS 位置。時間軸排序保證它們永遠不會在視覺上共存 — 但如果你跳過佈局步驟,就不會發現它們可能因時間錯誤而意外重疊的情況。
什麼算是意圖重疊
分層效果(文字後的光暈、陰影元素、背景圖案)和 z 軸堆疊設計(卡片堆疊、深度層次)是意圖內的。佈局步驟的目的是捕捉非意圖的重疊 — 兩個標題互相重疊、一個統計數字覆蓋標籤、內容溢出畫面。
資料屬性
所有片段
| 屬性 | 必要與否 | 值 |
|---|---|---|
id | 是 | 唯一識別碼 |
data-start | 是 | 秒數或片段 ID 參考("el-1"、"intro + 2") |
data-duration | img/div/合成需要 | 秒數。影片/音訊預設為媒體長度。 |
data-track-index | 是 | 整數。同一音軌的片段不能重疊。 |
data-media-start | 否 | 修剪來源偏移量(秒) |
data-volume | 否 | 0-1(預設 1) |
data-track-index 不影響視覺分層 — 請使用 CSS z-index。
合成片段
| 屬性 | 必要與否 | 值 |
|---|---|---|
data-composition-id | 是 | 唯一合成 ID |
data-start | 是 | 開始時間(根合成:使用 "0") |
data-duration | 是 | 優先於 GSAP 時間軸的持續時間 |
data-width / data-height | 是 | 像素尺寸(1920x1080 或 1080x1920) |
data-composition-src | 否 | 外部 HTML 檔案路徑 |
合成結構
透過 data-composition-src 載入的子合成使用 <template> 包裝。獨立合成(主要的 index.html)不使用 <template> — 它們將 data-composition-id div 直接放在 <body> 中。在獨立檔案上使用 <template> 會隱藏瀏覽器中的所有內容並中斷渲染。
子合成結構:
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<!-- 內容 -->
<style>
[data-composition-id="my-comp"] {
/* 作用域樣式 */
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// 補間...
window.__timelines["my-comp"] = tl;
</script>
</div>
</template>
在根檔案中載入:<div id="el-1" data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="5" data-track-index="0"></div>
動畫規則
GSAP 屬性
- 時間線:每個合成建立一個命名時間線
window.__timelines["<id>"] = gsap.timeline({paused: true})。獨立合成使用window.__timelines["main"]。 - 緩動:進入使用
power3.out,退出一半時間使用power2.in,強調使用back.out(1.7)。 - 透明度:使用
opacity進行淡化。進入時使用opacity: 0,退出時使用opacity: 0。 - 位置:使用
y進行垂直滑入/滑出,x進行水平滑入/滑出。進入時從y: 40或y: 60開始;退出時使用y: -30或y: -40。 - 縮放:使用
scale進行放大/縮小效果。進入時從scale: 0.8或scale: 0.6開始;退出時使用scale: 0.9。 - 文字:文字動畫使用
splitText(透過 GSAP 外掛)或逐字效果,但僅在需要時使用。預設為區塊級滑入。 - 顏色:不要補間到新的十六進位顏色,而是使用具有所需顏色的包裝元素進行交叉淡化。
時間定位
- 進入動畫從元素
data-start開始,並持續到data-duration。退出應在持續時間結束前開始,以便在結束時完成。 - 不要在 GSAP 中使用絕對時間,而是使用標籤(
tl.addLabel("enter", 0); tl.from(..., "enter"))。 - 對於重疊的轉場,退出在前一切片結束前 0.3-0.5 秒開始,而進入從同一時間開始(例如,剪輯 A 在 3.0 秒退出,剪輯 B 在 2.7 秒進入)。
示例
// 獨立合成
const tl = gsap.timeline({ paused: true });
tl.from(".title", { y: 60, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
tl.from(".subtitle", { y: 40, opacity: 0, duration: 0.5, ease: "power3.out" }, 0.2);
// ...
window.__timelines["main"] = tl;
音訊反應式動畫
- 使用
data-volume控制音量。 - 對於節拍同步:使用
gsap.to()搭配duration:等於節拍間隔。 - 使用 CSS
filter: brightness()或box-shadow進行發光/脈衝效果。 - 使用
gsap.timeline({paused: true, onUpdate: () => { ... }})進行自訂音訊分析,但僅在需要進階使用時。
場景轉場
- 交叉淡化:在同一個音軌上重疊兩個片段。退出片段淡化到
opacity: 0,進入片段從opacity: 0淡化進來。 - 擦拭:使用 CSS
clip-path進行擦拭過渡。從clip-path: inset(0 0 0 0)到clip-path: inset(0 100% 0 0)(方向可變)。 - 顯示:使用
scale和opacity。從scale: 0.8; opacity: 0到scale: 1; opacity: 1。 - 硬切換:在同一個時間點立即切換
opacity: 0和opacity: 1。使用data-start的精確時間。
注意事項
- 始終使用
box-sizing: border-box。 - 除非必要,否則不要使用
!important。 - 使用相對單位(
%、em、rem)進行回應式設計。 - 使用 CSS 變數進行顏色和間距一致性。
- 測試所有場景大小(1920x1080、1080x1920、16:9、9:16)。
- 使用語義化類名和 HTML 結構。
- 註釋複雜的動畫邏輯。