Opus Visual Motion Engine
一個嚴謹的視覺交付物管線:規劃 → 從模板建立 → 渲染 → QA → 修正 → 匯出。品質來自渲染前後應用的檢查關卡,而非設計者的品味。請依序執行步驟。即使任務「看起來很簡單」,也不得跳過任何步驟。
以下路徑是相對於此技能的目錄(你所獲得的 skill_resources 基底)。稱之為 $SKILL。
0. 先閱讀(無條件)
在撰寫任何程式碼之前,請閱讀與交付物相符的參考資料。這些資料在相關主題上具有權威性:
| 交付物 | 閱讀 |
|---|---|
| 影片、動態圖形、解說影片、產品影片 | references/video-generation.md、references/motion-design.md、references/javascript-animation.md |
| 動態字體 / 字型主導作品 | 上述影片組 + references/typography.md |
| 著陸頁、儀表板、Mockup、UI | references/visual-design.md、references/typography.md |
| 圖表、示意圖、資料視覺化 | references/visual-design.md §4 |
| 構圖 / 鏡頭決定 | references/cinematography.md |
| 時間與緩動 | references/animation-primitives.md |
| 需同步的音樂或人聲 | SKILL.md §10 + references/video-generation.md §6 |
範例作品位於 examples/*.md。閱讀最接近需求的那個。
1. 先規劃再寫程式碼(請寫出來)
在建立 HTML 之前,先將計劃寫在你的回覆中(或寫在輸出旁的 PLAN.md 裡)。僅存在於腦中的計劃不算數。
UI / 頁面
- 主要訊息:一句話。觀看者必須在 3 秒內理解什麼?
- 命名方向:一個名稱、4–6 個已命名的十六進位色彩 token 並帶有指定含義、帶有角色的字型、形式處理方式。從主題的產業、材質與慣用語衍生而來。玩具店與金融科技儀表板絕不會共享一個方向。
- 層級:用 PRIMARY / SECONDARY / TERTIARY 標記每個區塊。只有 PRIMARY 能獲得那一個大膽的動態效果。
- 章節推進:每個章節一行,說明其任務以及如何交接給下一個章節。
- 渲染前計算:文字寬度 ≈
chars × 0.55 × font-size(無襯線)或× 0.5(襯線);盒子寬度 =max(title_chars × 8, subtitle_chars × 7) + 24(在 14px 時);行容納數量 = Σ寬度 + 間距 ≤ 容器。追蹤每個連接器是否穿過每個盒子。如果穿過,則規劃一條 L 形路徑。
影片 / 動態圖形
- 核心訊息:一句話。如果需要兩句話,那就是兩支影片。
- 節拍:3–7 個。每個節拍回答「觀看者現在知道了什麼?」
- 每個節拍的場景卡:開始/結束(秒)、構圖、鏡頭、字型大小、基本圖形(2–4 個)、退出。使用
references/motion-design.md§2 中的模板。 - 標誌性時刻:命名出機制被「展示」出來的那唯一一個影格,而非被「標籤化」(例如,雜訊被濾波成信號)。如果故事只能靠其字幕才能運作,請重新設計該節拍。
- 全域時間線:絕對時間戳記。一個場景的 EXIT 在下一個場景佔用該空間之前完成。將
DURATION設定為真正的結束時間(預設 10–20 秒;僅在有旁白時為 30–45 秒)。
沒有場景卡的場景不應出現在影片中。
2. 從模板建立(影片)
將 assets/composition-template.html 複製到交付物旁,並填寫 EDIT 區塊。請勿改寫 ENGINE 區塊。這些區塊已提供:
- 一個 1920×1080 的
#stage(可為 1080×1920 或 1080×1080 更改STAGE_W/H),可適應任何視窗; window.DURATION、window.seek(t)、window.render(t)、window.SCENES:用於截圖/匯出的合約(QA 會從SCENES擷取每個場景);at()、ease.*、lerp、mulberry32(基於種子的 PRNG),以及用於場景可見性和 EXIT 的layer();- 音訊輔助函式
audioAt(t, "rms"| "low")、since(t, list)、hit(t, list, dur),它們會讀取window.AUDIO(§10),若無則回傳 0; - 一個預覽播放器(空白鍵、←/→、Home/End),在截圖和偏好減少動畫下會自動關閉;
- 所需的 3 層色彩 token(明亮層、受
prefers-color-scheme保護的暗層、[data-theme]層)。
對於頁面,請按照 §6 撰寫一般的單一檔案 HTML。
3. 確定性時間(影片)
render(t)是唯一的狀態驅動者。每個位置、透明度、大小、數字和 dashoffset 都根據以秒為單位的t計算得出。seek(x)會渲染出完全相同的影格,無論之前渲染了什麼。t以秒為單位。將t限制在[0, DURATION]內,切勿限制在[0, 1]:將秒數限制在 0..1 會導致整個時間線靜止不動。- 禁止使用:
setTimeout、setInterval、CSS 動畫/轉場作為時間線、未設定種子的Math.random()、狀態中的Date.now()。requestAnimationFrame僅能用於推進播放時鐘並呼叫render(clock)。模板已實作此功能。 - 隨機性:在構建時從
mulberry32(seed)生成,切勿在render內部生成。 - SVG 繪製效果需要這三個屬性:
pathLength="1"、stroke-dasharray="1"及stroke-dashoffset從 1 到 0。若無 dasharray,線條會從影格 0 開始就完全顯示。 inline-block文字跨度會吞掉尾隨空格。將間距放在margin-right中。
4. 動態語法
五種移動,每種各司其職。如果一個動態不屬於以下任何一種,請刪除它。
- ENTER:每個頁面或場景中一個編排好的序列,按閱讀順序,同層級的元素間隔 60–120 毫秒。每個區塊都做淡入是 AI 的典型特徵。
- EMPHASIS:一次一個元素:縮放膨脹(≤1.2)、發光或具有含義的顏色變化。
- TRANSITION:狀態改變。變化的部分移動;其他所有東西保持靜止。0.2–0.6 秒。
- CAMERA:舞台層級的縮放/平移,每個場景最多一次,0.8–1.2 秒,切勿在閱讀文字期間發生。
- EXIT:0.3–0.5 秒,完全完成。沒有元素在場景邊界處處於中間狀態,除非是一個刻意的連續性錨點,它保持完全相同或進行變形(同一個物件改變狀態,這是最強的可用轉場)。
轉場傳達意義:硬切 = 類別改變,變形 = 同一事物改變狀態,平移 = 遍歷。切勿到處都使用預設的交叉淡入淡出。時間與緩動請見 references/animation-primitives.md。一起移動的相鄰元素應獲得不同的持續時間或偏移。
5. 堆疊決策
| 情況 | 堆疊 |
|---|---|
| 頁面微動畫 | transform/opacity 上的 CSS 轉場,0.2–0.6 秒;在 @media (prefers-reduced-motion: no-preference) 內,循環 ≤2 秒 |
| 影片 / 動態圖形 / 動態字體 | 模板 + 原生 render(t)(預設) |
| >6 條交錯軌道或 SVG 變形編排 | GSAP UMD 固定版本,單一暫停的主時間線,seek(t) → tl.seek(t) |
| 圖表 | 內嵌 SVG(預設)或 Chart.js UMD 搭配 animation:false,使用十六進位顏色(Canvas 無法讀取 CSS 變數) |
| 生成式藝術 | p5.js 搭配 randomSeed/noiseSeed |
| 3D,僅當 3D 是訊息本身時 | Three.js UMD 固定版本;鏡頭在 render(t) 中編寫腳本 |
使用能達成結果的最簡單堆疊。外部腳本必須是來自 cdnjs.cloudflare.com 或 cdn.jsdelivr.net 的明確固定版本。其他所有東西皆內嵌。每個字型都需有一個真正的後備堆疊。
6. 技術底線(無可協商)
- 單一
.html檔案,CSS 和 JS 內嵌,小於 16 MB,圖片/資料做為data:URI。 - 顏色做為
:roottoken;深色層在@media (prefers-color-scheme: dark)下,由:root:not([data-theme="light"])保護,並在:root[data-theme="dark"]下重複;明確設定body背景色。 meta viewport搭配viewport-fit=cover;env(safe-area-inset-*)內邊距;html/body 使用height:100%,絕不使用100vh;寬區塊在其自己的overflow-x:auto內捲動;網格使用minmax(0,1fr)。- 顯示的計算後數字需經過
Math.round、toFixed或Intl.NumberFormat。計數器必須精準落在最終數值上。 - 可及性:對比度 ≥4.5:1(≥24px 文字為 ≥3:1),可見的焦點,尊重
prefers-reduced-motion(模板顯示最終結束影格)。 - 可讀字型:在 1080p 影片影格中,文字不小於 20px (H/54)。在行動頁面上,主體文字不小於 14px。
- 瀏覽器儲存空間絕不能用於保存時間線狀態。如有任何使用儲存空間的情況,需將其包裹在 try/catch 中。
7. 避免通用的人工智慧設計
在需求留有餘地之處,切勿將這些預設值作為選項(qa.mjs 會自動標記其中幾項):
| 典型特徵 | 修正方式 |
|---|---|
奶油色 #F4F1EA 搭配襯線展示字型 + 陶土色 #D97757 強調色 | 從主題自身的材料中提取色板 |
| 接近黑色 + 一種刺眼的強調色,無論主題為何都使用 | 僅在方向能以書面方式證明其合理性時才允許 |
| 相同的圓角卡片,到處都是同一種圓角半徑,相同的柔和陰影 | 按層級設定圓角半徑 token;卡片僅用於有限度的物件 |
| 作為裝飾的漸層、紫色漸層、玻璃擬態 | 平面填色;最多一個漸層,僅用於表示連續的物理屬性 |
| 全大寫標題、中間點元資料字串、連結上的「→」 | 使用句首大寫;刪除重複內容的標籤 |
| 預設使用 Inter / 系統字型 | 為此主題選擇字型,並帶有後備堆疊 |
| 標題中一個單詞被強調 | 僅當強調色是一個語義 token,且在作品中其他地方用於相同含義時才允許(例如,珊瑚色 = 雜訊) |
| 通用的英雄區:標題 + 副標題 + CTA + 截圖 | 以主題世界中最具特色的事物開場 |
| 所有東西都置中,每個區塊都是相同的網格節奏 | 每個區塊使用對齊系統;變化節奏 |
| 所有東西同時動起來,到處都是 300ms | 時間地圖 + 交錯(§4) |
將大膽用於一個地方,然後移除一個配件。
8. 渲染 + QA(交付前強制執行)
執行自動化 QA。它會無頭渲染檔案並生成證據與判定。需要 Node ≥18。在技能的全新副本中首次執行需要在 $SKILL/scripts 中執行 npm install(如果 scripts/node_modules 已存在則跳過)。
node "$SKILL/scripts/qa.mjs" path/to/deliverable.html
在 Windows 的 DSH 中,使用相同的命令透過 pwsh 工具執行它。選項:--step 0.25(為快速場景提供更精細的網格)、--out <dir>、--mode page|video。
影片模式(當存在 DURATION + seek 時自動啟用)會每隔一個步驟擷取一個影格並檢查:禁止的 API、亂序重訪的確定性、凍結的時間線、空白段落、近乎空白的跨度、未結束的結束影格、文字碰撞、文字疊在其他圖形上、裁剪、對比度、最小字型大小、從未繪製的 dashoffset 線條、減少動畫模式下的循環、控制台/頁面錯誤以及模板特徵。
頁面模式會渲染桌面 1440、行動 390 及暗色配置,並檢查相同的佈局規則以及行動水平溢出。
輸出位於 qa-<name>/:report.md(判定與發現)、report.json(每個影格的墨水覆蓋率與變化)、contact-sheet.png(所有影格在一張圖片上)、moments.png(取自 window.SCENES 的每個場景的進入點、中點和結束點)、key_000..100.png 和 frames/。
- 明確傳遞標誌性時刻和任何同步點:
--at 5.4,9.1。 - 刻意的圖層疊加(故意堆疊的動態字體、覆蓋在影片上的標題)應放在帶有
data-qa-allow-overlap屬性的元素內。這會跳過僅對該子樹的文字碰撞與文字疊圖形檢查。切勿用它來規避意外的碰撞。 fontWARN 表示首選字型家族未載入,文字正使用後備字型。在判斷佈局之前修正此問題,因為所有寬度都是錯的。
退出代碼 0 = 沒有 FAIL。FAIL 是阻塞性的。 每個 WARN 都必須被修正,或在你的審查中用一句話說明理由。
9. 審查循環
- 如果你的模型可以讀取圖片: 開啟
contact-sheet.png,然後是任何看起來有問題的key_*.png。判斷像素,而非你的意圖。 - 如果你的模型是純文字: 使用
report.md加上report.json中的每個影格表格。coverage< 0.015 表示該影格內容空白。一個場景的diff為 0 表示沒有任何東西在移動。diff的峰值標示著一次切換。交叉比對每個場景卡的時間戳記與這些數字。 - 用具體的觀察結果回答每個問題,而不是打勾:
- 構圖:在每個關鍵影格中,視線首先落在哪裡?那是要傳達的訊息嗎?
- 層級:PRIMARY 是否清晰可辨?每個影格中是否恰好只有一個重要的移動元素?
- 機制:標誌性時刻是否在移除字幕的情況下「展示」了概念?
- 連貫性:所有交接是否乾淨,沒有元素處於邊界的中間狀態,且錨點相同或正在變形?
- 時間:是否有偏移,沒有預設持續時間,且閱讀時間 ≥ 每 8 個字 1.2 秒?
- 字體排版 / 對比度 / 密度:依照計劃的大小,每個影格內 ≤5 個競爭元素,在暗色模式下可讀?
- 敘事:觀看者能否在觀看一次後重複核心訊息?
- 修正 → 重新執行
qa.mjs→ 重新審查。重複直到 QA 通過且審查中沒有任何未解決的項目。 - 最後一個問題,誠實回答:「這看起來是有意設計的,還是僅僅是生成的?」 如果答案是「生成的」:指出什麼是通用的,修改計劃(§1),然後重建。第一次渲染幾乎從不是最終交付物。預計需要 2–3 次生成。
10. 音訊同步(當有音樂或人聲時)
切勿靠耳朵猜測時間。將音訊轉換為資料,讓 render(t) 讀取它:
node "$SKILL/scripts/audio-data.mjs" music.mp3 --inject composition.html # 將 window.AUDIO 寫入 HTML
AUDIO.beats(以估計節奏均勻間隔的網格)、AUDIO.onsets(打擊、音符、音節),以及每個影格的AUDIO.rms/AUDIO.low(0..1;low是 <150 Hz 的鼓點/低音樂段)。- 使用模板輔助函式驅動動態:
hit(t, AUDIO.beats, 0.2)用於節拍脈衝(EMPHASIS)、audioAt(t, "low")用於對鼓點反應的縮放、since(t, AUDIO.onsets)用於逐次打擊的揭示。將場景切換放在節拍上:從AUDIO.beats挑選場景開始點。 - 同步透過構建在影格層級精準:一個脈衝落在其節拍時間點或之後的第一個影格上(≤1 影格,30 fps 時 ≤33 毫秒)。如果音訊開始時間晚於 t=0,請傳遞
--offset <s>。 - 在任何人在聆聽之前就會檢查音量大小。修剪過的樣本會導致退出碼 1。樣本峰值高於 −1 dBFS 會發出警告,因為 AAC 編碼會將其削波。會報告 LUFS 低於 −18 或高於 −12 的音量(平台會將其標準化到大約 −14)。
--bpm可覆蓋