HyperFrames
HTML 是视频的唯一事实来源。一个合成是一个带有 data-* 属性的 HTML 文件,用于控制时间、一个 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、包含 GSAP CDN 和已注册的 window.__timelines.main 的 index.html)。
# 这在你的 shell 中运行——纯文件复制,
# 无需 Chrome,除了 npx 缓存外无需网络。
npx hyperframes init "$COMP" --example blank --skip-skills --non-interactive
# 3. 仅编辑 $COMP/index.html——如果你需要非默认长度,更改根元素的 `data-duration`,
# 在 <style> 中替换占位调色板,为文本/图像添加 1-3 个片段 <div>,
# 并在现有的 `window.__timelines["main"] = gsap.timeline({paused:true})` 块内
# 追加匹配的 GSAP 补间。保持编辑最小化;脚手架已经是有效的 HF。
# 4. 通过 OD 守护进程分派渲染。不要在此 shell 中运行 `npx hyperframes
# render`——守护进程会在一个无沙箱的进程中为你运行它。(许多代理 CLI,特别是 Claude Code,
# 在 macOS sandbox-exec 中包装 Bash,在此环境下 puppeteer 的 Chrome
# 子进程会在帧捕获过程中挂起。守护进程
# 进程是无沙箱的,因此渲染可以可靠完成。)
#
# 分派器在 ~1s 内返回一个 {taskId};通过循环调用 `"$OD_NODE_BIN" "$OD_BIN" media wait <taskId>`
# 来驱动渲染完成。
# 每次调用最长轮询 25s(远低于你的 shell 工具的
# 默认 30s 限制),并以退出码 0/2/5 表示完成/运行中/失败。
out=$("$OD_NODE_BIN" "$OD_BIN" media generate \
--project "$OD_PROJECT_ID" \
--surface video \
--model hyperframes-html \
--output "<描述性名称>.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 + 编辑是默认路径。
你可以仍然从你自己的 shell 运行较轻量的 HF 子命令(它们不需要启动 Chrome):
npx hyperframes lint "$COMP"— 在分派前验证合成npx hyperframes transcribe <audio>— 生成字幕npx hyperframes tts <text>— 生成旁白
将守护进程分派保留给 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 之前,从高层次思考:
- 什么——观看者应该体验什么?确定叙事弧线、关键时刻和情感节拍。
- 结构——有多少个合成,哪些是子合成与内联合成,哪些轨道承载什么(视频、音频、叠加层、字幕)。
- 时序——哪些片段驱动持续时间,过渡落在哪里,节奏是什么。
- 布局——首先构建最终状态。请参阅下面的“动画前布局”。
- 动画——然后使用下面的规则添加运动。
对于小编辑(修复颜色、调整时序、添加一个元素),直接跳到规则。
视觉标识门
按此顺序检查:
- 项目中存在 DESIGN.md? → 阅读它。使用其确切的颜色、字体、运动规则和“不要做的事”约束。
- 存在 visual-style.md? → 阅读它。应用其
style_prompt_full和结构化字段。(注意:visual-style.md是一个项目特定的文件。visual-styles.md是包含 8 个命名预设的样式库——不同的文件。) - 用户指定了样式(例如,“Swiss Pulse”、“暗黑科技风”、“奢侈品牌”)?→ 阅读 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/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-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-