HyperFrames 是做什么的?
首次使用? 在开始之前先运行环境检查:
python3 /var/minis/skills/hyperframes-cli/scripts/check-env.py这将验证
minis-browser-use、ffmpeg以及所需技能是否已安装。 如果缺少hyperframes-cli技能,请安装它:python3 /var/minis/skills/hyperframes-cli/scripts/install-skill.py \ hyperframes-cli --repo heygen-com/hyperframes --subdir skills
HTML 是视频的唯一真实来源。合成(composition)是一个包含用于时序的 data-* 属性、用于动画的 GSAP 时间轴以及用于外观的 CSS 的 HTML 文件。框架负责处理剪辑可见性、媒体播放和时间轴同步。
方法
在编写 HTML 之前,先从高级别思考:
- 什么 — 观众应该体验到什么?识别叙事弧线、关键时刻和情感节拍。
- 结构 — 有多少个合成,哪些是子合成哪些是内联,哪些轨道承载什么(视频、音频、叠加层、字幕)。
- 时序 — 哪些剪辑驱动持续时间,过渡落在何处,节奏如何。
- 布局 — 先构建最终状态。请参阅下面的“布局前置动画”。
- 动画 — 然后使用下面的规则添加运动。
对于小改动(修复颜色、调整时序、添加一个元素),直接跳到规则部分。
视觉标识门控
按此顺序检查:
- 项目中存在 DESIGN.md? → 读取它。使用其确切的颜色、字体、运动规则和“不要做的事情”。
- 存在 visual-style.md? → 读取它。应用其
style_prompt_full和结构化字段。(注意:visual-style.md是项目特定的文件。visual-styles.md是包含 8 个命名预设的风格库 — 不同的文件。) - 用户命名了一种风格(例如“瑞士脉冲”、“黑暗与科技”、“奢侈品牌”)? → 读取 visual-styles.md 中的 8 个命名预设。生成一个最小的 DESIGN.md,包含:
## 风格提示(一段)、## 颜色(3-5 个带有角色的十六进制值)、## 排版(1-2 个字体系列)、## 不要做的事情(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来完全填充场景。使用内边距将内容向内推 — 永远不要使用position: absolute; top: Npx在内容容器上。绝对定位的内容容器在内容高于剩余空间时会溢出。仅保留position: absolute给装饰元素。 - 使用
gsap.from()添加入场 — 从屏幕外/不可见 动画到 CSS 位置。CSS 位置是真实依据;补间描述到达那里的旅程。 - 使用
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 + 间距处理间距。 */
错误 — 硬编码尺寸和绝对定位:
.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 进入)时,永远不要将所有场景的元素放在一个共享的 flex 容器中。即使每个元素以 opacity: 0 开始,flex 仍然为每个子元素分配高度。如果一个容器中有 5 个场景的内容,容器的总高度将远远超过舞台高度 — 然后 flex justify-content: center 会相对于这个过大的容器居中内容,将可见内容推出屏幕截图裁剪区域。
错误 — 所有场景在一个 flex 容器中:
<div class="scene-content"> <!-- flex 列,height:100% -->
<!-- 场景 A 元素 -->
<div id="title-a" style="opacity:0">标题 A</div>
<div id="body-a" style="opacity:0">正文 A</div>
<!-- 场景 B 元素 — 即使在不可见时也占用 flex 高度 -->
<div id="title-b" style="opacity:0">标题 B</div>
<div id="body-b" style="opacity:0">正文 B</div>
<!-- 场景 C ... -->
</div>
正确 — 每个场景是独立的绝对层:
<!-- 场景 A -->
<div class="scene" id="scene-a">
<div id="title-a" style="opacity:0">标题 A</div>
<div id="body-a" style="opacity:0">正文 A</div>
</div>
<!-- 场景 B — 对场景 A 的高度影响为零 -->
<div class="scene" id="scene-b">
<div id="title-b" style="opacity:0">标题 B</div>
<div id="body-b" style="opacity:0">正文 B</div>
</div>
/* 每个场景独立填充舞台 */
.scene {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 80px 120px;
box-sizing: border-box;
z-index: 2;
gap: 24px;
}
这是顺序场景唯一正确的模式。每个场景容器上的 position: absolute; inset: 0 与“布局前置动画”指南不冲突 — 该指南禁止在硬编码像素偏移上对内容元素(标题、卡片、统计数据)使用绝对定位。通过 inset: 0 填充完整舞台的场景容器是正确的且是必需的。
当元素随时间共享空间时
如果元素 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 =