跳到主要内容
BLKBLKTECH

资源 / resources

HyperFrames Block Registry Starter Kit

可复制的 HyperFrames 数据驱动视频底座,包含 Scene Schema、Block Contract、Theme Token、Validator、Builder 和 Agent Prompt 模板。

作者 BLKTECH 编辑部更新 2026年7月25日16 分钟难度 实战免费HyperFramesJavaScriptJSON SchemaGSAPMarkdown

这套 Starter Kit 用 Scene JSON、Block Contract、Theme Token、语义验证器和 Composition Builder,把 AI Agent 的输出限制为可验证数据,再生成引用 HyperFrames Block 的确定性视频工程。

使用说明

这套 Starter Kit 配合《HyperFrames Registry 实战》使用,目标是快速建立一个最小的数据驱动视频底座。

它不是完整 npm 包,而是一组可以复制进项目、再按实际 Registry 扩展的文件模板。

建议目录:

project/
  data/
    video.example.json
    audio-map.example.json

  schemas/
    scene.schema.json

  contracts/
    terminal-command.json

  themes/
    theme.css

  scripts/
    validate-scenes.mjs
    build-composition.mjs

  registry/
    blocks/

  compositions/
  assets/
  snapshots/
  output/

使用顺序:

复制模板
→ 修改 Block Contract
→ 让 Agent 输出 Scene JSON
→ 执行 Validator
→ 执行 Builder
→ HyperFrames check
→ Snapshot / Preview
→ 人工确认后 Render

文件一:video.example.json

{
  "schema_version": "1.0",
  "composition": {
    "id": "main",
    "width": 1920,
    "height": 1080,
    "duration": 15,
    "fps": 30
  },
  "scenes": [
    {
      "scene_id": "01-install",
      "block": "terminal-command",
      "block_version": "1.0.0",
      "start": 0,
      "duration": 7,
      "track": 1,
      "theme": "code-editorial",
      "motion": "type-and-confirm",
      "props": {
        "eyebrow": "INSTALL",
        "command": "npm install hyperframes",
        "output": "Installed successfully"
      },
      "audio": {
        "cue": "install",
        "sync": "word"
      }
    },
    {
      "scene_id": "02-render",
      "block": "terminal-command",
      "block_version": "1.0.0",
      "start": 7,
      "duration": 8,
      "track": 1,
      "theme": "terminal-green",
      "motion": "type-and-confirm",
      "props": {
        "eyebrow": "RENDER",
        "command": "npx hyperframes render --output out.mp4",
        "output": "Video written to out.mp4"
      },
      "audio": {
        "cue": "render",
        "sync": "word"
      }
    }
  ]
}

修改规则

  • composition.id 必须与主 Timeline key 一致
  • scene_id 全局唯一
  • block 必须存在于 Registry 或 compositions/
  • block_version 必须匹配 Contract
  • start + duration 不能超出 Composition
  • 同 Track 重叠必须是明确设计
  • Theme 和 Motion 必须来自 Block 支持列表

文件二:scene.schema.json

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.local/schemas/scene.schema.json",
  "title": "HyperFrames Scene",
  "type": "object",
  "required": [
    "scene_id",
    "block",
    "block_version",
    "start",
    "duration",
    "track",
    "theme",
    "motion",
    "props"
  ],
  "properties": {
    "scene_id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*$"
    },
    "block": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*$"
    },
    "block_version": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
    },
    "start": {
      "type": "number",
      "minimum": 0
    },
    "duration": {
      "type": "number",
      "exclusiveMinimum": 0,
      "maximum": 120
    },
    "track": {
      "type": "integer",
      "minimum": 0,
      "maximum": 50
    },
    "theme": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*$"
    },
    "motion": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*$"
    },
    "props": {
      "type": "object"
    },
    "audio": {
      "type": "object",
      "required": ["cue", "sync"],
      "properties": {
        "cue": {
          "type": "string"
        },
        "sync": {
          "enum": ["scene", "sentence", "word"]
        }
      },
      "additionalProperties": false
    },
    "allow_overlap": {
      "type": "boolean",
      "default": false
    }
  },
  "additionalProperties": false
}

这份是通用 Scene Schema。每个 Block 的具体 Props 应再用独立 Contract 或 Props Schema 验证。

文件三:Block Contract 模板

文件:

contracts/{{block-name}}.json
{
  "name": "{{block-name}}",
  "version": "1.0.0",
  "type": "hyperframes:block",
  "description": "{{一句话说明 Block 的用途}}",
  "dimensions": {
    "width": 1920,
    "height": 1080
  },
  "duration": {
    "min": 3,
    "default": 6,
    "max": 15
  },
  "themes": [
    "code-editorial",
    "terminal-green"
  ],
  "motion_presets": [
    "default"
  ],
  "props": {
    "title": {
      "type": "string",
      "required": true,
      "maxLength": 60
    },
    "subtitle": {
      "type": "string",
      "required": false,
      "maxLength": 120,
      "default": ""
    }
  },
  "media": {
    "images": 0,
    "videos": 0,
    "audio": 0
  },
  "checkpoints": [
    0.1,
    0.35,
    0.7,
    0.95
  ]
}

Contract 应该回答的问题

  • Block 叫什么
  • 当前版本是什么
  • 支持哪个画幅
  • 最短和最长时长
  • 支持哪些 Props
  • 文字长度限制
  • 支持哪些 Theme
  • 支持哪些 Motion Preset
  • 需要哪些媒体
  • Snapshot 应检查哪些相对时间点

文件四:registry-item.json 模板

Block Registry 目录:

registry/blocks/{{block-name}}/
  {{block-name}}.html
  registry-item.json

registry-item.json

{
  "$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
  "name": "{{block-name}}",
  "type": "hyperframes:block",
  "title": "{{Human Readable Title}}",
  "description": "{{One sentence description}}",
  "dimensions": {
    "width": 1920,
    "height": 1080
  },
  "duration": 8,
  "tags": [
    "{{category}}",
    "{{use-case}}"
  ],
  "files": [
    {
      "path": "{{block-name}}.html",
      "target": "compositions/{{block-name}}.html",
      "type": "hyperframes:composition"
    }
  ]
}

Component 模板没有固定尺寸和时长:

{
  "$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
  "name": "{{component-name}}",
  "type": "hyperframes:component",
  "title": "{{Human Readable Title}}",
  "description": "{{One sentence description}}",
  "tags": [
    "effect",
    "overlay"
  ],
  "files": [
    {
      "path": "{{component-name}}.html",
      "target": "compositions/components/{{component-name}}.html",
      "type": "hyperframes:snippet"
    }
  ]
}

文件五:theme.css

:root {
  --hf-bg: #12131c;
  --hf-surface: #1a1c29;
  --hf-surface-raised: #232635;
  --hf-foreground: #f6f7fb;
  --hf-muted: #9296a8;
  --hf-accent: #ff6b4a;
  --hf-success: #48f7b2;
  --hf-danger: #ff5f57;

  --hf-font-main: "Inter", system-ui, sans-serif;
  --hf-font-display: "Inter Tight", "Inter", sans-serif;
  --hf-font-code: "Fira Code", ui-monospace, monospace;

  --hf-text-hero: 124px;
  --hf-text-title: 88px;
  --hf-text-body: 42px;
  --hf-text-caption: 30px;

  --hf-radius-sm: 12px;
  --hf-radius-md: 20px;
  --hf-radius-lg: 32px;

  --hf-safe-margin: 96px;
  --hf-gap-sm: 20px;
  --hf-gap-md: 40px;
  --hf-gap-lg: 72px;

  --hf-motion-fast: 0.35s;
  --hf-motion-normal: 0.65s;
  --hf-motion-slow: 1.1s;
}

[data-theme="terminal-green"] {
  --hf-bg: #07110f;
  --hf-surface: #10221c;
  --hf-surface-raised: #163128;
  --hf-foreground: #f4fff9;
  --hf-muted: #86a095;
  --hf-accent: #48f7b2;
}

[data-theme="swiss-light"] {
  --hf-bg: #f2f0e9;
  --hf-surface: #ffffff;
  --hf-surface-raised: #e8e5dc;
  --hf-foreground: #111111;
  --hf-muted: #656565;
  --hf-accent: #173cff;
}

Theme 使用规则

  • Block 内部优先使用 var(--hf-*)
  • Scene 只选择 Theme 名称
  • 不让 Agent 直接传任意颜色值
  • 新 Theme 必须经过对比度和 Snapshot 检查
  • 字体文件应该保存到项目资产目录
  • 如果 Block 不支持某 Theme,Validator 应该失败

文件六:audio-map.example.json

{
  "version": "1.0",
  "duration": 15,
  "cues": {
    "install": {
      "start": 0.6,
      "duration": 4.8,
      "events": {
        "command": {
          "start": 1,
          "duration": 1.8
        },
        "result": {
          "start": 3.4,
          "duration": 0.7
        }
      }
    },
    "render": {
      "start": 7.4,
      "duration": 5.2,
      "events": {
        "command": {
          "start": 7.9,
          "duration": 2.1
        },
        "result": {
          "start": 11.2,
          "duration": 0.8
        }
      }
    }
  }
}

Audio Map 规则

  • 使用一个统一格式,不让每个 Block 直接适配不同 TTS 输出
  • 明确绝对时间还是 Scene 相对时间
  • Builder 负责时间换算
  • Cue 必须位于 Scene 时间窗口内
  • 不把毫秒时间直接等同于最终画面精度
  • 最终视觉事件仍然要映射到视频帧

文件七:validate-scenes.mjs 骨架

import fs from "node:fs";
import path from "node:path";
import Ajv from "ajv";

const root = process.cwd();

function readJson(relativePath) {
  return JSON.parse(
    fs.readFileSync(path.join(root, relativePath), "utf8")
  );
}

function fileExists(relativePath) {
  return fs.existsSync(path.join(root, relativePath));
}

const video = readJson("data/video.json");
const sceneSchema = readJson("schemas/scene.schema.json");
const audioMap = fileExists("data/audio-map.json")
  ? readJson("data/audio-map.json")
  : null;

const ajv = new Ajv({ allErrors: true, strict: true });
const validateScene = ajv.compile(sceneSchema);

const errors = [];
const sceneIds = new Set();

for (const scene of video.scenes) {
  if (!validateScene(scene)) {
    errors.push({
      type: "schema",
      scene: scene.scene_id,
      details: validateScene.errors
    });
    continue;
  }

  const contractPath = `contracts/${scene.block}.json`;

  if (!fileExists(contractPath)) {
    errors.push({
      type: "missing-contract",
      scene: scene.scene_id,
      block: scene.block
    });
    continue;
  }

  const contract = readJson(contractPath);

  if (sceneIds.has(scene.scene_id)) {
    errors.push({
      type: "duplicate-scene-id",
      scene: scene.scene_id
    });
  }

  sceneIds.add(scene.scene_id);

  if (scene.block_version !== contract.version) {
    errors.push({
      type: "block-version-mismatch",
      scene: scene.scene_id,
      expected: contract.version,
      actual: scene.block_version
    });
  }

  if (!contract.themes.includes(scene.theme)) {
    errors.push({
      type: "unsupported-theme",
      scene: scene.scene_id,
      theme: scene.theme
    });
  }

  if (!contract.motion_presets.includes(scene.motion)) {
    errors.push({
      type: "unsupported-motion",
      scene: scene.scene_id,
      motion: scene.motion
    });
  }

  if (
    scene.duration < contract.duration.min ||
    scene.duration > contract.duration.max
  ) {
    errors.push({
      type: "duration-out-of-range",
      scene: scene.scene_id
    });
  }

  if (scene.start + scene.duration > video.composition.duration) {
    errors.push({
      type: "scene-exceeds-composition",
      scene: scene.scene_id
    });
  }

  if (
    scene.audio?.cue &&
    (!audioMap || !audioMap.cues[scene.audio.cue])
  ) {
    errors.push({
      type: "missing-audio-cue",
      scene: scene.scene_id,
      cue: scene.audio.cue
    });
  }
}

for (let i = 0; i < video.scenes.length; i += 1) {
  for (let j = i + 1; j < video.scenes.length; j += 1) {
    const a = video.scenes[i];
    const b = video.scenes[j];

    if (a.track !== b.track) continue;

    const overlaps =
      a.start < b.start + b.duration &&
      b.start < a.start + a.duration;

    if (overlaps && !a.allow_overlap && !b.allow_overlap) {
      errors.push({
        type: "same-track-overlap",
        scenes: [a.scene_id, b.scene_id]
      });
    }
  }
}

if (errors.length > 0) {
  console.error(JSON.stringify({ ok: false, errors }, null, 2));
  process.exit(1);
}

console.log(JSON.stringify({
  ok: true,
  scenes: video.scenes.length,
  duration: video.composition.duration
}, null, 2));

文件八:build-composition.mjs 骨架

import fs from "node:fs";
import path from "node:path";

const root = process.cwd();
const video = JSON.parse(
  fs.readFileSync(path.join(root, "data/video.json"), "utf8")
);

function escapeAttribute(value) {
  return String(value)
    .replaceAll("&", "&amp;")
    .replaceAll("'", "&#39;")
    .replaceAll('"', "&quot;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;");
}

function renderScene(scene) {
  const variables = {
    ...scene.props,
    theme: scene.theme,
    motion: scene.motion
  };

  return `
    <div
      data-composition-id="${escapeAttribute(scene.block)}"
      data-composition-src="./${escapeAttribute(scene.block)}.html"
      data-start="${scene.start}"
      data-duration="${scene.duration}"
      data-track-index="${scene.track}"
      data-width="${video.composition.width}"
      data-height="${video.composition.height}"
      data-variable-values='${escapeAttribute(JSON.stringify(variables))}'
    ></div>`;
}

const html = `<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=${video.composition.width}, height=${video.composition.height}"
    />
    <title>Generated HyperFrames Composition</title>
    <style>
      html, body {
        margin: 0;
        width: ${video.composition.width}px;
        height: ${video.composition.height}px;
        overflow: hidden;
      }

      #root {
        position: relative;
        width: ${video.composition.width}px;
        height: ${video.composition.height}px;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <div
      id="root"
      data-composition-id="${escapeAttribute(video.composition.id)}"
      data-start="0"
      data-width="${video.composition.width}"
      data-height="${video.composition.height}"
      data-duration="${video.composition.duration}"
    >
${video.scenes.map(renderScene).join("\n")}
    </div>

    <script src="./vendor/gsap.min.js"></script>
    <script>
      window.__timelines = window.__timelines || {};
      window.__timelines["${escapeAttribute(video.composition.id)}"] =
        gsap.timeline({ paused: true });
    </script>
  </body>
</html>`;

const output = path.join(root, "compositions/index.html");
fs.mkdirSync(path.dirname(output), { recursive: true });
fs.writeFileSync(output, html);

console.log(JSON.stringify({
  ok: true,
  output: "compositions/index.html",
  scenes: video.scenes.length
}, null, 2));

文件九:package.json scripts

{
  "scripts": {
    "video:validate": "node scripts/validate-scenes.mjs",
    "video:build": "node scripts/build-composition.mjs",
    "video:check": "npm run video:validate && npm run video:build && hyperframes check --strict-variables",
    "video:preview": "hyperframes preview",
    "video:snapshot": "hyperframes snapshot --at 0.5,3.5,6.5,8,11,14.5",
    "video:render": "hyperframes render --quality high --output output/video.mp4"
  }
}

日常使用:

npm run video:check
npm run video:snapshot
npm run video:preview

# 最终确认后
npm run video:render

如果项目脚手架已经固定了 HyperFrames CLI 版本,应沿用项目现有命令,不要为了复制模板而取消版本固定。

文件十:AI Layout Driver Prompt

你是当前项目的 HyperFrames Layout Driver。

你的主要输出是符合 Schema 的 Scene JSON,不是任意 HTML、CSS 或 JavaScript。

开始前必须读取:
- BRIEF.md
- STORYBOARD.md
- frame.md
- data/video.json
- schemas/scene.schema.json
- contracts/*.json
- 当前 Registry Catalog

执行规则:
1. 为每个 Scene 选择已经注册且版本匹配的 Block。
2. Theme 和 Motion 只能使用 Block Contract 声明的枚举值。
3. Props 必须满足长度、类型和必填要求。
4. 不直接输出颜色值、任意 CSS 或远程媒体 URL。
5. 使用 Audio Map 中的 Cue 对齐场景和动作时间。
6. 如果 Registry 没有合适能力,先报告缺失 Block,不要重写整个项目。
7. 修改后运行 video:validate 和 video:build。
8. 然后执行 HyperFrames check 和关键帧 Snapshot。
9. 最终 Preview 未确认前,不执行高质量 Render。

输出格式:
- 修改后的 Scene JSON
- 使用的 Block、版本、Theme 和 Motion
- Validator 结果
- Check 结果
- Snapshot 时间点
- 仍需人工确认的问题

Block Authoring Checklist

Registry

  • Block 名使用稳定 kebab-case
  • registry-item.json 字段完整
  • Registry 类型是 hyperframes:block
  • 目标路径正确
  • 标签能表达用途
  • 有清晰版本号

Composition

  • Sub-composition 根位于 <template>
  • 样式和脚本也位于模板内
  • 根有 Composition ID、宽、高、起点和时长
  • Timeline key 与 Composition ID 完全一致
  • 使用一个同步创建的 paused Timeline
  • 可见片段具有正确的时间属性
  • 背景由全画幅子元素承载

ID

  • 所有 ID 使用 2~3 字母 Block 前缀
  • 组装多个实例后没有重复 ID
  • 媒体元素 ID 唯一

Variables

  • 变量有类型、标签和默认值
  • Enum 变量有 options
  • 文本有 maxLength
  • 直接文本和图片优先使用声明式绑定
  • 复杂变量只在初始化时读取一次
  • Preview 在没有覆盖变量时仍然正常

Determinism

  • 没有 Math.random()
  • 没有 Date.now()performance.now()
  • 没有无限 repeat: -1
  • 没有关键运行时网络请求
  • Timeline 不在异步回调中创建
  • 媒体播放交给 HyperFrames
  • 相同时间点重复 Snapshot 结果稳定

Visual QA

  • Block 有明确安全边距
  • 标题达到视频可读字号
  • 长文本有明确长度上限
  • Theme 对比度通过检查
  • 高潮后有停留时间
  • 最短、默认和最长时长都检查过
  • 所有支持 Theme 都生成过 Snapshot

Registry 上线前检查

npx hyperframes lint
npx hyperframes check
npx hyperframes snapshot --at {{时间点}}
npx hyperframes preview

如果 Block 需要贡献到共享 Registry,还应该:

  • 格式化 HTML
  • 更新 Registry Manifest
  • 生成目录预览图或视频
  • 验证 Registry 文件
  • 提交清晰的变更说明

推荐的第一批 Block

Block 主要 Props 推荐 Motion
title-card eyebrow、title、subtitle title-rise-reveal
terminal-command command、output type-and-confirm
process-flow nodes、result diagram-build
big-number value、label、source number-count-up
cta logo、message、action logo-resolve

不要一次建立几十个 Block。先让第一批 Block 经历真实视频项目,再根据使用频率和失败记录扩展。

推荐的成熟度路线

第 1 周
建立 Title、Terminal、CTA 三个 Block

第 2 周
加入 Scene Schema、Validator 和 Builder

第 3 周
加入 Theme 与 Motion Recipe

第 4 周
加入 Audio Map 和真实项目 Benchmark

后续
按真实需求扩展 Block,而不是按想象建立组件库

配套内容

NEXT ACTION / 下一步

查看完整 Registry 实战教程

把读到的方法变成一个小行动,完成后再回来迭代。

继续

RELATED / 相关推荐

接着读这些

按同一栏目、标签与技术栈为你挑选。