HyperFrames
HTML 是视频的真相来源。一个合成是一个 HTML 文件,包含用于时序的 data-* 属性、用于动画的 GSAP 时间线,以及用于外观的 CSS。该框架处理剪辑可见性、媒体播放和时间线同步。
方法
发现(仅限探索性请求)
对于用户尚未确定方向的开放式请求(“制作一个产品发布视频”、“为我们品牌创建一些内容”),在选择颜色之前要理解意图:
- 受众 — 谁观看这个?开发者?高管?普通消费者?
- 平台 — 在哪里播放?社交媒体(15秒)、网站英雄区、产品演示、内部使用?
- 优先级 — 什么最重要?动作质量?内容准确性?品牌保真度?速度?
- 变体 — 用户想要选项,还是一个最佳方案?
对于特定请求(“添加一个标题卡片”、“修复场景 3 的时序”),跳过发现。
对于探索性请求,考虑提供 2-3 个有意义的变体——不仅仅是颜色交换,而是不同的节奏、能量级别或结构方法。一个安全/常规,一个雄心勃勃。不要强制要求——这是一个在合适时可用的工具。
步骤 1:设计系统
如果项目中存在 design.md 或 DESIGN.md,请先阅读(检查两种大小写——在 Linux 上是不同的文件)。它是品牌颜色、字体和约束的真相来源。使用其确切值——不要发明颜色或替换字体。任何格式都可以(YAML frontmatter、散文、表格——只需提取值)。
如果它指定了本地找不到的字体(没有包含 .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。在适合视频的规模下使用品牌颜色——而不是在 Web 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来完全填充场景。使用内边距将内容向内推——永远不要对内容容器使用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 填充场景,内边距定位内容 */
.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 等)。
内边距定位内容。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 });
// 动画...
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":"标题","default":"你好"},
{"id":"theme","type":"enum","label":"主题","default":"light","options":[
{"value":"light","label":"浅色"},
{"value":"dark","label":"深色"}
]}
]'
>
<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 报告","theme":"dark"}' --output q4.mp4
# 或者从 JSON 文件
npx hyperframes render --variables-file ./vars.json
子合成的每个实例值: 同样的 getVariables() 在通过 data-composition-src 加载的子合成内部工作。每个宿主元素传递其自己的值:
<div
data-composition-id="card-pro"
data-composition-src="compositions/card.html"
data-variable-values='{"title":"专业版","price":"$29"}'
></div>
<div
data-composition-id="card-enterprise"
data-composition-src="compositions/card.html"
…
[为翻译截断]