HyperFrames
HTML 是影片的唯一真理來源。一個合成作品是一個包含 data-* 屬性(用於時間控制)、GSAP 時間軸(用於動畫)以及 CSS(用於外觀)的 HTML 檔案。此框架負責處理剪輯的可見性、媒體播放以及時間軸同步。
方法
探索(僅限探索性請求)
對於開放式請求(例如「製作一個產品發布影片」、「為我們的品牌創作一些內容」),當使用者尚未確定方向時,應先理解意圖,然後再選擇顏色:
- 觀眾 — 誰會觀看這個?開發者?主管?一般消費者?
- 平台 — 在哪裡播放?社群媒體(15 秒)、網站首頁、產品展示、內部使用?
- 優先級 — 什麼最重要?動態品質?內容準確性?品牌忠實度?速度?
- 變化 — 使用者想要多種選項,還是只想要一個最佳方案?
對於明確的請求(例如「加入一個標題卡」、「修正場景 3 的時間」),請跳過探索步驟。
對於探索性請求,可以考慮提供 2-3 個有實質差異的變化版本——不僅僅是配色不同,而是節奏、能量層級或結構方法上的差異。一個是安全/符合預期的,另一個則是大膽創新的。這並非強制要求——只是在適當情況下的可用工具。
步驟 1:設計系統
如果專案中存在 design.md 或 DESIGN.md 檔案,請先讀取它(請檢查兩種大小寫——在 Linux 系統中是不同檔案)。它包含了品牌顏色、字型以及限制條件。請使用其中的確切數值——不要自行發明顏色或替換字型。任何格式都可以(YAML 前置資料、散文、表格——只需提取數值)。
如果它指定的字型在本地找不到(沒有包含 .woff2 檔案的 fonts/ 目錄,也不是內建字型),請在撰寫 HTML 前警告使用者:「design.md 指定了 [字型名稱],但找不到字型檔案。請將 .woff2 檔案加入 fonts/,否則我將回退使用 [最接近的內建替代字型]。」
如果沒有 design.md 存在,請提供使用者一個選擇:
- 使用者已指定風格或氛圍? → 閱讀 visual-styles.md 了解 8 個已命名的預設風格。選擇最接近的。
- 想要以視覺方式瀏覽選項? → 執行設計選擇器:閱讀 references/design-picker.md 了解完整流程。它會提供一個視覺化選擇器頁面。使用者在瀏覽器中設定氛圍、色板、字體排印學和動態,然後複製產生的 design.md 並貼回對話中。
- 想要跳過步驟並快速進行? → 詢問:氛圍、淺色或深色、是否有任何品牌顏色/字型?然後從 house-style.md 中選擇一個色板。
design.md 定義了品牌。它不定義影片合成規則。 這些規則來自 references/video-composition.md 和 house-style.md。請以適合影片的規模使用品牌顏色——而不是以網頁 UI 的不透明度。
步驟 2:提示擴展
每個合成作品都應執行此步驟(單場景片段和簡單編輯除外)。此步驟將使用者的意圖對齊到 design.md 和 house-style.md,並產生一個一致的中間產物,讓所有下游代理程式能以相同方式讀取。
請閱讀 references/prompt-expansion.md 了解完整流程和輸出格式。
步驟 3:規劃
在撰寫 HTML 之前,先從高層次思考:
- 什麼 — 觀眾應該體驗到什麼?找出敘事弧線、關鍵時刻和情感節奏。
- 結構 — 有多少個合成作品?哪些是子合成作品,哪些是內聯的?哪些音軌承載哪些內容(影片、音頻、疊加層、字幕)。
- 節奏 — 在實作之前宣告你的場景節奏。哪些場景是快速切換,哪些是停留,著色器落在哪裡,能量高峰在哪裡。命名模式:快-快-慢-快-著色器-停留。請閱讀 references/beat-direction.md 了解節奏模板。
- 時間 — 哪些剪輯決定了持續時間?轉場落在哪裡?節奏如何?
- 佈局 — 先建立最終狀態。請參閱下方的「先佈局再動畫」。
- 動畫 — 然後使用下述規則加入動態。
只建立被要求的內容。 「一個標題卡」的請求不意味著你要加上「一個標題卡 + 3 個輔助場景 + 背景音樂 + 字幕」。每個場景、每個元素、每個補間動畫都應有其存在的理由。如果額外的場景或元素能真正改善作品,請提出建議——但不要擅自加入。
對於小型編輯(修正顏色、調整時間、加入一個元素),直接跳到規則步驟。
先佈局再動畫
將每個元素放置在其最顯眼的時刻——也就是它完全進入、正確定位且尚未退出時的畫面位置。首先將其寫成靜態的 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 位置是基準事實;補間動畫描述了到達那裡的過程。(在透過data-composition-src載入的子合成作品中,建議使用gsap.fromTo()——請參閱 references/motion-principles.md 中的 GSAP 承載規則。) - 使用
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/compositions) | 秒數。影片/音頻預設為媒體持續時間。 |
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-variable-values | 否 | 子合成主機上每個實例的變數覆蓋值的 JSON 物件 |
在根 <html> 元素上:
| 屬性 | 必填 | 值 |
|---|---|---|
data-composition-variables | 否 | 已宣告變數的 JSON 陣列(id/type/label/default)——驅動 Studio 編輯 UI,並為 getVariables() 提供預設值 |
合成結構
透過 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 });
// tweens...
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="10" data-track-index="1"></div>
變數(參數化合成)
使用不同內容渲染相同的合成——標題、主題顏色、價格、字幕——而無需編輯原始 HTML。
三步驟模式:
- 宣告:在合成的
<html>根元素上使用data-composition-variables宣告變數。每個條目需要id、type(string、number、color、boolean、enum之一)、label和default。列舉條目還需要options: [{value, label}, ...]。 - 讀取:在合成腳本內使用
window.__hyperframes.getVariables()讀取解析後的值。回傳的是已宣告的預設值、每個實例的覆蓋值以及 CLI 覆蓋值的合併結果。 - 覆蓋:在渲染時使用
npx hyperframes render --variables '{...}'(頂層)或使用主機元素上的data-variable-values='{...}'(子合成的每個實例)進行覆蓋。
<!doctype html>
<html
data-composition-variables='[
{"id":"title","type":"string","label":"Title","default":"Hello"},
{"id":"theme","type":"enum","label":"Theme","default":"light","options":[
{"value":"light","label":"Light"},
{"value":"dark","label":"Dark"}
]}
]'
>
<body>
<div data-composition-id="root" data-width="1920" data-height="1080">
<h1 id="hero" class="clip" data-start="0" data-duration="3"></h1>
<script>
const { title, theme } = window.__hyperframes.getVariables();
document.getElementById("hero").textContent = title;
document.body.dataset.theme = theme;
</script>
</div>
</body>
</html>
# 開發預覽使用已宣告的預設值
npx hyperframes preview
# 使用覆蓋值渲染
npx hyperframes render --variables '{"title":"Q4 Report","theme":"dark"}' --output q4.mp4
# 或從 JSON 檔案
npx hyperframes render --variables-file ./vars.json
子合成的每個實例值: 透過 data-composition-src 載入的子合成內部也可以使用相同的 getVariables()。每個主機元素傳遞其自己的值:
<div
data-composition-id="card-pro"
data-composition-src="compositions/card.html"
data-variable-values='{"title":"Pro","price":"$29"}'
></div>
<div
data-composition-id="card-enterprise"
data-composition-src="compositions/card.html"
data-variable-values='{"title":"Enterprise","price":"$99"}'
></div>
設計特性
- 類型安全:
getVariables()會根據每個變數的type強制轉換其值,因此在腳本中使用時無需進行額外的類型檢查。 - 編輯器整合:
data-composition-variables驅動 HyperFrames Studio 中的屬性面板 UI。每個變數都會顯示一個具有其label的控制項和適合其type的輸入元素。 - 腳本的宣告週期: 變數在
<body>渲染之前解析。腳本執行時getVariables()已具有最終值。
警示
- 不要依賴特定於平台的字型(例如 macOS 上的 'Helvetica Neue'、Windows 上的 'Segoe UI'、Linux 上的 'Ubuntu')——除非它們已載入。始終在
fonts/中提供.woff2檔案或使用 Web 安全字型。 - 不要假設固定的場景持續時間——除非使用者明確指定。讓 GSAP 時間軸決定其自身的持續時間。
- 「修正時間」意味著調整
data-start/data-duration——而不是整個鎖定畫面。尋找根本原因(配置錯誤的剪輯、重疊、缺口)。 - 輸出始終是一個單一的 index.html,包含一個帶有
data-composition-id="root"的<div>和所有子合成模板。 - 子合成檔案應寫入
compositions/目錄(例如compositions/hero-scene.html、compositions/card.html)。 - 資料屬性使用
camelCase——而不是kebab-case。data-start、data-duration、data-composition-id。使用 JavaScriptdataset屬性時不受影響,但為了與框架資料讀取器一致,我們使用camelCase。 - 永遠不要使用
<body>背景作為場景背景——合成通過<iframe>或<video>標籤載入,這些標籤可能無法正確繼承<body>樣式。始終使用明確的.scene-background元素或具有背景的.scene-content容器。檔案之間不能滑動。 - 對於每個合成場景,從單一的 CSS 範圍宣告開始:
[data-composition-id="root"] {}或[data-composition-id="scene-2"] {}。不要依賴全域選擇器,因為框架在載入多個合成時會串聯樣式表。 - 不要猶豫——提出替代方案。 如果使用者的請求效率低下或完全錯誤(例如,為一個簡單的單幀圖形請求學習複雜的 Blender),請說出來。使用者寧願被誠實告知,也不願浪費時間。