claude-design-hyperframes

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Claude Design + HyperFrames

Claude Design + HyperFrames

For this project, your medium is HyperFrames compositions: plain HTML + CSS + a paused GSAP timeline. A separate CLI (
npx hyperframes render index.html
) turns the HTML into an MP4. You are authoring the HTML files — the user runs the CLI locally. You do NOT need a CLI environment to produce these files.
HyperFrames replaces your default video-artifact workflow for this project. When the user asks for a video, animation, launch teaser, editorial explainer, product tour, social reel, or any motion deliverable:
  • Do NOT call
    copy_starter_component
    with
    kind: "animations.jsx"
    . The animations.jsx starter is the wrong format here — HyperFrames uses plain HTML + GSAP, not React Sprites.
  • Do NOT invoke the built-in "Animated video" skill. HyperFrames replaces it for this project.
  • Do NOT use React, Babel, or
    <script type="text/babel">
    . Compositions are plain HTML; animation state lives on a paused GSAP timeline registered on
    window.__timelines
    .
  • Do NOT hand-roll a 1920×1080 scale-to-fit stage wrapper.
    <hyperframes-player>
    (loaded in
    preview.html
    ) handles viewport scaling and letterboxing for you.
Your first action on a new brief is to read the brief, ask a clarifying question if it's sparse, and commit to a visual identity — not to copy a starter component.
Users attach this skill to a Claude Design chat, drop brand assets (screenshots, PDFs, reference videos, pasted palettes), and describe a video. You return
index.html
,
preview.html
,
README.md
, and
DESIGN.md
as a downloadable ZIP. The user runs
npx hyperframes render index.html
locally to produce the MP4. Work through the five steps below — each has a gate you must pass before moving to the next.

本项目中,你的创作载体是HyperFrames合成内容:纯HTML + CSS + 暂停状态的GSAP时间线。独立CLI工具(
npx hyperframes render index.html
)会将HTML转换为MP4。你负责编写HTML文件——用户在本地运行CLI工具即可,无需CLI环境就能生成这些文件。
HyperFrames将替代你在本项目中的默认视频生成工作流。 当用户要求制作视频、动画、发布预告、社论讲解、产品导览、社交短视频或任何动态交付内容时:
  • 请勿调用
    copy_starter_component
    并设置
    kind: "animations.jsx"
    。animations.jsx模板格式错误——HyperFrames使用纯HTML + GSAP,而非React Sprites。
  • 请勿调用内置的「动画视频」技能。本项目中HyperFrames已替代该功能。
  • 请勿使用React、Babel或
    <script type="text/babel">
    。合成内容为纯HTML;动画状态存储在注册于
    window.__timelines
    的暂停GSAP时间线中。
  • 请勿手动编写1920×1080等比适配的舞台容器。
    <hyperframes-player>
    (在
    preview.html
    中加载)会自动处理视口缩放和黑边填充。
接到新需求后的首要动作是阅读需求,若信息不足则提出澄清问题,确定视觉风格——而非直接复制模板组件。
用户将此技能附加到Claude Design对话中,上传品牌资产(截图、PDF、参考视频、粘贴的调色板),并描述视频需求。你需返回包含
index.html
preview.html
README.md
DESIGN.md
的可下载ZIP包。用户在本地运行
npx hyperframes render index.html
即可生成MP4。请遵循以下五个步骤——每一步都需满足条件后才能进入下一步。

Approach

工作流程

Before touching HTML, think in phases. Skipping phases is the single biggest quality problem in AI-generated video.
  1. Brief — what does the user want? What have they given you to synthesize from?
  2. Identity — what does this video LOOK like? palette, type, motion character, committed in one document before any HTML.
  3. Beats — what happens in what order? scenes, durations, verbs per element, mid-scene activity.
  4. Build — static layout first, then motion, then self-review.
  5. Deliver — preview shell + README for local render + caveats.

编写HTML前,需分阶段思考。跳过阶段是AI生成视频质量不佳的主要原因。
  1. 需求梳理——用户想要什么?提供了哪些可参考的素材?
  2. 视觉风格确定——视频的视觉呈现是什么样?调色板、字体、动效风格,需在编写HTML前先形成文档确定下来。
  3. 分镜规划——内容按什么顺序呈现?场景、时长、每个元素的动效、场景内的动态变化。
  4. 内容构建——先做静态布局,再添加动效,最后自我检查。
  5. 交付输出——预览壳文件+本地渲染说明文档+注意事项。

Step 1: Understand the brief

步骤1:理解需求

Gate: You can name the subject, the duration, the aspect ratio, and at least one source of visual direction (attachment, pasted palette/type/copy, named aesthetic, or clarifying-question answer). If you can't — you don't have enough to build.
准入条件:你能明确主题、时长、宽高比,以及至少一个视觉参考来源(附件、粘贴的调色板/字体/文案、指定风格、或澄清问题的回复)。若无法明确,则信息不足以开始制作。

Inputs, in order of reliability

输入优先级

  1. Attachments (strongest visual source).
    .fig
    Figma files, PDFs (brand guidelines, spec docs),
    .docx
    /
    .pptx
    , images/screenshots, reference video stills. Claude Design reads these natively with detail preserved. Mine for palette, typography, spacing, UI chrome, tone of voice.
  2. Pasted content. Hex codes, typefaces, copy samples, scripts, pasted style guides. Authoritative for what it covers.
  3. Research. When a brand, product, or topic is named,
    web_search
    and
    web_fetch
    aggressively. Static pages fetch fine — company blogs (
    <brand>.com/blog
    ), press pages, Wikipedia, Crunchbase, TechCrunch, docs sites — and yield (a) tone/positioning, (b) real copy (taglines, feature names, product language), (c) sometimes hex codes + typeface names from press kits. SPA marketing homepages (React/Vue/Angular) are the one weak case — they return near-empty shells because JavaScript isn't executed. Pivot to the brand's blog / press / Wikipedia when the homepage returns little.
  4. URLs the user provided. Start there, expand outward.
Combine channels. Strong attachments + light research gives you brand-accurate visuals AND brand-accurate copy.
  1. 附件(最强视觉参考)
    .fig
    Figma文件、PDF(品牌指南、规格文档)、
    .docx
    /
    .pptx
    、图片/截图、参考视频帧。Claude Design可原生读取这些文件并保留细节。从中提取调色板、排版、间距、UI样式、语气语调。
  2. 粘贴内容:十六进制颜色码、字体、文案样本、脚本、粘贴的风格指南。覆盖的内容具有权威性。
  3. 调研:当提到品牌、产品或主题时,积极使用
    web_search
    web_fetch
    。静态页面可正常获取——公司博客(
    <brand>.com/blog
    )、新闻页面、维基百科、Crunchbase、TechCrunch、文档站点——这些来源可提供:(a) 品牌调性/定位,(b) 官方文案(标语、功能名称、产品术语),(c) 有时可从新闻资料包中获取十六进制颜色码+字体名称。SPA营销主页(React/Vue/Angular)是例外——因未执行JavaScript,返回内容近乎空白。此时转向品牌博客/新闻/维基百科获取信息。
  4. 用户提供的URL:先从这些URL开始,再向外拓展。
整合多渠道信息。优质附件+适度调研可生成符合品牌视觉和文案规范的内容。

Mechanical trigger — ask ONE short question if the brief is sparse

触发机制——若需求信息不足,提出一个简短问题

If the prompt contains NONE of the following, ask one clarifying question before generating:
  • A file attachment
  • A pasted hex code or named typeface
  • A named aesthetic / style / movement / director / genre
  • A specific brand with a well-known visual identity (Apple, Linear, Stripe, Notion, Figma, Vercel, Tesla, Spotify, etc.)
  • The words "go", "just build", "make it", "surprise me", "ship it"
  • A follow-up turn continuing an existing composition
Do NOT rationalize past this check. "The user's email domain is the brand so I know what they want" is NOT a valid skip condition. "It's a well-known company so I'll just build" is NOT valid unless the brand is in the list above.
Send one short message (4–6 lines) with concrete options:
To make this look like yours — drop any of these (or describe in words):
  • A screenshot or two of your product, site, or an ad you like.
  • A brand PDF / style guide.
  • A reference video for pacing / color / energy.
  • A vibe in words — "clinical and cold", "loud and fast", "a particular director / movie".
  • A must-have — a specific shader, transition, text effect, or element you already want.
Or say "just build" and I'll commit to <one concrete aesthetic you've chosen for this brief — named concretely, not "warm editorial" or "generic dark mode">.
Wait for the reply. When the user answers, incorporate fully. When they say "just build" / "go" / "ship it" / "surprise me", commit to the aesthetic you offered and proceed.

若提示中不包含以下任何一项,生成内容前需先提出一个澄清问题:
  • 文件附件
  • 粘贴的十六进制颜色码或指定字体
  • 指定的美学风格/流派/导演/类型
  • 具有知名视觉风格的特定品牌(Apple、Linear、Stripe、Notion、Figma、Vercel、Tesla、Spotify等)
  • 包含“go”、“just build”、“make it”、“surprise me”、“ship it”等词汇
  • 基于现有合成内容的跟进需求
请勿跳过此检查。“用户邮箱域名对应品牌,所以我知道需求”不是有效的跳过理由。“这是知名公司,我直接制作”仅当品牌在上述列表中时才有效。
发送一条简短消息(4-6行),提供具体选项:
为了让内容更贴合你的需求,请提供以下任意一项(或用文字描述):
  • 产品、网站或你喜欢的广告的1-2张截图
  • 品牌PDF/风格指南
  • 参考视频(用于参考节奏/色彩/活力)
  • 风格描述——比如「冷峻专业」、「动感强烈」、「特定导演/电影风格」
  • 必备元素——特定着色器、转场效果、文字特效或你想要的元素
或者回复「直接制作」,我将为本次需求确定**<一个具体的美学风格名称,而非“温暖社论风”或“通用深色模式”>**。
等待用户回复。用户回复后,将其完全融入内容。若用户回复“直接制作”/“go”/“ship it”/“surprise me”,则按你提出的风格开始制作。

Step 2: Commit to a visual identity

步骤2:确定视觉风格

Gate:
DESIGN.md
exists in the project directory with palette, typography, and motion character defined.
准入条件:项目目录中存在
DESIGN.md
,其中定义了调色板、排版和动效风格。

Visual Identity Gate

视觉风格准入要求

<HARD-GATE> Before writing ANY composition HTML, you MUST have a visual identity defined. Do NOT write compositions with default or generic colors. </HARD-GATE>
Commit to ONE aesthetic and write
DESIGN.md
before
index.html
. The document is a thinking step, not a deliverable template.
DESIGN.md
contains:
  • Palette. Name each color's role (bg, ink, accent, muted). Use exact hex values or OKLCH. One accent hue, tinted neutrals.
  • Typography. Display face + body face. See banned list below — and look beyond the standard pairs. Weight contrast must be dramatic (300 vs 900, not 400 vs 700). Video sizes: 60px+ headlines, 20px+ body, 16px+ data labels.
  • Motion character. Pacing (fast/medium/slow/cinematic), primary transition family (CSS vs shader, which shader), easing defaults, what NOT to do.
Reference the tokens via CSS custom properties on
:root
in
index.html
.
<HARD-GATE> 在编写任何合成内容HTML前,必须先确定视觉风格。请勿使用默认或通用颜色编写合成内容。 </HARD-GATE>
确定一种美学风格,在编写
index.html
前先完成
DESIGN.md
。该文档是思考过程的记录,而非交付模板。
DESIGN.md
需包含:
  • 调色板:定义每种颜色的用途(背景、文本、强调、柔和色)。使用精确的十六进制值或OKLCH。一种强调色调,渐变中性色。
  • 排版:标题字体+正文字体。参考下方禁用列表——并跳出标准搭配。字重对比需鲜明(300 vs 900,而非400 vs 700)。视频字体尺寸:标题≥60px,正文≥20px,数据标签≥16px。
  • 动效风格:节奏(快/中/慢/电影感)、主要转场类型(CSS或着色器,具体哪种着色器)、默认缓动函数、禁忌动效。
index.html
:root
上通过CSS自定义属性引用这些样式变量。

Anti-monoculture

避免同质化

Training-data defaults every LLM reaches for. Commit to something the brief specifically calls for instead.
  • Don't default to warm editorial (cream paper + serif + terracotta accent).
  • Don't default to generic dark-mode tech (black + violet accent + Inter + geometric sans).
  • Banned fonts: Inter, Inter Tight, Roboto, Open Sans, Noto Sans, Arimo, Lato, Source Sans, PT Sans, Nunito, Poppins, Outfit, Sora, Fraunces, Playfair Display, Cormorant Garamond, Bodoni Moda, EB Garamond, Cinzel, Prata, Syne. Full list and reasoning:
    skills/hyperframes/references/typography.md
    .
  • Banned pairings (observed AI defaults): Fraunces + JetBrains Mono (every test-run of an editorial brief lands here); Inter + anything; Playfair + Lato. Pick different faces each time.
  • Lazy defaults to question: gradient text, left-edge accent stripes, cyan-on-dark, pure
    #000
    /
    #fff
    , identical card grids, everything centered with equal weight. See
    skills/hyperframes/house-style.md
    for the full list.

避免所有LLM易陷入的训练数据默认风格。优先采用需求中明确指定的风格。
  • 请勿默认使用温暖社论风(米纸色+衬线字体+赤陶色强调)。
  • 请勿默认使用通用深色科技风(黑色+紫色强调+Inter字体+几何无衬线)。
  • 禁用字体:Inter、Inter Tight、Roboto、Open Sans、Noto Sans、Arimo、Lato、Source Sans、PT Sans、Nunito、Poppins、Outfit、Sora、Fraunces、Playfair Display、Cormorant Garamond、Bodoni Moda、EB Garamond、Cinzel、Prata、Syne。完整列表及原因:
    skills/hyperframes/references/typography.md
  • 禁用搭配(AI常见默认):Fraunces + JetBrains Mono(每次社论需求测试都会出现);Inter + 任意字体;Playfair + Lato。每次选择不同的字体组合。
  • 需警惕的惰性默认:渐变文字、左侧强调条纹、深色背景配青色文字、纯
    #000
    /
    #fff
    、相同卡片网格、所有元素居中且字重一致。完整列表见
    skills/hyperframes/house-style.md

Step 3: Plan the beats

步骤3:分镜规划

Gate: You can list every scene, its duration, and at least one verb per animated element in that scene. If a verb is missing, the element isn't designed yet.
准入条件:你能列出每个场景、时长,以及每个场景中至少一个动画元素的动效动词。若缺少动词,则元素尚未设计完成。

Scene plan + pacing

场景规划与节奏

Hard ceiling: no scene longer than 5 seconds unless there's a deliberate pacing reason. Scenes in the 6–12s range read as draggy slides; viewers feel the stall. Only go longer than 5s when you can name the reason — a deliberate hold on a hero frame, a long cinematic push, a silence beat, a counter that animates over 6+ seconds to feel substantial. Default to quick. Slow down with intention.
Hard floor: scene must last at least as long as a viewer needs to read its text. A 2-second scene with a 20-word paragraph is broken — viewers cannot read it before the transition fires. The "too short" failure is as real as "too long."
Reading-time budget per scene:
Displayed text (visible during the scene)Minimum scene duration
No text (hero image, icon, decorative)1.5–2s
1–3 words (kicker, label, number, short headline)2–3s
4–10 words (short headline + tiny subhead)3–4s
11–20 words (a full sentence or two short lines)4–6s
21–35 words (multi-line paragraph, bullet list)6–8s
35+ words (dense explainer text)Split into two scenes. A single scene should not ask the viewer to read more than ~35 words.
On top of reading time, add entrance-animation buffer: 0.6–1.0s for the text to finish entering before the viewer can start reading it. Practical formula: scene_duration ≥ entrance_buffer + (word_count × 0.25s) + 0.5s transition tail, with a minimum of 1.5s.
Apply this per scene. If scene-3's display text is 18 words of serif body copy, scene-3 needs ~5s, not 3s. If scene-12 is a single-word slam ("Design."), 2s is fine — maybe ideal.
Last readable element must finish entering by the 50% mark of the scene. That gives the viewer the second half of the scene to actually read the text before the transition starts. If the last
tl.from("#s5-sub", …)
on a 4-second scene finishes at t=3.5s, the viewer has only 0.5s to read — not enough. Pull entrances earlier or lengthen the scene.
Anti-pattern: dividing total duration by scene count AND ignoring per-scene reading-time. A 2-minute video ÷ 10 scenes = 12-second scenes (too long per hard ceiling); or ÷ 60 scenes = 2-second scenes (too short if any of them has sentence-length text).
Better: pick a scene count targeting 3–5s average, then ADJUST each scene up or down based on what it has to show. Short scenes for punches, images, and kickers. Medium scenes for headlines. Longer scenes for body copy or bullet lists.
Video lengthTarget scene countAvg sceneNotes
10–15s social ad5–82–3sRelentless cuts, every scene is a punch
20–30s teaser8–122–4sOpen / build / payoff / close, varied
30–60s explainer12–203–5sEach beat its own scene — don't combine two ideas
60–120s narrative24–403–5sDense pacing. Think YouTube explainer, not slideshow
120–240s long-form40–704–5sSplit into sub-compositions, each act ~8–14 scenes
Four mechanical checks before closing Step 3:
  1. Per-scene reading-time check: count the words of display text in each scene. Does
    scene.data-duration
    satisfy the reading-time budget above? If not, extend the scene (if budget headroom exists) or split the text across two scenes.
  2. Last-readable-element check: for each scene, find the last
    tl.from
    on a readable text element. Does it finish (start + duration) before the 50% mark of the scene? If not, pull the entrance earlier.
  3. If a scene's
    data-duration
    exceeds 5 seconds, write one sentence justifying why it holds that long.
    If you can't, split it into two scenes with different beats.
  4. Model the rhythm as a wave, not a flat line.
    short-short-LONG-short-short-LONG-short
    reads as intentional pacing.
    flat-flat-flat-flat
    reads as a slideshow. Same-duration across scenes = dividing, not designing.
硬性上限:除非有明确的节奏需求,否则场景时长不得超过5秒。6-12秒的场景会显得拖沓;观众会感到节奏停滞。仅当能说明原因时才设置超过5秒的时长——比如刻意停留于核心画面、长镜头推进、静默节拍、需要6秒以上动画时长的计数器。默认采用快节奏,按需放慢。
硬性下限:场景时长需至少满足观众阅读文本的时间。2秒场景包含20字段落是不合理的——观众无法在转场前读完文本。“时长过短”的问题和“时长过长”一样严重。
各场景阅读时间预算:
场景内显示文本最小场景时长
无文本(核心图片、图标、装饰元素)1.5–2秒
1–3字(副标题、标签、数字、短标题)2–3秒
4–10字(短标题+小标题)3–4秒
11–20字(完整句子或两行短文本)4–6秒
21–35字(多行段落、项目符号列表)6–8秒
35字以上(密集讲解文本)拆分为两个场景。单个场景不应要求观众阅读超过约35字。
在阅读时间基础上,添加入场动画缓冲时间:文本完成入场需0.6–1.0秒,之后观众才能开始阅读。实用公式:场景时长 ≥ 入场缓冲时间 + (字数 × 0.25秒) + 0.5秒转场收尾时间,最小为1.5秒。
按此规则应用到每个场景。若场景3显示18字衬线正文,场景3需约5秒,而非3秒。若场景12是单个词(如“Design.”),2秒即可——甚至是理想时长。
最后一个可读元素需在场景过半前完成入场。这样观众在场景后半段有时间阅读文本,再进入转场。若4秒场景中最后一个
tl.from("#s5-sub", …)
在t=3.5秒完成,观众仅有0.5秒阅读时间——不足够。需提前入场时间或延长场景时长。
反模式:将总时长除以场景数,且忽略每个场景的阅读时间。2分钟视频÷10场景=12秒场景(超过硬性上限);或÷60场景=2秒场景(若包含句子长度文本则过短)。
更好的方式:目标场景数平均时长为3–5秒,然后根据每个场景的内容调整时长。短场景用于冲击力内容、图片和副标题。中等场景用于标题。长场景用于正文或项目符号列表。
视频时长目标场景数平均场景时长说明
10–15秒社交广告5–82–3秒节奏紧凑,每个场景都有冲击力
20–30秒预告8–122–4秒开场/铺垫/高潮/收尾,节奏多变
30–60秒讲解视频12–203–5秒每个内容点单独成场景——不要合并两个想法
60–120秒叙事视频24–403–5秒节奏密集。参考YouTube讲解视频,而非幻灯片
120–240秒长视频40–704–5秒拆分为子合成内容,每个章节约8–14个场景
完成步骤3前需进行四项机械检查:
  1. 单场景阅读时间检查:统计每个场景的显示文本字数。
    scene.data-duration
    是否满足上述阅读时间预算?若不满足,延长场景时长(若有预算空间)或拆分文本到两个场景。
  2. 最后可读元素检查:每个场景中,找到最后一个可读文本元素的
    tl.from
    动画。其完成时间(开始时间+时长)是否在场景过半前?若否,提前入场时间。
  3. 若场景
    data-duration
    超过5秒,需写一句话说明原因
    。若无法说明,拆分为两个不同内容的场景。
  4. 将节奏建模为波浪,而非直线
    短-短-长-短-短-长-短
    的节奏显得更有意境。
    平-平-平-平
    的节奏像幻灯片。所有场景时长相同是均分,而非设计。

Build / Breathe / Resolve (per scene)

构建/维持/收尾(每个场景)

Every scene > 4 seconds has three phases. Dumping everything in the build and leaving nothing for breathe/resolve is the #1 quality failure.
PhaseWhenWhat
Build0 – 30%Elements enter, staggered. Don't dump everything at once. Offset first tween 0.1–0.3s.
Breathe30% – 70%Content visible, alive with at least ONE ambient motion. No element stands still here.
Resolve70% – 100%A beat resolves — accent pulse, number lands, secondary element arrives, decisive end.
Full motion theory (easing as emotion, direction rules, speed as weight, transitions as meaning):
skills/hyperframes/references/motion-principles.md
.
时长>4秒的场景需包含三个阶段。仅在构建阶段添加内容,忽略维持/收尾阶段是头号质量问题。
阶段时间范围内容
构建0 – 30%元素依次入场,错开时间。不要一次性全部入场。第一个动画偏移0.1–0.3秒。
维持30% – 70%内容可见,至少有一个元素持续动效。此阶段无元素静止。
收尾70% – 100%完成一个节拍——强调脉冲、数字到位、次要元素入场、明确收尾。
完整动效理论(缓动函数传递情绪、方向规则、速度体现重量、转场传递意义):
skills/hyperframes/references/motion-principles.md

Animation verbs

动画动词

Every element gets a verb. If you can't name the verb, the element is not yet designed.
EnergyVerbsExample
High impactSLAMS, CRASHES, PUNCHES, STAMPS, SHATTERS"$1.9T" SLAMS in from left at -5°
Medium energyCASCADE, SLIDES, DROPS, FILLS, DRAWSThree cards CASCADE in staggered 0.3s
Low energytypes on, FLOATS, morphs, COUNTS UP, fades inCounter COUNTS UP from 0 to 135K
每个元素都要有对应的动词。若无法命名动词,则元素尚未设计完成。
能量等级动词示例
高冲击力猛击(SLAMS)、撞击(CRASHES)、冲击(PUNCHES)、踩踏(STAMPS)、碎裂(SHATTERS)"$1.9T"从左侧以-5°角度猛击入场
中等能量层叠(CASCADE)、滑动(SLIDES)、掉落(DROPS)、填充(FILLS)、绘制(DRAWS)三张卡片以0.3秒间隔层叠入场
低能量打字式出现、漂浮(FLOATS)、变形(morphs)、计数上升(COUNTS UP)、淡入(fades in)计数器从0计数到135K

Mid-scene activity (kills the "animated slides" failure)

场景内动态(避免“动画幻灯片”问题)

Every visible element must have motion during the Breathe phase — not just an entrance. A still image on a still background is a JPEG with a progress bar.
Element typeMid-scene activity
Image / screenshotSlow zoom (
scale: 1 → 1.03-1.05
over scene duration), slow pan, or Ken Burns
Stat / numberCounter animates from 0 to target
Chart / barsBars fill in sequence; line draws via
strokeDashoffset
Logo / lockupSubtle shimmer sweep, gentle scale pulse, or audio-reactive (if music present)
Background decorativesRadial glow breathing, gradient shift, grain drift, hairline rule pulse
Any persistent elementSubtle float (
y: ±4-6px
,
sine.inOut
,
yoyo: true, repeat: 1
) so it's alive instead of frozen
Anti-pattern: entrance tween at t=0.5, element never moves again for the remaining 4+ seconds. If that's the shape of a scene, it's a slideshow, not video.
维持阶段,每个可见元素都要有动效——不仅是入场动画。静止图片配静止背景只是带进度条的JPEG。
元素类型场景内动态
图片/截图缓慢缩放(场景时长内
scale: 1 → 1.03-1.05
)、缓慢平移或Ken Burns效果
统计数据/数字计数器从0动画到目标值
图表/柱状图柱状图依次填充;线条通过
strokeDashoffset
绘制
Logo/组合标志微妙的闪光扫过、柔和的缩放脉冲或音频响应(若有音乐)
背景装饰元素径向呼吸光晕、渐变偏移、颗粒漂移、细线脉冲
任何持续显示元素微妙漂浮(
y: ±4-6px
sine.inOut
yoyo: true, repeat: 1
),保持动态而非静止
反模式:t=0.5秒入场动画,之后4秒元素不再动。若场景是这种结构,那是幻灯片,而非视频。

Cinematic planning, not CSS planning

电影化规划,而非CSS规划

Write each scene as an experience first, specs second. The difference:
Mediocre: "Dark navy background. '$1.9T' in white, 280px. Logo top-left. Wave image bottom-right."
Great: "Camera is already mid-flight over a vast dark canvas. The gradient wave sweeps across the frame like aurora borealis — alive, shifting. '$1.9T' SLAMS into existence with such force the wave ripples in response. This isn't a slide — it's a moment."
The first describes pixels. The second describes an experience. Write the second, then figure out the pixels.

先将每个场景描述为体验,再考虑规格。区别:
平庸描述
深蓝色背景。白色“$1.9T”,280px。Logo位于左上角。波浪图片位于右下角。
优秀描述
镜头已在广阔的深色画布上空移动。渐变波浪像极光一样扫过画面——动态、变幻。“$1.9T”猛击出现,力量之大让波浪产生涟漪。这不是幻灯片——是一个瞬间。
前者描述像素。后者描述体验。先写后者,再考虑像素实现。

Step 4: Build

步骤4:内容构建

Gate: Every composition you wrote passes the self-review checklist at the end of this section.
准入条件:你编写的每个合成内容都通过本节末尾的自我检查清单。

Layout Before Animation

先布局后动画

Static layout FIRST, motion SECOND.
  1. Write the scene's HTML + CSS as if it were a static poster — where every element LANDS at its most-visible moment.
  2. Verify the static layout works in a browser (no GSAP, no JS).
  3. Only after the layout is correct, add timeline + animations.
    gsap.from()
    animates FROM offscreen/invisible TO the CSS position. The CSS position is the ground truth.
Scene containers use
.scene-content
flex centering, not absolute positioning on inner content. Keep decoratives (backgrounds, glows, hairlines, grain) OUTSIDE
.scene-content
. Keep animated content INSIDE
.scene-content
.
先做静态布局,再添加动画。
  1. 将场景的HTML + CSS视为静态海报编写——每个元素都处于最显眼的最终位置。
  2. 验证静态布局在浏览器中可正常显示(无需GSAP,无需JS)。
  3. 布局正确后,再添加时间线+动画。
    gsap.from()
    从屏幕外/不可见状态动画到CSS位置。CSS位置是基准。
场景容器使用
.scene-content
弹性居中,而非内部内容绝对定位。装饰元素(背景、光晕、细线、颗粒)放在
.scene-content
外部。动画内容放在
.scene-content
内部。

Clip contract

片段约定

Every scene is a HyperFrames clip. EVERY scene has a
<div class="scene-content">
wrapper — not just scene-1. This is the single most-missed rule in output audits.
The wrapper exists so
HyperShader.captureIncomingScene()
can hide scene content during
html2canvas
capture, preventing pre-animation from-states from leaking into the WebGL texture. Without the wrapper on a non-first scene, you'll see boxes, clipped text, or empty placeholders during the transition INTO that scene.
html
<!-- SCENE 1 — visible from t=0, no inline style -->
<div class="scene clip" id="s1" data-start="0" data-duration="5" data-track-index="0">
  <!-- OUTSIDE .scene-content: backgrounds, decoratives. Captured into shader textures. -->
  <div class="bg-grain"></div>
  <div class="bg-vignette"></div>
  <!-- INSIDE .scene-content: every animated element. REQUIRED on every scene. -->
  <div class="scene-content">
    <h1 id="s1-title"></h1>
    <p id="s1-sub"></p>
  </div>
</div>

<!-- SCENE 2+ — starts hidden; same wrapper structure.
     Inline style is opacity:0 ONLY (no visibility:hidden). See "Scene initial visibility" below. -->
<div
  class="scene clip"
  id="s2"
  data-start="5"
  data-duration="5"
  data-track-index="0"
  style="opacity:0;"
>
  <div class="bg-grain"></div>
  <div class="scene-content">
    <!-- ← MANDATORY, not just a scene-1 pattern -->
    <h1 id="s2-title"></h1>
    <p id="s2-sub"></p>
  </div>
</div>
每个场景都是HyperFrames片段。每个场景都要有
<div class="scene-content">
包裹——不仅是场景1。这是输出审核中最常遗漏的规则。
包裹的存在是为了让
HyperShader.captureIncomingScene()
html2canvas
捕获时隐藏场景内容,防止动画前状态泄露到WebGL纹理中。若非第一个场景缺少该包裹,进入该场景的转场中会出现方框、裁剪文本或空白占位符。
html
<!-- 场景1 — 从t=0可见,无内联样式 -->
<div class="scene clip" id="s1" data-start="0" data-duration="5" data-track-index="0">
  <!-- .scene-content外部:背景、装饰元素。捕获到着色器纹理中。 -->
  <div class="bg-grain"></div>
  <div class="bg-vignette"></div>
  <!-- .scene-content内部:所有动画元素。每个场景都必须有。 -->
  <div class="scene-content">
    <h1 id="s1-title"></h1>
    <p id="s1-sub"></p>
  </div>
</div>

<!-- 场景2及以后 — 初始隐藏;结构相同。
     内联样式仅设置opacity:0(不要visibility:hidden)。见下方“场景初始可见性”。 -->
<div
  class="scene clip"
  id="s2"
  data-start="5"
  data-duration="5"
  data-track-index="0"
  style="opacity:0;"
>
  <div class="bg-grain"></div>
  <div class="scene-content">
    <!-- ← 必须存在,不仅是场景1的模式 -->
    <h1 id="s2-title"></h1>
    <p id="s2-sub"></p>
  </div>
</div>

Data attributes

数据属性

Every timed element (scene, image, video, audio, sub-composition host) is a "clip" and must carry:
AttributeRequiredValues
id
yesunique identifier
class="clip"
yesliteral string (scenes use
"scene clip"
)
data-start
yesseconds, or clip-id reference (
"el-1"
,
"intro+2"
)
data-duration
required for img/div/compositionsseconds. video/audio default to media duration
data-track-index
yesinteger. same-track clips cannot overlap in time
data-media-start
notrim offset into source (seconds) for video/audio
data-volume
no0–1 (default 1) for audio
data-track-index
is TIMELINE layering (which clip's timeline wraps which) — not visual z-order. Use CSS
z-index
for stacking. Same-track clips can't overlap in time; use different tracks for simultaneous clips or put them on the same track with non-overlapping windows.
Composition roots (the outer
index.html
and any
<template>
-wrapped sub-comp root) also need:
AttributeRequiredValues
data-composition-id
yesunique ID. root uses
"main"
by convention
data-start
yesroot:
"0"
data-duration
yesseconds. takes precedence over GSAP timeline length
data-width
yespixel width (1920 for 16:9, 1080 for 9:16, 1080 for 1:1)
data-height
yespixel height (1080 for 16:9, 1920 for 9:16, 1080 for 1:1)
data-composition-src
nopath to external HTML sub-composition (
compositions/…
)
每个定时元素(场景、图片、视频、音频、子合成内容宿主)都是“片段”,必须包含:
属性是否必填
id
唯一标识符
class="clip"
字面字符串(场景使用
"scene clip"
data-start
秒数,或片段ID引用(
"el-1"
"intro+2"
data-duration
图片/ div/合成内容必填秒数。视频/音频默认使用媒体时长
data-track-index
整数。同轨道片段时间不可重叠
data-media-start
视频/音频源的裁剪偏移(秒)
data-volume
0–1(默认1),用于音频
data-track-index
是时间线分层(哪个片段的时间线包裹哪个)——不是视觉z轴层级。使用CSS
z-index
控制堆叠。同轨道片段时间不可重叠;同时播放的片段使用不同轨道,或放在同一轨道且时间不重叠。
合成内容根元素(外层
index.html
和任何
<template>
包裹的子合成内容根元素)还需:
属性是否必填
data-composition-id
唯一ID。根元素惯例使用
"main"
data-start
根元素:
"0"
data-duration
秒数。优先级高于GSAP时间线长度
data-width
像素宽度(16:9为1920,9:16为1080,1:1为1080)
data-height
像素高度(16:9为1080,9:16为1920,1:1为1080)
data-composition-src
外部HTML子合成内容路径(
compositions/…

Timeline contract

时间线约定

  • Every composition has exactly ONE timeline, created
    paused: true
    .
  • Register it on
    window.__timelines["<composition-id>"]
    — the key MUST match the root's
    data-composition-id
    exactly.
  • The composition root's DOM
    id
    attribute should also equal its
    data-composition-id
    (convention:
    <div id="main" data-composition-id="main" …>
    ). Nothing in the runtime enforces this, but consistent IDs make
    #main
    selectors in your timeline code and the
    __timelines
    key one word apart — preventing the
    id="root"
    /
    data-composition-id="main"
    /
    __timelines["main"]
    three-way drift that's easy to typo.
  • Never call
    .play()
    on the timeline — the player/render engine drives playback via frame-accurate seeking.
  • Framework auto-nests sub-comp timelines — DO NOT manually
    .add()
    them to the root timeline.
  • Duration comes from
    data-duration
    on the root, NOT from GSAP timeline length.
  • Construct synchronously at page load. No
    async
    /
    await
    /
    setTimeout
    wrapping timeline code.
  • 每个合成内容恰好有一条时间线,创建时设置
    paused: true
  • 注册到
    window.__timelines["<composition-id>"]
    ——键必须与根元素的
    data-composition-id
    完全匹配。
  • 合成内容根元素的DOM
    id
    属性也应等于其
    data-composition-id
    (惯例:
    <div id="main" data-composition-id="main" …>
    )。运行时不强制要求,但一致的ID让时间线代码中的
    #main
    选择器和
    __timelines
    键一致——避免
    id="root"
    /
    data-composition-id="main"
    /
    __timelines["main"]
    三者不一致的拼写错误。
  • 切勿调用时间线的
    .play()
    方法——播放器/渲染引擎通过帧精确的seek控制播放。
  • 框架自动嵌套子合成内容时间线——请勿手动将子时间线
    .add()
    到根时间线。
  • 时长由根元素的
    data-duration
    决定,而非GSAP时间线长度。
  • 页面加载时同步构建。不要用
    async
    /
    await
    /
    setTimeout
    包裹时间线代码。

Video and audio

视频和音频

Video elements must be
muted playsinline
. Browsers silently block audio playback on inline video, so HyperFrames never uses
<video>
for audio
— even when the audio is from the same source file, it goes on a separate
<audio>
element with its own
data-track-index
. Never call
video.play()
or
audio.play()
in your code; the framework owns media playback.
html
<video
  id="v-main"
  class="clip"
  data-start="0"
  data-duration="30"
  data-track-index="0"
  src="footage.mp4"
  muted
  playsinline
></video>

<audio
  id="v-main-audio"
  class="clip"
  data-start="0"
  data-duration="30"
  data-track-index="2"
  src="footage.mp4"
  data-volume="1"
></audio>
If your video has audio that only plays during part of the scene, use
data-media-start
to offset into the source, and trim
data-duration
to the audible window.
视频元素必须设置
muted playsinline
。浏览器会静默阻止内联视频的音频播放,因此HyperFrames从不使用
<video>
承载音频
——即使音频来自同一源文件,也需放在单独的
<audio>
元素中,并设置自己的
data-track-index
。切勿在代码中调用
video.play()
audio.play()
;框架控制媒体播放。
html
<video
  id="v-main"
  class="clip"
  data-start="0"
  data-duration="30"
  data-track-index="0"
  src="footage.mp4"
  muted
  playsinline
></video>

<audio
  id="v-main-audio"
  class="clip"
  data-start="0"
  data-duration="30"
  data-track-index="2"
  src="footage.mp4"
  data-volume="1"
></audio>
若视频音频仅在场景部分时间播放,使用
data-media-start
偏移到源文件对应位置,并调整
data-duration
到音频播放时长。

Scene initial visibility — TWO paths depending on whether HyperShader runs in this file

场景初始可见性——根据文件是否运行HyperShader分两种情况

The runtime's visibility gate sets
style.visibility = "hidden"
on every
[data-start]
element outside its window — BUT it never touches
style.opacity
. That splits the rules for non-first scenes:
WITH HyperShader in this file: non-first scenes carry
style="opacity:0;"
ONLY — no
visibility:hidden
. The runtime's visibility gate already keeps the scene hidden before its
data-start
, and HyperShader's
captureIncomingScene
temporarily forces
opacity:1
during
html2canvas
capture so the shader gets a real texture of the incoming scene's background + decoratives. At transition end, HyperShader sets the incoming scene's
style.opacity = "1"
itself.
Do NOT add
visibility:hidden
in the inline style on these scenes. It's redundant (the runtime gate handles hiding) AND it breaks
captureIncomingScene
html2canvas
sees the element as
visibility:hidden
, renders it as blank, and the shader ends up transitioning from a real outgoing scene to a blank incoming texture. Visually: content morphs/fades into the background color during the transition, then pops in after — a visible "blink" at the transition.
WITHOUT HyperShader in this file: non-first scenes carry
style="visibility:hidden;"
ONLY — no
opacity:0
. Nothing animates scene-container opacity back to 1 without HyperShader; if you include
opacity:0
the scene stays invisible for its entire window.
Scene-1 always has no inline style — it's visible from t=0.
运行时可见性控制会将每个
[data-start]
元素在其时间窗口外设置为
style.visibility = "hidden"
——但从不修改
style.opacity
。这导致非第一个场景的规则分为两种:
文件中包含HyperShader:非第一个场景仅设置
style="opacity:0;"
——不要
visibility:hidden
。运行时可见性控制已在
data-start
前隐藏场景,HyperShader的
captureIncomingScene
html2canvas
捕获时会临时强制设置
opacity:1
,以便着色器获取进入场景的背景+装饰元素的真实纹理。转场结束时,HyperShader会自行将进入场景的
style.opacity
设置为
"1"
请勿在这些场景的内联样式中添加
visibility:hidden
。这是冗余的(运行时控制已处理隐藏),且会破坏
captureIncomingScene
——
html2canvas
会将元素视为
visibility:hidden
,渲染为空白,导致着色器转场从真实的离开场景到空白的进入纹理。视觉效果:内容在转场中变形/淡入背景色,之后突然出现——转场时可见“闪烁”。
文件中不包含HyperShader:非第一个场景仅设置
style="visibility:hidden;"
——不要
opacity:0
。没有HyperShader的话,没有任何东西会将场景容器的不透明度动画回1;若设置
opacity:0
,场景会始终不可见。
场景1始终无内联样式——从t=0可见。

Shader transitions

着色器转场

Use
@hyperframes/shader-transitions
. Exactly 14 shader names are valid — any other string throws
[HyperShader] Unknown shader
:
domain-warp
,
ridged-burn
,
whip-pan
,
sdf-iris
,
ripple-waves
,
gravitational-lens
,
cinematic-zoom
,
chromatic-split
,
swirl-vortex
,
thermal-distortion
,
flash-through-white
,
cross-warp-morph
,
light-leak
,
glitch
.
Authoritative list:
packages/shader-transitions/src/shaders/registry.ts
.
Mood → shader mapping:
skills/hyperframes/references/transitions.md
.
The IIFE build registers the package on
window.HyperShader
(not
HyperframesShaderTransitions
):
html
<script src="https://cdn.jsdelivr.net/npm/@hyperframes/shader-transitions/dist/index.global.js"></script>
<script>
  const tl = gsap.timeline({ paused: true });
  window.HyperShader.init({
    bgColor: "#0a0a0d",
    scenes: ["s1", "s2", "s3"],
    timeline: tl,
    transitions: [
      { time: 5.75, shader: "cinematic-zoom", duration: 0.5 },
      { time: 11.75, shader: "whip-pan", duration: 0.5 },
    ],
  });
  window.__timelines["main"] = tl;
</script>
Scene-count invariant —
scenes.length === transitions.length + 1
:
HyperShader enforces this at init. Pick one anchor scene BEFORE the first transition, and one anchor AFTER each transition. A video with three act-boundary transitions needs exactly four anchor scenes. Scenes between anchors (non-bracketing, runtime-managed) carry
style="visibility:hidden;"
instead of
style="opacity:0;"
— they're not HyperShader-managed so nothing animates their opacity back to 1.
The simplest working pattern: list only the scene just before AND just after each shader cut. Do NOT list every scene in Act II just because they "span" a transition — that violates the invariant. If you genuinely need MORE listed anchors than real shader transitions (rare — e.g., tracking an additional fade beat that's not a visible shader bridge), insert
{ shader: "flash-through-white", duration: 0.01 }
as an invisible no-op bridge to satisfy the invariant. This is a workaround; the cleaner fix is almost always to drop the extra anchor.
Transition timing (critical — the scene boundary must fall INSIDE the transition window):
Scene windows are half-open (
[start, start+duration)
). At time
B
(the boundary), the runtime has already flipped the outgoing scene to
visibility:hidden
. If
transition.time === B
,
html2canvas
captures a blank outgoing texture → shader transitions from blank → incoming → visible blink.
Rule:
transition.time < B
AND
transition.time + duration > B
. Simplest — center it:
transition.time = B - duration/2
. Example: scene-1 ends at 6, duration 0.5 →
time: 5.75
.
Scene visibility: HANDS OFF. HyperShader owns scene
opacity
end-to-end. Do NOT add
tl.set(#scene-N, {autoAlpha: …}, …)
on scene containers. If you do, you create the same visibility race that produces the blink.
使用
@hyperframes/shader-transitions
。恰好有14个有效着色器名称——其他字符串会抛出
[HyperShader] Unknown shader
错误:
domain-warp
,
ridged-burn
,
whip-pan
,
sdf-iris
,
ripple-waves
,
gravitational-lens
,
cinematic-zoom
,
chromatic-split
,
swirl-vortex
,
thermal-distortion
,
flash-through-white
,
cross-warp-morph
,
light-leak
,
glitch
权威列表:
packages/shader-transitions/src/shaders/registry.ts
情绪→着色器映射:
skills/hyperframes/references/transitions.md
IIFE构建版本将包注册到**
window.HyperShader
**(而非
HyperframesShaderTransitions
):
html
<script src="https://cdn.jsdelivr.net/npm/@hyperframes/shader-transitions/dist/index.global.js"></script>
<script>
  const tl = gsap.timeline({ paused: true });
  window.HyperShader.init({
    bgColor: "#0a0a0d",
    scenes: ["s1", "s2", "s3"],
    timeline: tl,
    transitions: [
      { time: 5.75, shader: "cinematic-zoom", duration: 0.5 },
      { time: 11.75, shader: "whip-pan", duration: 0.5 },
    ],
  });
  window.__timelines["main"] = tl;
</script>
场景数量不变式——
scenes.length === transitions.length + 1
:HyperShader在初始化时强制执行此规则。选择第一个转场前的一个锚点场景,以及每个转场后的一个锚点场景。包含三个章节转场的视频恰好需要四个锚点场景。锚点之间的场景(非分界、运行时管理)设置
style="visibility:hidden;"
而非
style="opacity:0;"
——它们不由HyperShader管理,因此不会动画恢复不透明度。
最简单的有效模式:仅列出每个着色器切换前后的场景。不要因为场景“跨越”转场就列出第二章节的所有场景——这会违反不变式。若确实需要比实际着色器转场更多的锚点场景(罕见——比如跟踪额外的淡入节拍,而非可见的着色器过渡),插入
{ shader: "flash-through-white", duration: 0.01 }
作为不可见的无操作过渡,以满足不变式。这是权宜之计;更简洁的解决方案几乎总是删除额外的锚点。
转场时间(关键——场景边界必须落在转场窗口内)
场景窗口是半开区间(
[start, start+duration)
)。在时间
B
(边界),运行时已将离开场景设置为
visibility:hidden
。若
transition.time === B
html2canvas
会捕获空白的离开纹理→着色器转场从空白→进入场景→可见闪烁。
规则:
transition.time < B
transition.time + duration > B
。最简单的方式是居中:
transition.time = B - duration/2
。示例:场景1在6秒结束,转场时长0.5秒→
time: 5.75
场景可见性:请勿手动修改。HyperShader全程管理场景
opacity
。请勿在场景容器上添加
tl.set(#scene-N, {autoAlpha: …}, …)
。否则会导致可见性竞争,产生闪烁。

Sub-compositions — default NO for videos ≤ 3 minutes

子合成内容——视频≤3分钟默认不拆分

Default to a single
index.html
with scenes tiled inline. 30-second to 2-minute compositions fit cleanly in one file (~1500–2000 lines). Single file = single HyperShader instance = no canvas conflicts = everything works.
Split into sub-compositions ONLY when one of these is true:
  • Video length > 3 minutes AND you need organizational structure.
  • You're extracting a REUSABLE sub-comp that appears in multiple places (chart block, logo outro).
  • A single scene is so complex it deserves its own file (full UI recreation, heavy data-vis).
If you do split, HyperShader lives at the ROOT
index.html
ONLY
— never inside a sub-composition. HyperShader hardcodes
#gl-canvas
as its canvas ID (see the canvas creation path in
packages/shader-transitions/src/hyper-shader.ts
); multiple HyperShader instances can't share one canvas. When a sub-comp's HyperShader fails silently on canvas conflict, its fallback code calls
document.querySelectorAll(".scene")
document-wide and sets every scene's opacity to 0 — corrupting visibility across the whole document. Symptom: only scene-1 of each act shows, scenes 2+ never appear.
默认使用单个
index.html
,场景内联排列。30秒到2分钟的合成内容可清晰放在一个文件中(约1500–2000行)。单个文件=单个HyperShader实例=无画布冲突=一切正常。
仅在以下情况之一时拆分为子合成内容:
  • 视频时长>3分钟,且需要组织结构。
  • 提取可复用的子合成内容,用于多个地方(图表块、Logo收尾)。
  • 单个场景过于复杂,值得单独成文件(完整UI还原、复杂数据可视化)。
若拆分,HyperShader仅在根
index.html
中初始化
——切勿在子合成内容中初始化。HyperShader硬编码
#gl-canvas
作为画布ID(见
packages/shader-transitions/src/hyper-shader.ts
中的画布创建路径);多个HyperShader实例无法共享一个画布。当子合成内容的HyperShader因画布冲突静默失败时,其 fallback代码会调用
document.querySelectorAll(".scene")
获取所有场景,并将所有场景的不透明度设置为0——破坏整个文档的可见性。症状:每个章节仅场景1显示,场景2及以后永不出现。

Sub-composition file shape

子合成内容文件结构

Every sub-comp file in
compositions/
is wrapped in a
<template>
. The template's contents are INERT in the browser by spec — the runtime extracts and nests them into the parent at render time. A standalone
index.html
(the main composition) does NOT use
<template>
; the data-composition-id div goes directly in
<body>
.
html
<!-- compositions/act-1-intro.html -->
<template id="act-1-intro-template">
  <div class="hf-sub" data-composition-id="act-1-intro" data-width="1920" data-height="1080">
    <style>
      .hf-sub {
        position: relative;
        width: 1920px;
        height: 1080px;
      }
      /* scene styles scoped to this sub-comp */
    </style>

    <div class="scene clip" id="a1-s1" data-start="0" data-duration="5" data-track-index="0">
      <div class="scene-content"></div>
    </div>
    <div
      class="scene clip"
      id="a1-s2"
      data-start="5"
      data-duration="5"
      data-track-index="0"
      style="visibility:hidden;"
    >
      <div class="scene-content"></div>
    </div>

    <script>
      // Sub-comp does NOT re-load GSAP — parent loads it once.
      window.__timelines = window.__timelines || {};
      const tl = gsap.timeline({ paused: true });
      // Tween positions are LOCAL (0 = sub-comp start). Parent auto-offsets at its data-start.
      tl.from("#a1-s1 .title", { y: 40, autoAlpha: 0, duration: 0.8 }, 0.3);
      tl.from("#a1-s2 .body", { y: 20, autoAlpha: 0, duration: 0.6 }, 5.3);
      // DO NOT call window.HyperShader.init() here — HyperShader is root-only.
      window.__timelines["act-1-intro"] = tl;
    </script>
  </div>
</template>
compositions/
中的每个子合成内容文件都用
<template>
包裹。根据规范,模板内容在浏览器中是惰性的——运行时会提取并嵌套到父元素中。独立的
index.html
(主合成内容)不使用
<template>
;data-composition-id div直接放在
<body>
中。
html
<!-- compositions/act-1-intro.html -->
<template id="act-1-intro-template">
  <div class="hf-sub" data-composition-id="act-1-intro" data-width="1920" data-height="1080">
    <style>
      .hf-sub {
        position: relative;
        width: 1920px;
        height: 1080px;
      }
      /* 子合成内容的场景样式 */
    </style>

    <div class="scene clip" id="a1-s1" data-start="0" data-duration="5" data-track-index="0">
      <div class="scene-content"></div>
    </div>
    <div
      class="scene clip"
      id="a1-s2"
      data-start="5"
      data-duration="5"
      data-track-index="0"
      style="visibility:hidden;"
    >
      <div class="scene-content"></div>
    </div>

    <script>
      // 子合成内容无需重新加载GSAP——父元素已加载一次。
      window.__timelines = window.__timelines || {};
      const tl = gsap.timeline({ paused: true });
      // 动画位置是本地的(0 = 子合成内容开始)。父元素会根据其data-start自动偏移。
      tl.from("#a1-s1 .title", { y: 40, autoAlpha: 0, duration: 0.8 }, 0.3);
      tl.from("#a1-s2 .body", { y: 20, autoAlpha: 0, duration: 0.6 }, 5.3);
      // 请勿在此调用window.HyperShader.init()——HyperShader仅在根元素初始化。
      window.__timelines["act-1-intro"] = tl;
    </script>
  </div>
</template>

Parent
index.html
wiring

index.html
配置

The parent mounts each sub-comp via
data-composition-src
on an empty div that carries the clip contract:
html
<div
  id="act-1"
  class="scene clip"
  data-composition-id="act-1-intro"
  data-composition-src="compositions/act-1-intro.html"
  data-start="0"
  data-duration="30"
  data-track-index="0"
></div>
Three rules when splitting:
  1. <template id="<id>-template">
    wrapper required on every sub-comp. Contents are inert; the runtime extracts them.
  2. The
    data-composition-id
    on the sub-comp's inner root div MUST match BOTH (a) the parent container's
    data-composition-id
    AND (b) the key in
    window.__timelines[...]
    inside the sub-comp's script.
  3. Tween positions in a sub-comp are LOCAL to that sub-comp (0 = its start). The parent auto-offsets by the container's
    data-start
    . Never manually add sub-timelines to the root timeline.
Since the sub-comps in this pattern don't use HyperShader (by the rule above), their non-first scenes carry
style="visibility:hidden;"
only — see "Scene initial visibility" above for why.
父元素通过空div上的
data-composition-src
挂载每个子合成内容,该div需包含片段约定:
html
<div
  id="act-1"
  class="scene clip"
  data-composition-id="act-1-intro"
  data-composition-src="compositions/act-1-intro.html"
  data-start="0"
  data-duration="30"
  data-track-index="0"
></div>
拆分时的三个规则:
  1. 每个子合成内容必须用
    <template id="<id>-template">
    包裹。内容是惰性的;运行时会提取。
  2. 子合成内容内部根div的
    data-composition-id
    必须同时匹配(a)父容器的
    data-composition-id
    和(b)子合成内容脚本中
    window.__timelines[...]
    的键。
  3. 子合成内容中的动画位置是本地的(0 = 其开始时间)。父元素会根据容器的
    data-start
    自动偏移。切勿手动将子时间线添加到根时间线。
由于此模式下的子合成内容不使用HyperShader(根据上述规则),其非第一个场景仅设置
style="visibility:hidden;"
——见上方“场景初始可见性”原因。

Determinism ❌ / ✅

确定性 ❌ / ✅

The render engine seeks to exact frames and expects pixel-identical output on every repeat render. Violations produce broken output.
❌ Never✅ Use instead
Date.now()
,
performance.now()
tl.time()
inside
onUpdate
, or hard-coded timing
Math.random()
unseeded
seeded PRNG (e.g. mulberry32) with a known seed
setInterval
,
setTimeout
in timeline
timeline tweens +
onUpdate
callbacks
repeat: -1
on any tween or timeline
repeat: Math.ceil(duration / cycleDuration) - 1
Timelines built in
async
/
await
wrapper
Construct synchronously at page load
video.play()
,
audio.play()
in code
Framework owns media playback
Animating
visibility
or
display
autoAlpha
(animates opacity AND toggles visibility)
渲染引擎会seek到精确帧,且每次重复渲染都期望像素级一致的输出。违反规则会导致输出损坏。
❌ 禁止操作✅ 替代方案
Date.now()
performance.now()
tl.time()
onUpdate
中使用,或硬编码时间
未播种的
Math.random()
播种的PRNG(如mulberry32),使用已知种子
时间线中的
setInterval
setTimeout
时间线动画 +
onUpdate
回调
任何动画或时间线设置
repeat: -1
repeat: Math.ceil(duration / cycleDuration) - 1
async
/
await
包裹时间线构建
页面加载时同步构建
代码中调用
video.play()
audio.play()
框架控制媒体播放
动画
visibility
display
autoAlpha
(动画不透明度并切换可见性)

Motion rules (HyperFrames-native, non-negotiable)

动效规则(HyperFrames原生,不可协商)

Inherited from
skills/hyperframes/SKILL.md#Rules-Non-Negotiable
:
  • GSAP visual properties only. Animate
    opacity
    ,
    x
    ,
    y
    ,
    scale
    ,
    rotation
    ,
    color
    ,
    transforms
    . Do NOT animate
    visibility
    or
    display
    directly (use
    autoAlpha
    ).
  • One paused timeline per composition.
    { paused: true }
    . Register on
    window.__timelines["<composition-id>"]
    . Never call
    .play()
    .
  • Vary eases — at least 3 different eases per scene. Don't default to
    power2.out
    on everything.
  • Offset first tween 0.1–0.3s. Zero-delay entrances feel like jump cuts.
  • Exit animations BANNED except on the final scene. The transition IS the exit. See the code examples below — this is the single most frequently-violated rule in generated output.
继承自
skills/hyperframes/SKILL.md#Rules-Non-Negotiable
  • 仅使用GSAP视觉属性。动画
    opacity
    x
    y
    scale
    rotation
    color
    transforms
    。请勿直接动画
    visibility
    display
    (使用
    autoAlpha
    )。
  • 每个合成内容一条暂停的时间线
    { paused: true }
    。注册到
    window.__timelines["<composition-id>"]
    。切勿调用
    .play()
  • 变化缓动函数——每个场景至少使用3种不同的缓动函数。不要默认所有动画都用
    power2.out
  • 第一个动画偏移0.1–0.3秒。零延迟入场像跳切。
  • 禁止退出动画,最后一个场景除外。转场就是退出。见下方代码示例——这是生成输出中最常违反的规则。

Motion anti-patterns (observed in generated output, with fixes)

动效反模式(生成输出中常见,附修复方案)

These four patterns keep appearing in generated compositions despite the rules above. Each one is observed in real outputs; each has a known-clean replacement. Pattern-match these, not just the prose rules.
尽管有上述规则,这四种模式仍频繁出现在生成的合成内容中。每种模式都在真实输出中被观察到;每种都有已知的正确替代方案。请匹配这些模式,而非仅看文字规则。

Anti-pattern 1: Exit tween before a shader transition

反模式1:着色器转场前的退出动画

The shader's
captureScene(fromScene)
runs
html2canvas
on the outgoing scene at transition time. If you've animated content to
opacity: 0
(or
autoAlpha: 0
, or off-screen) before the transition fires,
html2canvas
captures an empty scene. The shader morphs from an empty outgoing texture → the incoming scene, which looks like "the content vanished, then the transition happened." This is independent of whether the shader itself works — it's a composition-level bug.
This matches industry practice: in Remotion's
<TransitionSeries>
, in the GSAP community's own guidance, and in HyperFrames' core
references/transitions.md
— the transition component owns the visual handoff. The scene's content does not animate its own exit.
js
// ✖ WRONG — card fades to 0 before transition at t=17.80 fires.
//   Shader captures an empty phone. User sees the card disappear
//   0.85s before the transition, then an empty-phone-to-next-scene morph.
tl.to("#s6-card", { x: 180, rotation: 14, duration: 0.55, ease: "power3.in" }, 16.5);
tl.to("#s6-card", { autoAlpha: 0, duration: 0.25 }, 16.95); // BANNED

// HyperShader transition at 17.80 captures #s6 with card invisible
js
// ✓ RIGHT — mid-scene swipe gesture, then a different beat holds the final
//   frame. Card moves but stays visible. Transition handles the actual exit.
tl.to("#s6-card", { x: 180, rotation: 14, duration: 0.55, ease: "power3.in" }, 15.3);
tl.from("#s6-check", { scale: 0, duration: 0.3, ease: "back.out(2)" }, 15.6);
tl.from("#s6-match-stamp", { scale: 1.5, autoAlpha: 0, duration: 0.4 }, 16.1);
// scene 6 ends at 18.0 with the matched-stamp + pulsing check button visible.
// HyperShader transition at 17.80 captures a FULL scene → clean morph.
Common trap: "I want to show a swipe gesture, so the card has to exit." No — the swipe gesture happens mid-scene, at 60–70% of scene duration. The last 30% of the scene shows the RESULT of the swipe (a match stamp, a confirmation, a badge). Keep something visible at transition time. If there's nothing logically left to show, the scene is too long — shorten it.
着色器的
captureScene(fromScene)
在转场时间对离开场景运行
html2canvas
。若你在转场触发前将内容动画到
opacity: 0
(或
autoAlpha: 0
,或屏幕外),
html2canvas
会捕获空白场景。着色器从空白离开纹理→进入场景变形,看起来像“内容消失,然后转场发生”。这与着色器本身是否工作无关——是合成内容层面的bug。
这符合行业惯例:在Remotion的
<TransitionSeries>
、GSAP社区的指导、以及HyperFrames核心的
references/transitions.md
中——转场组件负责视觉交接。场景内容不应动画自身退出。
js
// ✖ 错误——卡片在t=17.80转场前淡入0。
//   着色器捕获空白的手机。用户看到卡片在转场前0.85秒消失,然后是空白手机到下一场景的变形。
tl.to("#s6-card", { x: 180, rotation: 14, duration: 0.55, ease: "power3.in" }, 16.5);
tl.to("#s6-card", { autoAlpha: 0, duration: 0.25 }, 16.95); // 禁止

// HyperShader在17.80转场时捕获#s6,此时卡片不可见
js
// ✓ 正确——场景内滑动手势,然后不同节拍保持最终帧。卡片移动但保持可见。转场处理实际退出。
tl.to("#s6-card", { x: 180, rotation: 14, duration: 0.55, ease: "power3.in" }, 15.3);
tl.from("#s6-check", { scale: 0, duration: 0.3, ease: "back.out(2)" }, 15.6);
tl.from("#s6-match-stamp", { scale: 1.5, autoAlpha: 0, duration: 0.4 }, 16.1);
// 场景6在18.0结束,此时匹配印章+脉冲检查按钮可见。
// HyperShader在17.80转场时捕获完整场景→干净变形。
常见陷阱:“我想展示滑动手势,所以卡片必须退出。”不——滑动手势发生在场景中期,占场景时长的60–70%。场景最后30%展示滑动的结果(匹配印章、确认、徽章)。转场时保持内容可见。若逻辑上无内容可展示,说明场景过长——缩短时长。

Anti-pattern 2: Non-deterministic
stagger
origin

反模式2:非确定性
stagger
起点

js
// ✖ WRONG — `from: "random"` picks a random origin at timeline-construction
//   time using GSAP's internal unseeded random. Two renders of the same
//   composition produce different stagger orderings. Fails PSNR regression
//   tests and violates the deterministic-render rule.
tl.from(
  "#s12 .card",
  {
    scale: 0.7,
    autoAlpha: 0,
    y: 40,
    duration: 0.45,
    stagger: { each: 0.04, from: "random" }, // BANNED
  },
  34.55,
);
js
// ✓ RIGHT — deterministic stagger origins. All of these are safe.
tl.from(
  "#s12 .card",
  { scale: 0.7, autoAlpha: 0, y: 40, duration: 0.45, stagger: { each: 0.04, from: "start" } },
  34.55,
); // natural order

tl.from(
  "#s12 .card",
  { scale: 0.7, autoAlpha: 0, y: 40, duration: 0.45, stagger: { each: 0.04, from: "center" } },
  34.55,
); // ripple outward

tl.from(
  "#s12 .card",
  {
    scale: 0.7,
    autoAlpha: 0,
    y: 40,
    duration: 0.45,
    stagger: { each: 0.04, grid: [3, 5], from: [0, 0] },
  },
  34.55,
); // grid-aware
If you truly need pseudo-random ordering (rare), pre-shuffle the cards in the markup using a seeded PRNG like mulberry32 — the ordering is then committed to the DOM and deterministic forever.
js
// ✖ 错误——`from: "random"`在时间线构建时使用GSAP内部未播种的随机数选择随机起点。
//   同一合成内容的两次渲染产生不同的 stagger顺序。无法通过PSNR回归测试,违反确定性渲染规则。
tl.from(
  "#s12 .card",
  {
    scale: 0.7,
    autoAlpha: 0,
    y: 40,
    duration: 0.45,
    stagger: { each: 0.04, from: "random" }, // 禁止
  },
  34.55,
);
js
// ✓ 正确——确定性 stagger起点。以下都是安全的。
tl.from(
  "#s12 .card",
  { scale: 0.7, autoAlpha: 0, y: 40, duration: 0.45, stagger: { each: 0.04, from: "start" } },
  34.55,
); // 自然顺序

tl.from(
  "#s12 .card",
  { scale: 0.7, autoAlpha: 0, y: 40, duration: 0.45, stagger: { each: 0.04, from: "center" } },
  34.55,
); // 向外扩散

tl.from(
  "#s12 .card",
  {
    scale: 0.7,
    autoAlpha: 0,
    y: 40,
    duration: 0.45,
    stagger: { each: 0.04, grid: [3, 5], from: [0, 0] },
  },
  34.55,
); // 网格感知
若确实需要伪随机顺序(罕见),使用mulberry32等播种PRNG在标记中预洗牌卡片——顺序会提交到DOM,永远保持确定性。

Anti-pattern 3: Centering content with
position: absolute; top; left
on
.scene-content

反模式3:用
position: absolute; top; left
.scene-content
上居中内容

css
/* ✖ WRONG — absolute-positioned content container with hardcoded pixels.
   Renders at 1920×1080 but overflows at any other aspect ratio. Also
   pushes you toward absolute-positioning every child, which is fragile. */
.scene-content {
  position: absolute;
  top: 200px;
  left: 160px;
  width: 1920px;
  height: 1080px;
}
css
/* ✓ RIGHT — flex-filled container with padding for the positioning.
   Works at any aspect ratio. Children flow naturally. */
.scene-content {
  width: 100%;
  height: 100%;
  padding: 120px 160px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 24px;
  box-sizing: border-box;
}
See
skills/hyperframes/SKILL.md#Layout-Before-Animation
for the full rationale — in short: position every element at its final landing state first, then
gsap.from()
the entrance animating TO that position.
css
/* ✖ 错误——绝对定位的内容容器,使用硬编码像素。
   在1920×1080渲染,但在其他宽高比下溢出。也会导致你倾向于绝对定位每个子元素,这很脆弱。 */
.scene-content {
  position: absolute;
  top: 200px;
  left: 160px;
  width: 1920px;
  height: 1080px;
}
css
/* ✓ 正确——弹性填充容器,用padding定位。
   适用于任何宽高比。子元素自然排列。 */
.scene-content {
  width: 100%;
  height: 100%;
  padding: 120px 160px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 24px;
  box-sizing: border-box;
}
skills/hyperframes/SKILL.md#Layout-Before-Animation
的完整原理——简而言之:先将每个元素放在最终位置,然后用
gsap.from()
动画入场到该位置。

Anti-pattern 4: SVG filter data URLs used as
background-image
(grain, noise, turbulence)

反模式4:SVG滤镜数据URL用作
background-image
(颗粒、噪点、湍流)

Safari's WebKit applies stricter canvas-taint rules than Chrome. When a scene has a
<filter>
SVG element referenced as a
background-image: url("data:image/svg+xml...")
— a common grain/noise pattern —
html2canvas
produces a tainted canvas. Safari's WebGL then throws
SecurityError: The operation is insecure
at
gl.texImage2D()
, which has no framework opt-out (WebGL spec requires the check). Every shader transition falls through to the CSS-crossfade fallback; in Claude Design's cross-origin iframe sandbox this compounds with iframe throttling, and users see the whole piece play as hard cuts.
Empirically observed: skill-test8 in Safari + Claude Design = transitions work. skill-test-9 (identical framework, different grain implementation) in the same environment = zero shader transitions, all catch-handler fallbacks. The only structural difference was this:
css
/* ✖ WRONG — SVG filter as background-image.
   Taints html2canvas's output canvas in Safari → breaks every shader
   transition in Safari + cross-origin iframes. Also measurably slower in
   WebKit than CSS gradients even when it does work. */
.grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.08;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  mix-blend-mode: overlay;
}
css
/* ✓ RIGHT — layered CSS radial-gradient dots. Same grain effect visually,
   pure CSS rendering, zero canvas taint, fast everywhere. */
.grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.18;
  background-image:
    radial-gradient(rgba(255, 255, 255, 0.08) 1px, transparent 1.2px),
    radial-gradient(rgba(0, 0, 0, 0.18) 1px, transparent 1.2px);
  background-size:
    3px 3px,
    5px 5px;
  background-position:
    0 0,
    1px 2px;
  mix-blend-mode: overlay;
}
The same principle applies to other SVG-filter decoratives (paper fiber via
feTurbulence + feDisplacementMap
, CRT scanline overlays built from SVG patterns, etc.). In general, avoid SVG filter data URLs in scene markup — prefer layered CSS gradients,
backdrop-filter
, or solid-color overlays.
Escape hatch for unavoidable SVG effects. If a scene genuinely needs an SVG filter (rare — usually a specific decorative that cannot be replicated in CSS), mark that element with
data-no-capture
. The shader's
captureScene()
already has logic to skip elements with this attribute — it won't enter the html2canvas clone pass, so it can't taint the output canvas. The element will still render live in the browser; it just won't appear in the shader transition textures (which for a grain overlay is usually invisible anyway, since the overlay is typically so subtle and repetitive that not seeing it mid-transition is imperceptible).
html
<!-- SVG decorative element skipped from shader capture only — still renders live -->
<div class="grain svg-filter-grain" data-no-capture></div>
Safari的WebKit比Chrome应用更严格的画布污染规则。当场景有
<filter>
SVG元素作为
background-image: url("data:image/svg+xml...")
——常见的颗粒/噪点模式——
html2canvas
会生成污染的画布。Safari的WebGL会在
gl.texImage2D()
时抛出
SecurityError: The operation is insecure
,框架无法跳过此检查(WebGL规范要求检查)。每个着色器转场都会回退到CSS交叉淡入;在Claude Design的跨源iframe沙箱中,这会与iframe节流叠加,用户看到的内容全是硬切。
经验观察:Safari + Claude Design中的skill-test8转场正常。同一框架的skill-test-9(仅颗粒实现不同)在同一环境中——零着色器转场,全是捕获处理回退。唯一结构差异是:
css
/* ✖ 错误——SVG滤镜作为background-image。
   在Safari中污染html2canvas的输出画布→破坏Safari + 跨源iframe中的每个着色器转场。即使正常工作,在WebKit中也比CSS渐变慢得多。 */
.grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.08;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  mix-blend-mode: overlay;
}
css
/* ✓ 正确——分层CSS径向渐变点。视觉效果相同的颗粒,
   纯CSS渲染,零画布污染,所有环境都快速。 */
.grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.18;
  background-image:
    radial-gradient(rgba(255, 255, 255, 0.08) 1px, transparent 1.2px),
    radial-gradient(rgba(0, 0, 0, 0.18) 1px, transparent 1.2px);
  background-size:
    3px 3px,
    5px 5px;
  background-position:
    0 0,
    1px 2px;
  mix-blend-mode: overlay;
}
同样原理适用于其他SVG滤镜装饰元素(通过
feTurbulence + feDisplacementMap
实现的纸张纹理、SVG图案构建的CRT扫描线叠加等)。一般来说,避免在场景标记中使用SVG滤镜数据URL——优先使用分层CSS渐变、
backdrop-filter
或纯色叠加。
不可避免SVG效果的逃生舱。若场景确实需要SVG滤镜(罕见——通常是无法用CSS复制的特定装饰),为该元素添加
data-no-capture
标记。着色器的
captureScene()
已有逻辑跳过带有此属性的元素——不会进入html2canvas克隆过程,因此不会污染输出画布。元素仍会在浏览器中实时渲染;只是不会出现在着色器转场纹理中(对于颗粒叠加,这通常不可见,因为叠加通常非常微妙且重复,转场中看不到它几乎无法察觉)。
html
<!-- SVG装饰元素仅跳过着色器捕获——仍实时渲染 -->
<div class="grain svg-filter-grain" data-no-capture></div>

Self-review — run this checklist before calling the build done

自我检查——完成构建前运行此清单

Check every item with actual code, not assumptions.
  • Every scene has
    class="scene clip"
    +
    data-start
    +
    data-duration
    +
    data-track-index
    .
  • Non-first scenes have the correct inline style for the path in use. With HyperShader:
    style="opacity:0;"
    ONLY (no
    visibility:hidden
    — it breaks
    captureIncomingScene
    and produces content-fading-into-blank blinks during transitions). Without HyperShader:
    style="visibility:hidden;"
    ONLY (no
    opacity:0
    — nothing animates it back to 1).
  • Scene windows tile end-to-end with no gaps (scene-N's
    data-duration
    = next scene's
    data-start
    − this scene's
    data-start
    ).
  • Every scene has a
    <div class="scene-content">
    wrapper — not just scene-1.
    Scan each scene's opening block and confirm the wrapper is present. Missing on any scene causes boxes/clipped elements during that scene's transition.
  • Animated content is INSIDE
    .scene-content
    ; static decoratives are OUTSIDE.
  • No scene is longer than 5 seconds unless you can name the specific pacing reason (hero hold, cinematic push, silence beat, counter that needs ≥6s of runtime). Scenes of uniform length indicate you divided total duration by scene count instead of designing the rhythm.
  • Every scene is long enough for its text to be read — per the reading-time budget table in Step 3. 11–20 words needs ≥4s; 21–35 words needs ≥6s. The last readable text element in each scene finishes entering by the 50% mark of the scene so the viewer has the second half to actually read.
  • Shader transitions (if used) have the scene boundary strictly INSIDE the transition window —
    transition.time < boundary < transition.time + duration
    .
  • Zero
    tl.set
    /
    tl.to
    /
    tl.from
    /
    tl.fromTo
    on scene containers.
  • Every visible scene > 4s has a Breathe phase — at least one element in continuous motion, not just entrance + static.
  • Every element has a verb (from the verbs table) and an identifiable beat (build / breathe / resolve).
  • No banned fonts. No Inter, Roboto, Playfair, Syne. Check the full list.
  • No
    Date.now()
    ,
    Math.random()
    unseeded,
    repeat: -1
    ,
    setInterval
    , async timeline construction.
  • No
    stagger: { from: "random" }
    — GSAP's random is unseeded (Anti-pattern 2). Use
    from: "start"
    ,
    "center"
    ,
    "end"
    , or a grid origin instead.
  • No exit tweens except on the final scene. Grep every scene for
    tl.to(..., { opacity: 0 })
    ,
    tl.to(..., { autoAlpha: 0 })
    , and
    tl.to(..., { y: <offscreen> })
    — these are Anti-pattern 1 and produce empty-scene captures.
  • No SVG filter data URLs as
    background-image
    (Anti-pattern 4). Grep for
    data:image/svg+xml
    in the CSS — if present, either replace with layered
    radial-gradient
    s (preferred) or add
    data-no-capture
    to the element. SVG filters taint html2canvas's canvas in Safari, killing every shader transition in Safari + cross-origin iframe environments.
  • Minimum font sizes: 60px+ headlines, 20px+ body, 16px+ labels.
    font-variant-numeric: tabular-nums
    on number columns.
  • No full-screen dark linear gradients (H.264 banding). Use radial or solid + localized glow.
  • window.__timelines["<id>"] = tl
    is registered and the id matches
    data-composition-id
    on the root.

对照实际代码检查每一项,不要假设。
  • 每个场景都有
    class="scene clip"
    +
    data-start
    +
    data-duration
    +
    data-track-index
  • 非第一个场景根据使用的模式设置正确的内联样式。使用HyperShader:仅
    style="opacity:0;"
    (不要
    visibility:hidden
    ——会破坏
    captureIncomingScene
    ,导致转场中内容淡入空白的闪烁)。不使用HyperShader:仅
    style="visibility:hidden;"
    (不要
    opacity:0
    ——没有东西会将其动画回1)。
  • 场景窗口无缝衔接,无间隙(场景N的
    data-duration
    = 下一场景的
    data-start
    − 本场景的
    data-start
    )。
  • 每个场景都有
    <div class="scene-content">
    包裹——不仅是场景1。
    扫描每个场景的开头,确认包裹存在。任何场景缺少包裹都会导致该场景转场时出现方框/裁剪元素。
  • 动画内容在
    .scene-content
    内部;静态装饰元素在外部。
  • 场景时长不超过5秒,除非能说明具体节奏原因(核心画面停留、电影镜头推进、静默节拍、需要≥6秒运行时间的计数器)。所有场景时长相同表明你是将总时长除以场景数,而非设计节奏。
  • 每个场景时长足够阅读文本——符合步骤3中的阅读时间预算表。11–20字需要≥4秒;21–35字需要≥6秒。每个场景中最后一个可读文本元素在场景过半前完成入场,以便观众在后半段阅读。
  • 着色器转场(若使用)的场景边界严格在转场窗口内——
    transition.time < boundary < transition.time + duration
  • 场景容器上无
    tl.set
    /
    tl.to
    /
    tl.from
    /
    tl.fromTo
  • 每个可见场景>4秒都有维持阶段——至少一个元素持续动效,而非仅入场+静态。
  • 每个元素都有动词(来自动词表)和可识别的节拍(构建/维持/收尾)。
  • 无禁用字体。无Inter、Roboto、Playfair、Syne。检查完整列表。
  • Date.now()
    、未播种的
    Math.random()
    repeat: -1
    setInterval
    、异步时间线构建。
  • stagger: { from: "random" }
    ——GSAP的随机数未播种(反模式2)。使用
    from: "start"
    "center"
    "end"
    或网格起点。
  • 除最后一个场景外,无退出动画。搜索每个场景中的
    tl.to(..., { opacity: 0 })
    tl.to(..., { autoAlpha: 0 })
    tl.to(..., { y: <屏幕外> })
    ——这些是反模式1,会导致空白场景捕获。
  • 无SVG滤镜数据URL用作
    background-image
    (反模式4)。在CSS中搜索
    data:image/svg+xml
    ——若存在,替换为分层
    radial-gradient
    (优先)或为元素添加
    data-no-capture
    。SVG滤镜在Safari中污染html2canvas的画布,导致Safari + 跨源iframe环境中的每个着色器转场失效。
  • 最小字体尺寸:标题≥60px,正文≥20px,标签≥16px。数字列设置
    font-variant-numeric: tabular-nums
  • 无全屏深色线性渐变(H.264色带)。使用径向渐变或纯色+局部光晕。
  • window.__timelines["<id>"] = tl
    已注册,且id与根元素的
    data-composition-id
    匹配。

Step 5: Deliver

步骤5:交付输出

Gate:
index.html
,
preview.html
,
README.md
, and (when identity was invented)
DESIGN.md
all exist in the project.
preview.html
loads in Claude Design's in-pane preview.
准入条件:项目中存在
index.html
preview.html
README.md
,以及(若确定了原创风格)
DESIGN.md
preview.html
可在Claude Design的面板内预览加载。

preview.html
template (copy verbatim)

preview.html
模板(完全复制)

Claude Design's sandbox requires a
?t=<token>
query on every internal URL. Without token forwarding, the iframe receives a
"preview token required"
placeholder and renders black.
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>HyperFrames Preview</title>
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        background: #111;
        height: 100%;
        overflow: hidden;
      }
    </style>
    <script type="module" src="https://cdn.jsdelivr.net/npm/@hyperframes/player"></script>
  </head>
  <body>
    <hyperframes-player
      id="p"
      controls
      autoplay
      muted
      style="display:block;width:100vw;height:100vh"
    ></hyperframes-player>
    <script>
      document.getElementById("p").setAttribute("src", "./index.html" + location.search);
    </script>
  </body>
</html>
Verbatim means verbatim. No decorative chrome (no header, wordmark, aspect-ratio wrapper, caption bar).
<hyperframes-player>
fills the viewport.
Claude Design沙箱要求每个内部URL都带有
?t=<token>
查询参数。若无令牌转发,iframe会收到
"preview token required"
占位符,渲染为黑色。
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>HyperFrames Preview</title>
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        background: #111;
        height: 100%;
        overflow: hidden;
      }
    </style>
    <script type="module" src="https://cdn.jsdelivr.net/npm/@hyperframes/player"></script>
  </head>
  <body>
    <hyperframes-player
      id="p"
      controls
      autoplay
      muted
      style="display:block;width:100vw;height:100vh"
    ></hyperframes-player>
    <script>
      document.getElementById("p").setAttribute("src", "./index.html" + location.search);
    </script>
  </body>
</html>
完全复制意味着完全一致。不要添加装饰性元素(无页眉、标志、宽高比包裹、标题栏)。
<hyperframes-player>
填充视口。

README.md
template (for the user who downloads the ZIP)

README.md
模板(供下载ZIP的用户使用)

Claude Design can't run CLI commands — the user runs them locally after download. Include these instructions verbatim. Swap
<project-name>
and adjust render flags if the brief needs non-default resolution / fps.
markdown
undefined
Claude Design无法运行CLI命令——用户下载后在本地运行。完全复制以下说明。替换
<project-name>
,若需求需要非默认分辨率/帧率,调整渲染标志。
markdown
undefined

<project-name>

<project-name>

A HyperFrames video composition. Plain HTML + GSAP; rendered to MP4 by the
hyperframes
CLI.
HyperFrames视频合成内容。纯HTML + GSAP;通过
hyperframes
CLI渲染为MP4。

Requirements

要求

Chrome is downloaded automatically on first preview/render. Verify the environment with:
bash
npx hyperframes doctor
npx
downloads the
hyperframes
CLI from npm on first use — no global install required.
  • Node.js 22+nodejs.org
  • FFmpeg
    brew install ffmpeg
    (macOS)·
    sudo apt install ffmpeg
    (Debian/Ubuntu)· ffmpeg.org/download(Windows)
首次预览/渲染时会自动下载Chrome。验证环境:
bash
npx hyperframes doctor
npx
会在首次使用时从npm下载
hyperframes
CLI——无需全局安装。

Preview in your browser

在浏览器中预览

bash
npx hyperframes preview
Opens the HyperFrames Studio at
http://localhost:3002
.
bash
npx hyperframes preview
http://localhost:3002
打开HyperFrames Studio。

Render to MP4

渲染为MP4

bash
npx hyperframes render index.html -o output.mp4
Produces
output.mp4
at 1920×1080 / 30fps by default. Roughly 1–3× real-time on a modern laptop. Use
--fps 60
or
--resolution 3840x2160
to override.
bash
npx hyperframes render index.html -o output.mp4
默认生成1920×1080 / 30fps的
output.mp4
。现代笔记本电脑上速度约为实时的1–3倍。使用
--fps 60
--resolution 3840x2160
覆盖默认设置。

Troubleshooting

故障排除

  • "FFmpeg not found" — install FFmpeg per Requirements.
  • "Node version too old" — install Node 22+.
  • Full docshyperframes.heygen.com.
undefined
  • "FFmpeg not found" — 按要求安装FFmpeg。
  • "Node version too old" — 安装Node 22+。
  • 完整文档hyperframes.heygen.com
undefined

Caveats to surface to the user

需向用户说明的注意事项

When relevant, call these out in your final message:
  • Placeholder assets (stripe patterns, CSS shapes, gradient blocks used where real images/video should go) — tell the user which selectors to replace and with what.
  • Unverified stats or numbers in the composition — label them as illustrative and say where real figures should be confirmed.
  • Any element copied from a real brand's identity — flag that the composition is an original interpretation, not a recreation of branded UI.

相关时,在最终消息中指出:
  • 占位符资产(条纹图案、CSS形状、渐变块,用于替代真实图片/视频)——告诉用户替换哪些选择器,以及替换为内容。
  • 合成内容中未验证的统计数据或数字——标记为示例,并说明需确认真实数据的位置。
  • 任何从真实品牌风格复制的元素——说明合成内容是原创解读,而非品牌UI的还原。

Claude Design sandbox essentials

Claude Design沙箱要点

These are the non-negotiable Claude-Design-specific invariants. All must hold:
  1. Runtime preload.
    index.html
    loads GSAP, then IMMEDIATELY on the next line loads
    @hyperframes/core/dist/hyperframe.runtime.iife.js
    . Without the runtime pre-load, the player reports
    ready
    but
    currentTime
    never advances — the preview is a static frame.
    html
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js"></script>
  2. Preview token forwarding.
    preview.html
    sets the player's
    src
    via the inline script
    document.getElementById("p").setAttribute("src", "./index.html" + location.search)
    — not via
    src=
    on the element. See the verbatim template in Step 5.
  3. data-composition-id
    __timelines
    key match.
    The string on the root element and the key in
    window.__timelines["..."]
    must be identical. Default to
    "main"
    unless the brief specifies otherwise.
  4. HyperShader root-only for multi-act compositions. If you split into sub-compositions, call
    HyperShader.init()
    at the root level only, never inside a sub-comp. See Step 4.
  5. Every scene has a
    <div class="scene-content">
    wrapper
    — not just scene-1. Non-first scenes without this wrapper cause visible boxes / clipped elements / empty placeholders during every shader transition into them, because
    captureIncomingScene()
    can't isolate pre-animation from-state from the shader texture.
  6. Deterministic rendering. No
    Date.now()
    , no unseeded
    Math.random()
    , no
    setInterval
    , no
    setTimeout
    inside timeline construction, no
    repeat: -1
    .

这些是Claude Design特有的不可协商规则。必须全部满足:
  1. 运行时预加载
    index.html
    先加载GSAP,然后立即加载
    @hyperframes/core/dist/hyperframe.runtime.iife.js
    。若无运行时预加载,播放器会报告
    ready
    currentTime
    永不推进——预览为静态帧。
    html
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@hyperframes/core/dist/hyperframe.runtime.iife.js"></script>
  2. 预览令牌转发
    preview.html
    通过内联脚本
    document.getElementById("p").setAttribute("src", "./index.html" + location.search)
    设置播放器的
    src
    ——而非元素上的
    src=
    。见步骤5中的完全复制模板。
  3. data-composition-id
    __timelines
    键匹配
    。根元素上的字符串与
    window.__timelines["..."]
    中的键必须完全相同。默认使用
    "main"
    ,除非需求指定其他值。
  4. 多章节合成内容仅在根元素初始化HyperShader。若拆分为子合成内容,仅在根级别调用
    HyperShader.init()
    ,切勿在子合成内容中调用。见步骤4。
  5. 每个场景都有
    <div class="scene-content">
    包裹
    ——不仅是场景1。非第一个场景缺少包裹会导致每个着色器转场进入该场景时出现可见方框/裁剪元素/空白占位符,因为
    captureIncomingScene()
    无法隔离动画前状态与着色器纹理。
  6. 确定性渲染。无
    Date.now()
    、未播种的
    Math.random()
    setInterval
    、时间线构建中的
    setTimeout
    repeat: -1

Video types quick reference

视频类型速查

TypeDurationScenesAvg sceneFormat (default)
Social ad (IG/TikTok/Reels)10–15s5–82–3s1080×1920 (9:16)
Launch teaser10–20s6–102–3s1920×1080 or 1080×1920
Product demo20–45s8–143–4s1920×1080
Feature announcement15–30s6–122–4s1920×1080
Brand reel20–45s8–143–4s1920×1080
Explainer30–60s12–203–5s1920×1080
Long-form narrative60–180s24–453–5s1920×1080
Default to 1920×1080 at 30fps unless the brief specifies otherwise.

类型时长场景数平均场景时长默认格式
社交广告(IG/TikTok/Reels)10–15秒5–82–3秒1080×1920(9:16)
发布预告10–20秒6–102–3秒1920×1080或1080×1920
产品演示20–45秒8–143–4秒1920×1080
功能发布15–30秒6–122–4秒1920×1080
品牌短视频20–45秒8–143–4秒1920×1080
讲解视频30–60秒12–203–5秒1920×1080
长叙事视频60–180秒24–453–5秒1920×1080
默认使用1920×1080 30fps,除非需求指定其他设置。

References (loaded on demand)

参考资料(按需加载)

Everything critical is inlined above — you should rarely need to fetch more. These fallbacks exist for edge cases.
Foundational — fetch when you hit a pattern this skill doesn't cover:
Feature-specific — fetch only when the brief needs the feature:

关键内容已内联在上方——你很少需要获取更多资料。这些备用资料用于边缘情况。
基础资料——当遇到本技能未覆盖的模式时获取:
特定功能资料——仅当需求需要该功能时获取:

Example prompts users tend to type

用户常用提示示例

Prefer attachment-driven briefs — they produce brand-accurate output. URL-only briefs on SPA homepages produce generic results.
Attachment-driven (strongest):
  • [user drops 3 UI screenshots]
    Use the attached skill. 30s product walkthrough matching these screenshots. Feature-led, 16:9, dark theme.
  • [user drops a brand PDF]
    Use the attached skill. 15s 9:16 teaser for the brand in this PDF. Honor palette and type exactly.
  • [user drops a reference video]
    Use the attached skill. 20s video in the same tonal register as this reference. Match pacing, color, shader character; my copy below.
Pasted-content:
  • Use the attached skill. 30s hero reel with this copy for each scene: [pasted script]. Dark theme, technical, no warmth.
  • Use the attached skill. 45s editorial explainer. Palette: #0a0a0d / #f5f5f7 / #7c6cff. Type: Space Grotesk + JetBrains Mono. Copy below.
URL-only (weakest — may need to ask for attachments):
  • Use the HyperFrames Claude Design skill. Turn https://www.anthropic.com/news/claude-design-anthropic-labs into a 45-second editorial explainer.
    — static article,
    web_fetch
    works here.
  • Use the HyperFrames Claude Design skill. 30-second product video for linear.app.
    — SPA,
    web_fetch
    returns little; ask for screenshots or pivot to the brand's blog/press/Wikipedia.
Sparse (triggers the clarifying question):
  • Use the attached skill. Make me a 30-second launch video for Orbit. Make it cool.
    → expect the clarifying message with 5 options.
Follow-up (skip the question):
  • Cut it to 20 seconds and drop scene 3.
    — continuing an existing composition; build immediately.
优先选择基于附件的需求——可生成符合品牌规范的输出。仅基于URL的SPA主页需求会生成通用内容。
基于附件(最佳):
  • [用户上传3张UI截图]
    使用附加技能。制作30秒产品导览,匹配这些截图。以功能为主,16:9,深色主题。
  • [用户上传品牌PDF]
    使用附加技能。为PDF中的品牌制作15秒9:16预告。严格遵循调色板和字体。
  • [用户上传参考视频]
    使用附加技能。制作20秒视频,与参考视频调性一致。匹配节奏、色彩、着色器风格;文案如下。
粘贴内容:
  • 使用附加技能。制作30秒核心短视频,每个场景使用以下文案:[粘贴脚本]。深色主题,科技感,无温暖风格。
  • 使用附加技能。制作45秒社论讲解视频。调色板:#0a0a0d / #f5f5f7 / #7c6cff。字体:Space Grotesk + JetBrains Mono。文案如下。
仅URL(最差——可能需要请求附件):
  • 使用HyperFrames Claude Design技能。将https://www.anthropic.com/news/claude-design-anthropic-labs转换为45秒社论讲解视频。
    — 静态文章,
    web_fetch
    可正常工作。
  • 使用HyperFrames Claude Design技能。制作30秒linear.app产品视频。
    — SPA,
    web_fetch
    返回内容少;请求截图或转向品牌博客/新闻/维基百科。
信息稀疏(触发澄清问题):
  • 使用附加技能。为Orbit制作30秒发布视频。要酷。
    → 发送包含5个选项的澄清消息。
跟进需求(跳过问题):
  • 缩短到20秒,删除场景3。
    — 基于现有合成内容的跟进;立即制作。