
Srt Hyperframes Editorial Video
- 89 installs
- 12 repo stars
- Updated June 11, 2026
- yangagent/srt-hyperframes-editorial-video-skill
Helps with ai & agent building tasks.
About
srt-hyperframes-editorial-video is a Claude Code skill in the AI & Agent Building category.
- srt-hyperframes-editorial-video
- AI & Agent Building
- AI-coding skill
Srt Hyperframes Editorial Video by the numbers
- 89 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #4,891 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yangagent/srt-hyperframes-editorial-video-skill --skill srt-hyperframes-editorial-videoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 89 |
|---|---|
| repo stars | ★ 12 |
| Last updated | June 11, 2026 |
| Repository | yangagent/srt-hyperframes-editorial-video-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
HyperFrames Editorial Video
把 SRT 字幕文件制作成 HyperFrames + GSAP 的 Editorial 风格视频。只支持 SRT 输入。
调用本 skill 时,用户必须提供 SRT 字幕文件路径。没有 srtPath 时,主 Agent 必须先暂停并 要求用户提供 .srt 文件路径。
制作前必须先选择主题:
- 暗色风格(
dark-editorial) - 牛皮纸风格(
kraft-editorial) - 克莱因蓝风格(
klein-blue) - 鼠尾草绿风格(
sage-green)
SubAgent 使用授权与硬性要求
当用户要求“使用 HyperFrames,根据 SRT 字幕生成视频”或以其他方式触发本 skill 时,视为用户 已经显式要求按本 skill 工作流使用 SubAgent。主 Agent 不得因为通用工具说明中“仅在用户明确 要求 sub-agent 时使用”的限制而跳过本 skill 规定的 SubAgent 阶段。
第 3 步和第 4 步是强制 SubAgent 阶段。若当前运行环境没有可用 SubAgent 工具,主 Agent 必须 立即暂停并向用户说明阻塞原因,不得降级为主 Agent 自行完成这些阶段。
核心原则
- SRT 时间是唯一时间事实源。
- AI 只负责语义分组、视觉转译和局部场景设计。
- 脚本负责 SRT 解析、连续性校验、相对时间计算、creator 分片、scene-plan 校验和最终
composition 组装。
- creator 不直接修改最终
index.html,只写独立 scene 片段。
Path Contract
主 Agent 和所有 SubAgent 统一使用绝对路径:
skillRoot: 当前srt-hyperframes-editorial-video目录绝对路径referencesRoot:{skillRoot}/referencesscriptsRoot:{skillRoot}/scriptsthemesRoot:{skillRoot}/themessrtPath: 用户提供的 SRT 文件绝对路径projectBaseDir:{dirname(srtPath)}/hyperframes-editorial-video-projectsprojectRoot:{projectBaseDir}/{yyyy-mm-dd-hh-mm-ss}
强制要求:
- SubAgent prompt 中必须写入展开后的绝对路径,不要只传变量名。
- 阶段协议文档只能从
referencesRoot读取。 - 脚本只能从
scriptsRoot执行。 - 所有运行态状态必须由主 Agent 显式传递。
工作流
0. 环境预检
进入正式制作前,主 Agent 必须先完成环境预检。预检失败时立即暂停并报告缺失项。
预检命令:
node --version
npm --version
npx hyperframes --version
ffmpeg -version判定规则:
node --version必须是v22.0.0或更高。npm --version必须成功输出版本号。npx hyperframes --version必须成功输出版本号;若首次执行需要联网下载 CLI,应在正式制作前完成。ffmpeg -version必须成功输出版本信息。- 如果任一预检失败,必须明确告诉用户是哪一项环境不符合要求,并附上失败命令、实际输出或缺失原因。
1. 确定主题和 SRT 路径
SRT 字幕文件路径是必须输入条件。用户未提供 SRT 路径时,先暂停并要求用户提供。
用户必须明确选择一个主题。向用户展示主题时优先使用 theme.json 中的 nameZh,并同时标明 对应 themeId:
- 暗色风格(
dark-editorial) - 牛皮纸风格(
kraft-editorial) - 克莱因蓝风格(
klein-blue) - 鼠尾草绿风格(
sage-green)
如果用户未指定主题,先暂停并要求用户从上述中文风格名中选择;不要默认代选。实际执行脚本时 使用括号内的 themeId。
如果用户给的是相对 SRT 路径,主 Agent 必须先解析为绝对路径并确认文件存在。后续所有 步骤统一使用最终确认过的 srtPath。
2. 初始化项目
执行:
node "{scriptsRoot}/init-project.mjs" --srt-path "{srtPath}" --theme "{themeId}"脚本会创建 {dirname(srtPath)}/hyperframes-editorial-video-projects/{timestamp}/,复制模板样式、 复制主题 token,写入 .theme,并创建 src/scenes/ 与 scene-plans/。
3. 生成 groups.json 和 storyboard.json
本阶段必须使用 storyboard SubAgent 执行;用户触发本 skill 即视为已显式授权本阶段使用 SubAgent。主 Agent 禁止自行生成 groups.json 或直接生成 storyboard.json 来替代 storyboard SubAgent。主 Agent 只负责计算并展开:
storyboardReference = {referencesRoot}/storyboard-parser.mdstoryboardScript = {scriptsRoot}/generate-storyboard.mjs
SubAgent 先读取 storyboard-parser.md,再读取 SRT,按语义和时间生成 {projectRoot}/groups.json。随后运行:
node "{storyboardScript}" "{srtPath}" "{projectRoot}/groups.json" "{projectRoot}/storyboard.json"脚本负责解析 SRT、校验 groups 连续性,并生成 storyboard.json。
4. 按 creator 分片并并行实现 scene
主 Agent 读取 {projectRoot}/storyboard.json 后,必须每 10 个 storyboard scene 分配一个 creator。creator 分片是并行 SubAgent 工作单元,不是主 Agent 的本地实现任务。 creator 命名规则固定:
const creatorId = `creator-${String(index + 1).padStart(2, "0")}`;对每个 creator 执行:
node "{scriptsRoot}/generate-creator-scenes.mjs" \
"{projectRoot}/storyboard.json" \
"{creatorId}" \
"10" \
"{projectRoot}/scene-plans/{creatorId}.scenes.json"必须为每个 creator 分片启动独立 creator SubAgent;如果 creator 数量大于 1,必须在同一轮尽量 并行启动全部 creator SubAgent,不要串行启动。每个 creator 只接收自己的 .scenes.json 事实源和 references/scene-creator.md 协议。全局 skill 和 creator 阶段参考文档由 scene-creator.md 按职责和选择结果要求读取,不在主流程重复列出。
主 Agent 禁止直接编写任何 SceneXXX.html、SceneXXX.css 或 SceneXXX.js 文件来代替 creator SubAgent。主 Agent 的职责是分片、启动 creator SubAgent、等待全部 creator 完成、审查 结果、组装 composition 和运行最终校验。
1. 生成 {projectRoot}/scene-plans/{creatorId}.json 2. 运行 validate-scene-plan.mjs 3. 编写 {projectRoot}/src/scenes/SceneXXX.html 4. 编写 {projectRoot}/src/scenes/SceneXXX.css 5. 编写 {projectRoot}/src/scenes/SceneXXX.js 6. 运行 validate-scene-js.mjs
creator 不得修改最终 index.html。
给 creator SubAgent 的 prompt 必须保持低噪声。creatorId 只用于主 Agent 分片和命名文件, 不要传给 SubAgent,也不要把 scene JSON 内联到 prompt。主 Agent 先展开:
scenesDataPath = "{projectRoot}/scene-plans/{creatorId}.scenes.json"
planPath = "{projectRoot}/scene-plans/{creatorId}.json"
tokensPath = "{projectRoot}/styles/tokens.css"
validateScript = "{scriptsRoot}/validate-scene-plan.mjs"
validateJsScript = "{scriptsRoot}/validate-scene-js.mjs"然后使用推荐模板:
你是 HyperFrames Editorial scene creator,负责 scenesDataPath 中列出的所有 scenes。
## 路径
- projectRoot: "{projectRoot}"
- scenesDataPath: "{scenesDataPath}"
- planPath: "{planPath}"
- referencesRoot: "{referencesRoot}"
- tokensPath: "{tokensPath}"
- validateScript: "{validateScript}"
- validateJsScript: "{validateJsScript}"
## 任务
读取 "{referencesRoot}/scene-creator.md",按其中协议完成 scene-plan、校验和
SceneXXX.html/css/js 片段实现。全局 skill 和二级参考文档的读取顺序以 scene-creator.md 为准。
scene-plan 校验命令:
node "{validateScript}" "{planPath}" "{scenesDataPath}"
写完 SceneXXX.html/css/js 后的 JS timing 校验命令:
node "{validateJsScript}" "{projectRoot}" "{scenesDataPath}"
不要修改最终 index.html。完成后按 scene-creator.md 的返回格式报告结果。5. 组装 HyperFrames composition
所有 creator 完成后执行:
node "{scriptsRoot}/generate-composition.mjs" \
"{projectRoot}" \
"{projectRoot}/storyboard.json"脚本会生成最终 {projectRoot}/index.html,创建唯一 paused GSAP timeline,硬切 scene, 并调用每个 scene 片段注册的 builder。
最终 HTML 必须同时服务两套 HyperFrames 事实源:
- GSAP 主 timeline:每个 scene 必须用
tl.addLabel(scene.id, scene.startTime / 1000)注册 label,
并按 SRT 时间硬切显示。
- Studio/调试时间轴:每个
<section id="scene_xxx" class="ed-scene">必须由
generate-composition.mjs 自动补齐 class="clip"、data-start、data-duration 和 data-track-index="0"。data-start 来自 storyScene.startTime / 1000;data-duration 对非最后一个 scene 使用“到下一个 scene 的间隔减 0.001 秒”,避免浮点边界被 npx hyperframes lint 判成 overlapping_clips_same_track。
creator 片段不需要、也不得手写这些 clip 元数据;它们是最终组装脚本的职责。
6. 交付前校验
至少运行:
node "{scriptsRoot}/validate-ed.mjs" "{projectRoot}/index.html" "{projectRoot}/storyboard.json"
npx hyperframes lint "{projectRoot}"
storyboardDerivedTimes=$(node "{scriptsRoot}/generate-inspect-times.mjs" "{projectRoot}/storyboard.json")
npx hyperframes inspect "{projectRoot}" --at "$storyboardDerivedTimes" --jsongenerate-inspect-times.mjs 会从 {projectRoot}/storyboard.json 计算 storyboardDerivedTimes, 格式为逗号分隔的秒数。时间点覆盖每个 scene 的关键画面:
- scene 开始后约 0.2s。
- skeleton 建立后的稳定点,通常为 scene 开始后约 1.0s,短 scene 可适当提前。
- 每个 segment 的
scene.startTime + segment.relativeStart附近。 - scene 结束前约 0.2s。
inspect 不使用 --strict;errors 必须修复,warnings 由主 Agent 结合语义判断是否需要修复。
npx hyperframes lint 必须达到 errorCount: 0。尤其不得出现:
timed_element_missing_clip_classoverlapping_clips_same_track
如果重新生成过 scene 数量、data-start、data-duration 或 class="clip",并且用户正在使用 Studio/调试模式,主 Agent 应备份或清理 {projectRoot}/.thumbnails,让调试时间轴重新读取最新 scene clip 索引。优先使用非破坏性备份目录,例如 .thumbnails.bak-<timestamp>。
例外:ghost 文字允许刻意溢出画面。若 inspect 报出的 text_box_overflow 只发生在 ghost 类装饰性大字上,例如 .ed-ghost-type、.sh-ghost、.es-ghost 或语义等价的 ghost heading/text,且主体内容、字幕、图表、标签和关键视觉没有被遮挡或截断,则视为设计允许的 overflow,不要为了消除该错误去调整 ghost 的位置、字号、bottom、overflow 或裁切方式。 主 Agent 只需在校验总结中注明这些 text_box_overflow 属于 ghost 设计溢出并继续渲染。
7. 渲染最终视频
交付前校验通过后,执行:
npx hyperframes render "{projectRoot}" --quality high --output "{projectRoot}/output.mp4" > "{projectRoot}/render.log" 2>&1
node "{scriptsRoot}/validate-render-log.mjs" "{projectRoot}/render.log"最终视频固定输出到 {projectRoot}/output.mp4。如果文件已存在,主 Agent 必须先确认当前渲染 是否应覆盖旧文件;不要把最终视频输出到默认 renders/ 时间戳目录。
渲染完成后必须检查 {projectRoot}/render.log。如果日志中出现 timeline 未注册、JS exception、 q(...) 被当作 DOM 元素使用、或 GSAP target not found,主 Agent 必须先回到对应 src/scenes/SceneXXX.js 修复,再重新运行 generate-composition.mjs、交付前校验和 render。 不要只修改最终 index.html。
数据结构
storyboard.json
interface Storyboard {
totalDuration: number;
sceneCount: number;
scenes: {
id: string;
startTime: number;
duration: number;
displayDuration?: number;
segments: {
text: string;
relativeStart: number;
relativeDuration: number;
}[];
}[];
}资源导航
assets/template/:HyperFrames composition 样式模板。themes/:内置主题,每个主题包含theme.json和tokens.css。references/storyboard-parser.md:SRT 到 groups/storyboard 的协议。references/scene-creator.md:creator 规划和片段实现协议。references/TIMING-CANON.md:SRT 相对时间和 focus cue 规则。references/VISUAL-SYSTEM.md:token、primitive class 和主题规则。references/BEAT-ARCHETYPES.md:layout 索引、图形化选择规则和 layout 通用契约。references/layout-patterns/:按layout_id渐进读取的单 layout 详细约束。references/MOTION-CANON.md:GSAP 与 HyperFrames 通用动画规则。scripts/init-project.mjs:从 SRT 和主题创建项目。scripts/generate-storyboard.mjs:从 SRT 和 groups 生成 storyboard。scripts/generate-creator-scenes.mjs:为 creator 切分局部 scenesData。scripts/validate-scene-plan.mjs:校验 creator scene-plan。scripts/validate-scene-js.mjs:在 creator 阶段校验 SceneXXX.js timing。scripts/generate-composition.mjs:组装最终index.html。scripts/generate-inspect-times.mjs:从storyboard.json生成inspect --at时间点。scripts/validate-ed.mjs:补充静态检查。scripts/validate-render-log.mjs:检查最终 render 日志中的黑屏级运行时错误。
用户快捷指令
生成 4K 60 帧版本
当用户说“将某个视频生成 4K 60 帧版本”、“生成 4K60”、“导出 4k 60fps”等语义等价指令时, 主 Agent 自动执行本流程,不需要重新走 SRT 分组、scene creator 或 composition 生成流程。
先确定 projectRoot:
- 如果用户给的是 HyperFrames 项目目录,直接使用该目录。
- 如果用户给的是
{projectRoot}/output.mp4、output.mp4或其他位于项目根目录的视频文件,
且同目录存在 index.html,使用该视频文件所在目录作为 projectRoot。
- 如果只能拿到孤立视频文件,且无法从同目录推断出 HyperFrames 项目目录,先要求用户提供对应
HyperFrames 项目目录;npx hyperframes render 是从 composition 重新渲染,不是普通视频转码。
执行前若 {projectRoot}/output-4k.mp4 已存在,必须先询问用户是否覆盖。未明确同意时不要覆盖。
执行:
npx hyperframes render "{projectRoot}" \
--resolution landscape-4k \
--fps 60 \
--quality high \
--output "{projectRoot}/output-4k.mp4" \
> "{projectRoot}/render-4k.log" 2>&1
node "{scriptsRoot}/validate-render-log.mjs" "{projectRoot}/render-4k.log"说明:
landscape-4k输出 3840x2160,适用于本 skill 默认的 1920x1080 横屏 composition。--fps 60输出 60fps;--quality high用于最终交付。- 输出文件固定命名为
{projectRoot}/output-4k.mp4。 - 渲染完成后必须检查
render-4k.log。如果日志中出现 timeline 未注册、JS exception、
q(...) 被当作 DOM 元素使用、或 GSAP target not found,先修复对应 src/scenes/SceneXXX.js,重新运行 generate-composition.mjs,再重新渲染 4K 60 帧版本。
打开调试模式
当用户说“打开调试模式”、“进入调试模式”、“预览调试”等语义等价指令时,主 Agent 自动执行本流程。 调试模式使用 HyperFrames Studio preview。
先确定 projectRoot。如果当前对话没有明确项目目录,先要求用户提供 HyperFrames 项目目录;该目录 必须包含 index.html。
随后要求用户提供调试音频文件路径。拿到音频后:
1. 确认音频文件存在。 2. 如果音频不在 {projectRoot} 内,复制到 {projectRoot}/assets/debug-audio{ext};如果 assets/ 不存在,先创建。 3. 在 {projectRoot}/index.html 中查找固定调试音频槽位,并只替换槽位内部内容。正常生成的 index.html 已经预置该槽位;没有音频时槽位为空,不影响 preview 或 render:
<!-- ED_DEBUG_AUDIO_SLOT_START -->
<!-- Debug audio is inserted here during preview-only debug mode. Keep empty for normal renders. -->
<!-- ED_DEBUG_AUDIO_SLOT_END -->插入音频后应变为:
<!-- ED_DEBUG_AUDIO_SLOT_START -->
<audio
id="ed-debug-audio"
data-start="0"
data-track-index="99"
src="assets/debug-audio.mp3"
data-volume="1"
></audio>
<!-- ED_DEBUG_AUDIO_SLOT_END -->规则:
- 只在
ED_DEBUG_AUDIO_SLOT_START和ED_DEBUG_AUDIO_SLOT_END之间写入或移除调试音频。
不要搜索 <div id="main"> 或 scene 位置来自行决定插入点。
- 如果槽位中已经有旧调试
<audio>,直接替换槽位内部内容,不要重复插入多个<audio>。 - 音频只作为调试辅助,不要写入
src/scenes/SceneXXX.*,不要修改storyboard.json,不要重新分组。 - 如果重新运行
generate-composition.mjs覆盖了index.html,它会保留空槽位;需要重新把调试
音频写入该槽位再 preview。
- 不要调用
audio.play();HyperFrames 负责媒体播放和 timeline sync。
启动 preview:
npx hyperframes preview "{projectRoot}" --port "{port}"选择一个未占用端口,优先从 3002 开始;如果被占用则递增。记录 preview 进程 PID / session, 用于停止调试模式。启动成功后把 Studio URL 告诉用户:
http://localhost:{port}/#project/{project-directory-name}如果 preview 没有自动打开浏览器,则使用系统打开命令打开上述 Studio URL。
调试模式期间,当用户说出“第 N 个场景有什么问题”、“scene_00N 有问题”、“某个画面元素遮挡、 错位、不出现、节奏不对”等具体反馈时:
1. 根据用户描述定位 storyboard scene:优先匹配 scene_00N;若用户说“第 N 个场景”,映射到 SceneNNN.html/css/js 和 scene_00N。 2. 读取对应 src/scenes/SceneNNN.html、SceneNNN.css、SceneNNN.js、相关 scene-plan 和 storyboard.json 片段。 3. 只修改对应 scene 源文件;不要直接修最终 index.html 中拼接后的片段。 4. 重新组装 composition,让 preview 热更新:
node "{scriptsRoot}/generate-composition.mjs" "{projectRoot}" "{projectRoot}/storyboard.json"5. 重新把调试音频写入固定槽位,因为 generate-composition.mjs 会把槽位恢复为空。 6. 保持 preview 运行,让用户在 Studio 中继续查看。调试模式下不要自动运行校验脚本;只有用户明确要求 检查、验收或渲染前确认时,再运行 validate-scene-js.mjs、validate-ed.mjs 或 npx hyperframes inspect。
停止调试模式
当用户说“停止调试模式”、“关闭调试模式”、“退出调试”等语义等价指令时:
1. 清空 {projectRoot}/index.html 中 ED_DEBUG_AUDIO_SLOT_START 到 ED_DEBUG_AUDIO_SLOT_END 之间的临时音频,保留槽位注释本身。 2. 停止之前记录的 npx hyperframes preview 进程 / session。 3. 不删除用户原始音频文件。{projectRoot}/assets/debug-audio{ext} 是调试副本,可保留;只有用户明确要求清理时再删除。 4. 报告调试模式已停止,并说明调试音频已从 timeline 移除。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, initial-scale=1.0" />
<title>HyperFrames Editorial Video</title>
<script src="https://registry.npmmirror.com/gsap/3.14.2/files/dist/gsap.min.js"></script>
<link rel="stylesheet" href="styles/tokens.css" />
<link rel="stylesheet" href="styles/base.css" />
<link rel="stylesheet" href="styles/motion.css" />
<style>
.title-layout {
justify-content: center;
}
.title-meta {
display: flex;
align-items: center;
gap: 24px;
margin-top: 12px;
}
.chart-layout {
display: flex;
gap: 78px;
align-items: center;
}
.chart-copy {
flex: 0 0 38%;
display: flex;
flex-direction: column;
gap: 28px;
}
.chart-headline {
margin: 0;
font-size: 86px;
line-height: 1.08;
}
.chart-note {
max-width: 580px;
font-size: 30px;
line-height: 1.42;
color: var(--text-2);
}
.chart-board {
flex: 1 1 auto;
position: relative;
height: 650px;
padding: 42px 46px;
background: var(--surface-2);
border: var(--rule-w) var(--rule-style) var(--rule);
border-radius: var(--r-sm);
}
.chart-svg {
position: absolute;
inset: 42px 46px 118px;
width: calc(100% - 92px);
height: calc(100% - 160px);
}
.chart-labels {
position: absolute;
left: 72px;
right: 72px;
bottom: 42px;
display: flex;
gap: 32px;
}
.chart-label {
flex: 1 1 0;
display: flex;
flex-direction: column;
gap: 8px;
color: var(--text-2);
}
.chart-label strong {
font-size: 30px;
line-height: 1.1;
}
.chart-label span {
font-size: 18px;
color: var(--text-mute);
}
.chart-stat {
position: absolute;
right: 62px;
top: 42px;
text-align: right;
}
.chart-stat .ed-stat {
font-size: 122px;
}
.transform-layout {
display: flex;
gap: 72px;
align-items: center;
}
.state-copy {
flex: 0 0 42%;
display: flex;
flex-direction: column;
gap: 26px;
}
.state-headline {
margin: 0;
font-size: 84px;
line-height: 1.08;
}
.state-board {
flex: 1 1 auto;
position: relative;
height: 650px;
}
.state-cluster,
.state-result {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
.state-cluster {
flex-wrap: wrap;
gap: 26px;
}
.state-chip {
flex: 1 1 calc(50% - 26px);
min-height: 138px;
padding: 28px;
display: flex;
flex-direction: column;
justify-content: space-between;
background: var(--surface-2);
border: var(--rule-w) var(--rule-style) var(--rule);
border-radius: var(--r-sm);
}
.state-chip strong,
.state-result-block strong {
font-size: 34px;
line-height: 1.1;
}
.state-chip span,
.state-result-block span {
font-size: 20px;
color: var(--text-mute);
}
.state-result {
opacity: 0;
}
.state-result-block {
width: 560px;
min-height: 360px;
padding: 44px;
display: flex;
flex-direction: column;
justify-content: space-between;
background: var(--accent-soft);
border: var(--rule-w) var(--rule-style) var(--accent);
border-radius: var(--r-sm);
}
.state-path {
position: absolute;
inset: 0;
}
</style>
</head>
<body>
<div
id="main"
data-composition-id="main"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="13.5"
>
<!-- ED_DEBUG_AUDIO_SLOT_START -->
<!-- Debug audio is inserted here during preview-only debug mode. Keep empty for normal renders. -->
<!-- ED_DEBUG_AUDIO_SLOT_END -->
<section id="scene-title" class="ed-scene">
<div class="ed-ghost-type" data-anim="ambient" style="left: 980px; top: 112px;">ED</div>
<div class="scene-pad title-layout">
<div class="ed-kicker label-mono" data-anim="kicker">Statement Hero</div>
<h1 class="ed-headline serif-cn" data-anim="title">
先把文字<br />
翻译成 <span class="serif-it ed-accent">visual logic</span>
</h1>
<p class="ed-lead" data-anim="lead">
杂志感负责气质,图形和动画负责理解。
</p>
<div class="title-meta">
<span class="ed-badge" data-anim="badge">TEXT: restrained</span>
<span class="ed-badge" data-anim="badge">GRAPHICS: explanatory</span>
</div>
<hr class="rule ed-draw-line" data-anim="rule" />
</div>
</section>
<section id="scene-chart" class="ed-scene" style="opacity: 0;">
<div class="ed-ghost-type" data-anim="ambient" style="left: -110px; bottom: 44px;">DATA</div>
<div class="scene-pad chart-layout">
<div class="chart-copy">
<div class="ed-kicker label-mono" data-anim="kicker">Chart Explain</div>
<h2 class="serif-cn chart-headline" data-anim="title">
复杂度集中在<br />
<span class="ed-accent">解释动画</span>
</h2>
<p class="chart-note" data-anim="lead">
原文不再整段上屏,而是转成阶段、趋势和关键压力点。
</p>
</div>
<div class="chart-board ed-chart" data-anim="chart-board">
<div class="chart-stat">
<div class="label-mono" data-anim="stat-label">Peak Load</div>
<div class="ed-stat ed-accent" data-anim="counter">0</div>
</div>
<svg class="chart-svg" viewBox="0 0 760 430" aria-hidden="true">
<line class="ed-chart-axis" x1="40" y1="380" x2="720" y2="380" />
<line class="ed-chart-axis" x1="40" y1="80" x2="40" y2="380" />
<rect class="ed-chart-bar" data-anim="bar-a" x="120" y="248" width="104" height="132" />
<rect class="ed-chart-bar" data-anim="bar-b" x="330" y="188" width="104" height="192" />
<rect class="ed-chart-bar is-accent" data-anim="bar-c" x="540" y="96" width="104" height="284" />
<path
class="ed-path"
data-anim="trend"
pathLength="1"
d="M 86 304 C 190 286, 246 256, 316 238 S 468 214, 560 128 S 636 116, 694 92"
/>
<circle class="ed-marker" data-anim="marker" cx="644" cy="96" r="10" />
</svg>
<div class="chart-labels">
<div class="chart-label" data-anim="label">
<strong class="serif-cn">输入整理</strong>
<span class="label-mono">raw text</span>
</div>
<div class="chart-label" data-anim="label">
<strong class="serif-cn">视觉转译</strong>
<span class="label-mono">diagram first</span>
</div>
<div class="chart-label" data-anim="label">
<strong class="serif-cn">动画解释</strong>
<span class="label-mono">gsap + svg</span>
</div>
</div>
</div>
</div>
</section>
<section id="scene-transform" class="ed-scene" style="opacity: 0;">
<div class="ed-ghost-type" data-anim="ambient" style="right: -180px; top: 112px;">STATE</div>
<div class="scene-pad transform-layout">
<div class="state-copy">
<div class="ed-kicker label-mono" data-anim="kicker">Transformation State</div>
<h2 class="serif-cn state-headline" data-anim="title">
从段落墙<br />
重组成解释图
</h2>
<p class="ed-lead" data-anim="lead">
旧状态被压缩、归类、连接,最后形成可观看的结构。
</p>
</div>
<div class="state-board" data-anim="board">
<svg class="ed-svg-layer state-path" viewBox="0 0 760 650" aria-hidden="true">
<path
class="ed-connector is-muted"
data-anim="state-path"
pathLength="1"
d="M 132 154 C 272 250, 394 256, 520 326"
/>
<path
class="ed-connector"
data-anim="state-path"
pathLength="1"
d="M 124 492 C 280 394, 410 382, 520 326"
/>
</svg>
<div class="state-cluster">
<div class="state-chip" data-anim="old-node">
<strong class="serif-cn">长段落</strong>
<span class="label-mono">too much text</span>
</div>
<div class="state-chip" data-anim="old-node">
<strong class="serif-cn">弱动画</strong>
<span class="label-mono">fade only</span>
</div>
<div class="state-chip" data-anim="old-node">
<strong class="serif-cn">无关系</strong>
<span class="label-mono">no diagram</span>
</div>
<div class="state-chip" data-anim="old-node">
<strong class="serif-cn">像 PPT</strong>
<span class="label-mono">static slide</span>
</div>
</div>
<div class="state-result" data-anim="result">
<div class="state-result-block">
<span class="ed-badge">NEW STATE</span>
<strong class="serif-cn">图形对象先建立,文字只负责命名。</strong>
<div class="ed-callout" data-anim="callout">
<span class="ed-marker"></span>
<span class="label-mono">explain with motion</span>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true, defaults: { ease: "power2.out" } });
const peak = { value: 0 };
const counter = document.querySelector("#scene-chart [data-anim='counter']");
gsap.set("#scene-chart, #scene-transform", { opacity: 0 });
gsap.set(".ed-draw-line", { scaleX: 0 });
gsap.set("#scene-chart [data-anim='bar-a'], #scene-chart [data-anim='bar-b'], #scene-chart [data-anim='bar-c']", { attr: { y: 380, height: 0 } });
gsap.set("#scene-chart [data-anim='trend'], #scene-transform [data-anim='state-path']", { strokeDashoffset: 1 });
gsap.set("#scene-chart [data-anim='marker']", { scale: 0, transformOrigin: "center center" });
gsap.set("#scene-transform [data-anim='result']", { opacity: 0, scale: 0.92 });
gsap.set("#scene-transform [data-anim='callout']", { opacity: 0, x: -22 });
tl.addLabel("title", 0);
tl.fromTo("#scene-title [data-anim='kicker']", { y: 22, opacity: 0 }, { y: 0, opacity: 1, duration: 0.55, ease: "power3.out" }, "title+=0.18");
tl.fromTo("#scene-title [data-anim='title']", { y: 64, opacity: 0, filter: "blur(18px)", scale: 0.985 }, { y: 0, opacity: 1, filter: "blur(0px)", scale: 1, duration: 0.95, ease: "expo.out" }, "title+=0.36");
tl.fromTo("#scene-title [data-anim='lead']", { x: -34, opacity: 0 }, { x: 0, opacity: 1, duration: 0.62, ease: "power2.out" }, "title+=0.9");
tl.fromTo("#scene-title [data-anim='badge']", { opacity: 0, y: 16 }, { opacity: 1, y: 0, duration: 0.42, stagger: 0.1, ease: "power3.out" }, "title+=1.08");
tl.to("#scene-title [data-anim='rule']", { scaleX: 1, duration: 0.72, ease: "power4.out" }, "title+=1.22");
tl.to("#scene-title [data-anim='ambient']", { x: -26, y: 12, duration: 3.2, ease: "sine.inOut" }, "title+=0.2");
tl.addLabel("toChart", 3.65);
tl.set("#scene-chart", { opacity: 1 }, "toChart");
tl.set("#scene-title", { opacity: 0 }, "toChart");
tl.addLabel("chart", 3.78);
tl.fromTo("#scene-chart [data-anim='kicker']", { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.45, ease: "power3.out" }, "chart+=0.1");
tl.fromTo("#scene-chart [data-anim='title']", { opacity: 0, x: -44 }, { opacity: 1, x: 0, duration: 0.68, ease: "expo.out" }, "chart+=0.24");
tl.fromTo("#scene-chart [data-anim='lead']", { opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 0.55, ease: "power2.out" }, "chart+=0.62");
tl.fromTo("#scene-chart [data-anim='chart-board']", { opacity: 0, y: 42, scale: 0.985 }, { opacity: 1, y: 0, scale: 1, duration: 0.68, ease: "power3.out" }, "chart+=0.38");
tl.fromTo("#scene-chart [data-anim='stat-label']", { opacity: 0, y: -12 }, { opacity: 1, y: 0, duration: 0.38 }, "chart+=0.82");
tl.to(peak, { value: 87, duration: 1.1, ease: "power3.out", onUpdate: () => { counter.textContent = Math.round(peak.value); } }, "chart+=0.92");
tl.to("#scene-chart [data-anim='bar-a']", { attr: { y: 248, height: 132 }, duration: 0.82, ease: "power4.out" }, "chart+=0.98");
tl.to("#scene-chart [data-anim='bar-b']", { attr: { y: 188, height: 192 }, duration: 0.82, ease: "power4.out" }, "chart+=1.12");
tl.to("#scene-chart [data-anim='bar-c']", { attr: { y: 96, height: 284 }, duration: 0.82, ease: "power4.out" }, "chart+=1.26");
tl.to("#scene-chart [data-anim='trend']", { strokeDashoffset: 0, duration: 1.08, ease: "power3.inOut" }, "chart+=1.22");
tl.fromTo("#scene-chart [data-anim='label']", { opacity: 0, y: 18 }, { opacity: 1, y: 0, duration: 0.42, stagger: 0.1 }, "chart+=1.48");
tl.to("#scene-chart [data-anim='marker']", { scale: 1, duration: 0.42, ease: "back.out(1.7)" }, "chart+=2.02");
tl.to("#scene-chart [data-anim='ambient']", { x: 34, duration: 4.4, ease: "sine.inOut" }, "chart+=0.2");
tl.addLabel("toTransform", 8.35);
tl.set("#scene-transform", { opacity: 1 }, "toTransform");
tl.set("#scene-chart", { opacity: 0 }, "toTransform");
tl.addLabel("transform", 8.48);
tl.fromTo("#scene-transform [data-anim='kicker']", { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.45, ease: "power3.out" }, "transform+=0.1");
tl.fromTo("#scene-transform [data-anim='title']", { opacity: 0, x: -42 }, { opacity: 1, x: 0, duration: 0.68, ease: "expo.out" }, "transform+=0.25");
tl.fromTo("#scene-transform [data-anim='lead']", { opacity: 0, y: 22 }, { opacity: 1, y: 0, duration: 0.55 }, "transform+=0.7");
tl.fromTo("#scene-transform [data-anim='board']", { opacity: 0, y: 34 }, { opacity: 1, y: 0, duration: 0.62, ease: "power3.out" }, "transform+=0.42");
tl.fromTo("#scene-transform [data-anim='old-node']", { opacity: 0, y: 28, scale: 0.96 }, { opacity: 1, y: 0, scale: 1, duration: 0.46, stagger: 0.1 }, "transform+=0.72");
tl.to("#scene-transform [data-anim='state-path']", { strokeDashoffset: 0, duration: 0.86, stagger: 0.16, ease: "power3.inOut" }, "transform+=1.3");
tl.to("#scene-transform [data-anim='old-node']", { x: 84, y: -12, scale: 0.72, opacity: 0.28, duration: 0.72, stagger: { amount: 0.18, from: "edges" }, ease: "power3.inOut" }, "transform+=1.74");
tl.to("#scene-transform [data-anim='result']", { opacity: 1, scale: 1, duration: 0.72, ease: "expo.out" }, "transform+=2.18");
tl.to("#scene-transform [data-anim='callout']", { opacity: 1, x: 0, duration: 0.46, ease: "power3.out" }, "transform+=2.78");
tl.to("#scene-transform [data-anim='ambient']", { x: -24, y: 14, duration: 4.0, ease: "sine.inOut" }, "transform+=0.15");
tl.to("#scene-transform", { opacity: 0, duration: 0.55, ease: "sine.inOut" }, 12.95);
window.__timelines["main"] = tl;
</script>
</body>
</html>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: var(--shell);
color: var(--text);
font-family: var(--font-body);
font-feature-settings: var(--font-features);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
[data-composition-id="main"] {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: var(--surface);
}
.ed-scene {
position: absolute;
inset: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background: var(--surface);
color: var(--text);
}
.ed-scene::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background: var(--surface-vignette);
z-index: 0;
}
.scene-pad {
position: relative;
z-index: 2;
width: 100%;
height: 100%;
padding: var(--stage-pad-y) var(--stage-pad-x);
display: flex;
flex-direction: column;
gap: 32px;
}
.ed-layer-bg,
.ed-layer-main,
.ed-layer-fg {
position: absolute;
inset: 0;
pointer-events: none;
}
.ed-layer-bg { z-index: 1; }
.ed-layer-main { z-index: 2; }
.ed-layer-fg { z-index: 3; }
.serif-cn {
font-family: var(--font-display-cn);
font-weight: 700;
letter-spacing: -0.005em;
}
.serif-it {
font-family: var(--font-display-en);
font-style: italic;
font-weight: 500;
letter-spacing: -0.025em;
}
.label-mono {
font-family: var(--font-mono);
font-size: var(--type-kicker);
font-weight: 500;
letter-spacing: 0.24em;
text-transform: uppercase;
color: var(--text-mute);
}
.hero-num {
font-family: var(--hero-num-font);
font-style: var(--hero-num-style);
font-weight: var(--hero-num-weight);
letter-spacing: var(--hero-num-track);
font-feature-settings: "tnum", "lnum";
font-variant-numeric: tabular-nums;
line-height: 0.9;
}
.ed-svg-layer {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: visible;
pointer-events: none;
}
.rule {
height: 0;
border: 0;
border-top: var(--rule-w) var(--rule-style) var(--rule);
width: 100%;
margin: 0;
transform-origin: left center;
}
.ed-axis {
position: relative;
width: 100%;
height: var(--rule-w);
background: var(--rule);
transform-origin: left center;
}
.ed-axis.is-vertical {
width: var(--rule-w);
height: 100%;
transform-origin: center top;
}
.ed-chart {
position: relative;
color: var(--text);
}
.ed-chart-axis {
stroke: var(--rule);
stroke-width: var(--rule-w);
vector-effect: non-scaling-stroke;
}
.ed-chart-bar {
fill: var(--surface-3);
transform-box: fill-box;
transform-origin: center bottom;
}
.ed-chart-bar.is-accent {
fill: var(--accent);
}
.ed-path,
.ed-connector {
fill: none;
stroke: var(--accent);
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
vector-effect: non-scaling-stroke;
}
.ed-path.is-muted,
.ed-connector.is-muted {
stroke: var(--rule);
}
.ed-callout {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 14px 18px;
background: var(--surface-2);
border: var(--rule-w) var(--rule-style) var(--rule);
border-radius: var(--r-sm);
color: var(--text-2);
font-size: var(--type-callout);
line-height: 1.22;
}
.ed-marker {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid var(--accent);
background: var(--surface);
box-shadow: 0 0 0 8px var(--accent-soft);
}
svg .ed-marker {
width: auto;
height: auto;
border: 0;
border-radius: 0;
fill: var(--surface);
stroke: var(--accent);
stroke-width: 3;
vector-effect: non-scaling-stroke;
transform-box: fill-box;
}
.ed-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 12px;
border: var(--rule-w) var(--rule-style) var(--rule);
border-radius: var(--r-sm);
color: var(--text-mute);
background: var(--surface-2);
font-family: var(--font-mono);
font-size: var(--type-kicker);
font-weight: 500;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.ed-stat {
font-family: var(--font-display-en);
font-size: var(--type-stat);
font-weight: 800;
line-height: 0.85;
letter-spacing: -0.03em;
font-feature-settings: "tnum", "lnum";
font-variant-numeric: tabular-nums;
}
.ed-ghost-type {
position: absolute;
font-family: var(--font-display-en);
font-style: italic;
font-weight: 900;
font-size: var(--type-ghost);
line-height: 0.8;
letter-spacing: -0.04em;
color: var(--ghost-type-color);
opacity: 1;
pointer-events: none;
user-select: none;
white-space: nowrap;
}
.ed-kicker {
display: inline-flex;
align-items: center;
gap: 18px;
font-size: var(--type-kicker);
}
.ed-kicker::before {
content: "";
width: 54px;
height: 1px;
background: var(--accent);
}
.ed-headline {
margin: 0;
max-width: 1380px;
font-size: var(--type-headline-xl);
line-height: 1.04;
}
.ed-lead {
max-width: 820px;
font-size: var(--type-body);
line-height: 1.45;
color: var(--text-2);
}
.ed-body {
font-size: var(--type-body-min);
line-height: 1.52;
color: var(--text-2);
}
.ed-subline {
max-width: 920px;
margin: 0;
font-size: var(--type-body);
line-height: 1.36;
color: var(--text-2);
}
.ed-result-label {
font-size: var(--type-result);
line-height: 1.05;
color: var(--accent);
}
.ed-meta {
font-family: var(--font-mono);
font-size: var(--type-meta);
font-weight: 500;
line-height: 1.35;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-mute);
}
.ed-accent {
color: var(--accent);
}
[data-anim] {
will-change: transform, opacity, filter, clip-path;
}
.ed-draw-line {
transform-origin: left center;
}
.ed-path[data-anim],
.ed-connector[data-anim] {
stroke-dasharray: 1;
stroke-dashoffset: 1;
}
Layout Selection Guide
storyboard scene 对应一个 .ed-scene;SRT 内部拍点由 focusPlan / focusCue 表达。creator 先通过本文的 Layout Selection Table 选择 family、layout_id 和 layout_variant,自检重复度约束后,再只读取实际使用到的 references/layout-patterns/<layout_id>.md。
本文件只负责 layout 选择索引、图形化思考和所有 layout 共用的实现契约。具体 slot、 HTML/CSS skeleton、motion direction 和 layout 专属禁令只写在单个 layout 文件中。 不要一次性读取整个 layout-patterns/ 目录。
选择规则
- 开工先问四件事:这段字幕真正要观众看懂什么?它能被看成关系、大小、方向、顺序、定位、
分组、前后状态或机制吗?这一幕最值得放大的 1 个主对象是什么?画面能否比字幕多提供一个 可见细节,例如数字、标签、证据点、状态差异或机制部件?
- 画面要演事情,不只是把字幕打字上屏。非文字主导 scene 的
body应写可执行视觉对象、
短标签、数据点、节点、证据点或状态差异,不写抽象形容词或字幕摘要。
- 让 LLM 自己决定具体画面对象,但必须落入下方某个
layout_id。图形化优先只在它能产生清楚的
数据、趋势、流程、因果、机制、对比、证据、状态变化或结构关系时成立。如果图形化会变成 牵强扫描、无意义节点、装饰性路径或一堆弱标签,应选择文字主导 layout。
- 非文字主导 layout 必须有真实主视觉对象:数字、图表、连续进程轴、时间轴、中缝分栏、
压力带、中心模型、证据主体或状态转化结构。禁止只用均分矩形、节点阵列、大边框、 卡片网格或简单文字罗列承载语义。
- layout 是高保真视觉架构,不是 wireframe,也不是固定元素清单。creator 必须保留所选 layout 的
核心构图语法、主视觉关系、标题组位置、阅读路径、ghost type、hairline 和单点 accent 逻辑。
layout_id表示语义家族;layout_variant只改变同一语义家族内的构图、阅读路径和主视觉锚点,
不能改变 layout 的语义边界,也不能把一个 layout 自由混搭成另一个 layout。
- 当相邻或近邻 scene 必须重复同一
layout_id时,应优先切换layout_variant拉开视觉节奏。 - 当前 creator 内的 scene-plan 会被
validate-scene-plan.mjs做本地重复度硬校验。写 plan 前必须
先用 Layout Selection Table 完成当前分片的 layout selection ledger,并按表格下方的重复度约束 自检。
- 当某个 layout 或 family 已接近上限时,优先选择更具体的语义 family:
rule-*表达规则边界,
decision-* 表达路径选择,formula-* 表达组成关系,evidence-* 表达证据定位, transform-* 表达前后变化,error-* 表达异常诊断或修复。
statement-*不应成为默认兜底;只有句子本身就是最佳视觉对象时才使用。compare-*过多时,
必须判断当前内容是否其实更适合 rule-*、decision-*、transform-* 或 error-*。
- ghost type 属于统一背景材质。layout 可以决定 ghost 文案、位置、字号比例、贴边裁切和
transform,但不得重写 .ed-ghost-type 的字体、颜色、透明度、字重、字距或 line-height。
- 相邻 scene 应优先选择不同
layout_id拉开节奏。只有当两段内容确实需要同一视觉结构时,才允许
连续使用同一 layout_id,并需在 visual_intent 中说明为什么不能换型。
- 文字主导 layout 不是默认兜底。强判断、情绪冲击、行动号召、转场提问、总结句、必须完整阅读的
核心表达,如果句子本身就是最佳视觉对象,应直接选择文字主导 layout。因为“不知道怎么画”而退回 文字主导 layout 是失败;为了避免文字主导而硬做弱图形同样是失败。
- 数字、比例、排行、计数、明确量化结果:优先
data-*或chart-*。 - 趋势、阶段推进、增长、衰减、历史变化:优先
chart-*或timeline-*。 - 流程、系统链路、步骤、工作流:优先
process-*。 - 因果、原因、阻力、为什么:优先
cause-*、process-*或transform-*。 - 心理机制、概念解释、认知偏差或解释模型:优先
concept-*、cause-*或transform-*。 - 新旧对比、before/after、两种选择、优劣变化:优先
compare-*或transform-*。 - 多候选比较、A/B/C 方案比较、工具/模板/模型横向选择:优先
compare-multi-option-board;不要硬塞
二分 before/after。
- 速度 vs 质量、成本 vs 效果、简单 vs 可控等二维取舍:优先
compare-tradeoff-matrix。 - 罗列、分类、特征并列、优先级:优先
list-*。list-*不是 checklist、process、decision 或
formula;没有检查动作、步骤推进、筛选条件或组合结果时,不要强行使用这些 family。
- 术语解释、概念定义、“是什么 / 不是什么 / 应该如何理解”:优先
definition-*。如果重点是解释模型
内部关系,才使用 concept-*;如果只是强判断句,才使用 statement-*。
- 具体 case 举例、用一个场景说明一句话:优先
example-*。如果画面主对象是真实证据、截图、引用或
来源定位,才使用 evidence-*;如果是错误 / 正确做法,优先 rule-correct-wrong-example。
- 证据、案例、引用、截图要点、来源定位:优先
evidence-*。生活经历、情境引入、开场提问不是
天然 evidence;没有真实证据主体、截图、引用、来源或可标注局部时,不要使用 evidence-*。
- 行动建议:如果是短口号、结论句或需要被完整读到的提醒,使用
statement-*。如果包含清楚的
前后状态、行为变化或替换关系,使用 transform-* 或 compare-*。 “请记住 / 所以 / 下次 / 当你”这类语气或触发词优先进入 kicker、headline 或 subline, 不得作为底部 label、caption 或 final-label。
- 教程中的阈值判断、质量边界、风险边界、是否升级策略:优先
rule-threshold-decision。 - 教程中的错误做法、推荐做法、常见误区、正确示例:优先
rule-correct-wrong-example。 - 检查项、发布前确认、调试复核、风险项定位:优先
checklist-risk-scan。 - 条件判断、根据素材状态或用户条件选择路径:优先
decision-condition-path。 - 多个候选项按筛选标准逐层减少,最终得到推荐候选:优先
decision-criteria-shortlist。 - 现象、异常、失败状态到原因诊断:优先
error-symptom-diagnosis。 - 修正前、修正动作、修正后结果:优先
error-fix-before-after。 - 输入要素组合成结果、方法配方、提示词组成:优先
formula-input-to-result。 - 多个因素中聚焦一个关键变量:优先
formula-key-variable-focus。 - 软件教程、工具教程中的连续操作步骤和当前步骤:优先
sequence-operation-steps。 - 长教程中的当前章节、当前进度、下一步提示:优先
chapter-current-progress。
Video Text Hierarchy Contract
本 skill 输出的是 1920x1080 视频,不是网页、仪表盘或 slide 脚注系统。文字必须像杂志视频 排版一样形成清楚的阅读组。
- 禁止 orphan copy:内容性文字不能孤立出现在画面边角、底部,或远离 headline、图形主体、
状态容器、轴线、节点、quote 的位置。
- 禁止 footnote-style copy:SRT 内容句、结论句、行动提示、心理解释不能被做成
source、caption、final-label、meta rail 中的 16-24px 小字。
caption/source/meta rail只承载真实元信息:来源、单位、时间、编号、索引或极短
分类标签。它们不能承载字幕里的语义内容。
- 来自 SRT 语义内容的文字必须进入 headline、subline、structure label、result label、callout、
axis label、state label 或 quote line。
.label-mono只用于 kicker、编号、单位、来源、极短英文/数字元标签;不得承载中文内容句。- 内容性中文文字最低使用
--type-body-min。结论、结果、行动建议、状态变化结果应使用
--type-result 或 headline 档位。
- kicker 是例外:它可以使用小号 mono 风格,但必须贴靠 headline 组,不能独立出现在底部或角落。
- 如果某个辅助短语找不到明确父对象,应删除、并入 headline/subline,或更换 layout。
Layout 通用契约
- 每个 scene 必须有主视觉锚点:大标题、大数字、核心短语、关键对象、状态对比或关系结构。
- 每个 scene 只放大 1 个 primary object,最多 1-2 个辅助读数或辅助标记;不要把字幕里所有
概念都搬上屏。
- 每个 scene 必须有至少一个 final accent / focus confirmation:关键词、数字、marker、active
node、selected side、final state、crop frame 或 highlight 都可以,但不能整场没有被确认的视觉焦点。 accent 的状态时机以 TIMING-CANON.md 为准。
- 每个 scene 必须使用一个平级
layout_id对应的高保真 layout;不要自由混搭其他 layout 的主结构。 - 每个 scene 必须写入合法
layout_variant。具体 variants 以所选 layout 文件为准;所有 layout
文件都必须声明具名 variants,禁止使用 default。
layout_variant必须在最终 HTML/CSS 中体现为真实构图差异,而不是只改 plan 字段、文字内容或动画节奏。
同一 layout_id 在同个 creator 内重复使用时,后一个 scene 必须明显改变至少一种结构骨架: 主视觉锚点、grid 比例、阅读路径、主对象位置、分区方向、dominant object 面积或辅助信息退让方式。
- 采用 layout 后,以对应
HTML Skeleton和CSS Skeleton为视觉架构起点,复用核心布局框架和视觉层级。 - skeleton 是 variant 的起点,不是所有 variants 的共同终点。选择某个 variant 后,必须按该 variant 的
构图说明改造 skeleton;禁止不同 layout_variant 生成几乎相同的 HTML/CSS,只替换文案。
- 内容优先于模板完整度。可以替换、删减、合并或重排辅助文案、节点数量、SVG 图形、证据内容、
局部尺寸和说明行数;不要为了填满 prototype 示例而伪造指标、步骤、时间点、证据或标签。
- 调整内容填充时,不要破坏 layout 的主轴方向、核心视觉关系、headline 对齐、阅读路径和
designer prototype 的整体美感。
- JS 动画只绑定实际渲染出来的
data-animhooks;不要为了满足 hook 名单制造空元素,也不要
临时发明一套与骨架无关的动画目标。
- primary object 对应一个 primary visual family,例如
headline、number、bar、curve、
state container、timeline、process rail、core model、evidence。 同一 scene 中可以有辅助元素,但只能服务 primary visual family。
- 大型视觉对象必须来自
visibleContent、body或screenShouldShow,包括 chart、曲线、状态容器、
evidence surface、highlight region、大色块、巨大数字和流程轨道。
- layout skeleton 是视觉语法示例,不是固定数量模板。可见内容对象、列表项、节点、步骤、候选项、
检查项、指标和标签必须按 visibleContent 的真实内容增减,禁止为了填满 skeleton 示例新增概念。
- headline、body、result、meta、focus 应落入所选 layout 的对应阅读区域。内容性文字不能进入
source、caption 或 meta rail,除非它已经被改写成真实元信息。
- body 中的主图形对象应进入 layout 的主视觉区域,并占据主视觉面积;曲线、图表、状态容器、
流程轨道不能被画成小 inset,除非 layout 明确允许。
- 主视觉区域内只能有一个 dominant visual object。bar、curve、巨大数字、状态容器、
证据主体、流程轨道和节点图不能同权重并列。
- 如果确实需要同时出现两类图形对象,必须明确主次:primary 对象承担画面解释,secondary
对象只能作为 marker、meta、reference、ghost layer 或贴靠 primary 的辅助读数。
- 不要添加 skeleton、
body或screenShouldShow未声明的大矩形、色块、surface、highlight
region 或大型图形对象。
- 禁止自由漂浮元素。accent word、formula、meta、callout、marker、subline 必须贴靠标题组、
图形主体、结构线、状态容器、轴线或节点。不要创建底部脚注区。
- 非纯文字 layout 的元素应贴合 layout 明确给出的 rail、axis、container、zone、band 或 stack;
禁止用 position:absolute 把节点散放到画面四角,除非 layout 明确给出固定槽位。
- layout 必须能承载
TIMING-CANON.md要求的 initial / progression / final 三态。
Layout Selection Table
在读取任何 layout-patterns/<layout_id>.md 前,creator 只能通过下表选择 family、layout_id 和候选 layout_variant。先用表格完成当前 creator 分片的 plan-stage selection ledger,检查重复度约束后, 再读取实际使用到的 layout 文件。
硬性重复度约束:
- 同一
layout_id + layout_variant组合最多出现 1 次。 - 同一
layout_id最多出现 2 次。 - 同一 layout family 通常最多出现 3 次;短分片以
validate-scene-plan.mjs为准。
| family | layout_id | layout_variant | 作用 | 适用场景 |
|---|---|---|---|---|
| statement | statement-editorial | cover-left-anchor | 左锚定杂志封面式大标题 | 稳定判断、总结、承诺 |
| statement | statement-editorial | center-quote-lockup | 居中大标题锁定一句核心话 | 一句话就是画面主体的结论 |
| statement | statement-editorial | vertical-rail | 竖向 rail + 横向大标题 | 原则、命令、章节感主张 |
| statement | statement-editorial | cropped-keyword | 巨大裁切关键词带动标题组 | 可抽取强关键词的短句 |
| statement | statement-diagonal-command | slash-command | 斜向压边命令构图 | 停止、转向、砍掉、重选 |
| statement | statement-diagonal-command | edge-pressure | 标题贴边形成压迫感 | 强提醒、警告、立即行动 |
| statement | statement-diagonal-command | split-impact | 斜线切分命令词和补充句 | “从 A 切到 B”的决断句 |
| quote | quote-oversized | center-monument | 居中纪念碑式 quote | 需要完整读完的 takeaway |
| quote | quote-oversized | cropped-mark | 裁切引号或关键词托住正文 | 情绪强、短而重的句子 |
| quote | quote-oversized | wide-breathing | 宽版留白的安静 quote | 慢节奏、沉稳结论 |
| quote | quote-margin-index | margin-ledger | quote + 边注账本系统 | 有出处、编号、日期、关键词索引 |
| quote | quote-margin-index | side-index | 竖向 index rail + quote | 带章节、主题编号或关键词 |
| quote | quote-margin-index | archive-strip | 档案条组织来源信息 | 摘录、记录、文件感 quote |
| data | data-dominant-number | center-stat | 中心巨大数字 | 单个冲击数字、比例、计数 |
| data | data-dominant-number | left-stat-right-proof | 左数字 + 右支撑读数 | 数字需要上下文证明 |
| data | data-dominant-number | bottom-metric-deck | 主数字 + 底部辅助指标 | 有 2-3 个真实支撑指标 |
| data | data-cropped-ruler | edge-cropped-scale | 贴边裁切数字 + 标尺 | 比例、压缩率、损耗、占比 |
| data | data-cropped-ruler | vertical-ruler | 纵向标尺定位数值 | 上下限、等级、边界判断 |
| data | data-cropped-ruler | horizontal-gauge | 横向 gauge 显示位置 | 进度、距离、区间位置 |
| chart | chart-dominant-marker | right-chart-left-claim | 左结论 + 右大图表 | 图表证明一个结论 |
| chart | chart-dominant-marker | full-bleed-chart | 图表近全画面主导 | 趋势或拐点本身最重要 |
| chart | chart-dominant-marker | cropped-curve-marker | 放大裁切曲线和 marker | 峰值、异常点、转折点 |
| chart | chart-interval-stability | banded-range | 稳定带 / 区间带主导 | 波动范围、上下限、稳定区间 |
| chart | chart-interval-stability | stacked-intervals | 多个区间上下堆叠 | 阶段区间、耗时段、范围对比 |
| chart | chart-interval-stability | threshold-window | 阈值窗口判断当前区间 | 合格区、风险区、边界判断 |
| process | process-advancing-spine | horizontal-spine | 横向连续流程轴 | 3-5 步工作流或系统链路 |
| process | process-advancing-spine | stepped-rail | 阶梯式推进轨道 | 逐步升级、层层加工 |
| process | process-advancing-spine | endpoint-emphasis | 放大终点结果 | 字幕重点落在最终产出 |
| process | process-descending-construction | diagonal-drop | 斜向下降加工路径 | 任务一路被加工成结果 |
| process | process-descending-construction | cascade-build | 瀑布式层层落位 | 多输入或步骤累积成结果 |
| process | process-descending-construction | layered-descent | 分层向下构建 | 上游材料到输出层的顺序关系 |
| timeline | timeline-alternating-axis | alternating-axis | 上下交错横向时间轴 | 多阶段沿时间推进 |
| timeline | timeline-alternating-axis | compressed-milestones | 压缩里程碑轴 | 长时间跨度的关键节点 |
| timeline | timeline-alternating-axis | turning-point-focus | 放大单个转折点 | 某个关键变化是重点 |
| timeline | timeline-compressed-tempo | compression-band | 压缩时间带 | 周期缩短、等待减少、节奏加快 |
| timeline | timeline-compressed-tempo | before-after-tempo | 前后节奏对照 | 旧流程慢、新流程快 |
| timeline | timeline-compressed-tempo | sprint-lanes | 多条短节奏 lane | 多任务流或批次被压缩 |
| list | list-priority-stack | ranked-column | 纵向优先级列表 | 最重要三点、从高到低排序 |
| list | list-priority-stack | hero-first-stack | 第一项作为主视觉 | 先做什么、第一优先级需要放大 |
| list | list-priority-stack | compressed-priority-deck | 多项压缩成层级 deck | 较多优先项但仍需主次 |
| list | list-grouped-catalog | sectioned-ledger | 分组账本式分类罗列 | 几类素材、几种情况、类型归纳 |
| list | list-grouped-catalog | two-group-columns | 两类对象并列分组 | 两组类别、两类情况 |
| list | list-grouped-catalog | cluster-catalog | 多类别分区 catalog | 多组分类清单、类别目录 |
| list | list-feature-board | single-feature-focus | 主特征放大,辅助特征贴靠 | 多个特征里聚焦一个关键特征 |
| list | list-feature-board | offset-feature-board | 主特征与辅助特征错位排布 | 特征并列但需要主次层级 |
| list | list-feature-board | editorial-feature-grid | 非均分 editorial feature grid | 特征较多但不能做平均网格 |
| compare | compare-editorial-split | vertical-split | 左右中缝分栏 | before/after、旧方案/新方案 |
| compare | compare-editorial-split | diagonal-split | 斜向切分对比 | 强烈转向、从混乱切到清晰 |
| compare | compare-editorial-split | top-bottom-split | 上下状态对照 | 过程前后、输入输出 |
| compare | compare-editorial-split | asymmetric-35-65 | 不对称推荐对比 | 结论明显偏向一侧 |
| compare | compare-asymmetric-recommendation | hero-recommendation | 推荐侧主视觉 | 新方案明显占主导 |
| compare | compare-asymmetric-recommendation | narrow-before-wide-after | 窄 before + 宽 after | before/after 转换,after 是记忆点 |
| compare | compare-asymmetric-recommendation | proof-stack-recommendation | 推荐侧叠放证明点 | 推荐理由比旧侧细节重要 |
| compare | compare-multi-option-board | option-columns | 多候选横向列 | A/B/C 方案、工具、模板、模型比较 |
| compare | compare-multi-option-board | winner-highlight | 推荐候选提权 | 多候选中有明确推荐项 |
| compare | compare-multi-option-board | criteria-row-board | 标准 rail 对齐候选 | 候选需要按同一标准横向比较 |
| compare | compare-tradeoff-matrix | quadrant-map | 二维象限权衡 | 速度/质量、成本/效果取舍 |
| compare | compare-tradeoff-matrix | axis-tradeoff | 两条取舍轴为主视觉 | 简单/可控、快/稳等双轴关系 |
| compare | compare-tradeoff-matrix | sweet-spot-marker | 推荐落点 marker | 需要确认目标区间或平衡点 |
| cause | cause-pressure-chokepoint | left-pressure-right-result | 压力带 → 阻断点 → 结果 | 清楚单向因果 |
| cause | cause-pressure-chokepoint | center-chokepoint | 居中放大瓶颈 | 瓶颈本身是主焦点 |
| cause | cause-pressure-chokepoint | diagonal-pressure-flow | 斜向压力流 | 下坠、失控、被挤压的因果 |
| cause | cause-stacked-pressure | stacked-bands | 多条压力带叠加 | 多个原因共同压出结果 |
| cause | cause-stacked-pressure | weight-column | 压力垂直堆成重量柱 | 负担累积、阻力越来越重 |
| cause | cause-stacked-pressure | pressure-slab | 压力压成 slab 指向结果 | 复杂原因整体压缩成后果 |
| concept | concept-core-satellite | left-copy-orbit | 左文案 + 右核心卫星图 | 标题解释模型,diagram 承载关系 |
| concept | concept-core-satellite | center-orbit | 核心居中,卫星环绕 | 核心概念明确、外围项适中 |
| concept | concept-core-satellite | layered-rings | 核心 + 多层环形结构 | 层级能力、内外圈机制 |
| concept | concept-core-satellite | side-stack-core | 一侧核心 + 一侧堆叠模块 | 少量模块或 2-3 个关联项 |
| concept | concept-sectional-layer | layered-section | 分层剖面 | 输入层、能力层、核心层、输出层 |
| concept | concept-sectional-layer | cutaway-core | 核心 cutaway + 周围层级 | 解释系统内部组成 |
| concept | concept-sectional-layer | vertical-strata | 纵向地层结构 | 从基础到结果的概念层级 |
| definition | definition-term-breakdown | term-left-breakdown | 术语锚点 + 拆解说明 | 定义术语、解释概念 |
| definition | definition-term-breakdown | center-term-radial | 术语居中,拆解项贴靠 | 核心术语需要被完整读到 |
| definition | definition-term-breakdown | keyword-cutaway | 关键词局部放大拆解 | 把抽象词拆成可见部件 |
| definition | definition-not-this-that | rejected-definition | 错误定义降权或划掉 | 纠正误解、撤掉错误理解 |
| definition | definition-not-this-that | replacement-lockup | 正确定义放大确认 | 重新定义、锁定正确理解 |
| definition | definition-not-this-that | two-line-correction | 不是 X / 而是 Y 的短句纠偏 | “不是 X,而是 Y” |
| evidence | evidence-annotations | document-annotation | 证据主体 + 局部标注 | 完整截图、文档、案例证明 |
| evidence | evidence-annotations | zoom-callout | 全貌 + 放大局部 | 证据中的某个细节是重点 |
| evidence | evidence-annotations | side-proof-board | 证据 + 侧边 proof board | 证据和 1-2 个证明读数并列 |
| evidence | evidence-final-file-proof | hero-file-proof | 最终文件作为主视觉 | 强调结果对象已经完成 |
| evidence | evidence-final-file-proof | spec-readout | 交付物 + 规格读数 | 输出格式、尺寸、质量参数证明 |
| evidence | evidence-final-file-proof | delivery-poster | 交付物 proof poster | 结尾或确认交付场景 |
| example | example-case-card | hero-example | 具体 case 作为主视觉 | 举例说明、case 引入 |
| example | example-case-card | annotated-example | case 主体 + 局部关键标注 | 用一个具体场景解释一句话 |
| example | example-case-card | example-result-pair | case 与结果读数并列 | 示例需要同时看到前后结果 |
| transform | transform-fragments-to-block | scattered-to-block | 散碎 fragments 到稳定块 | 混乱、碎片到结构化结果 |
| transform | transform-fragments-to-block | split-before-after | 左右 before/after + seam | 前后状态都需要阅读 |
| transform | transform-fragments-to-block | funnel-compress | 碎片经漏斗压缩成块 | 筛选、归并、压缩动作 |
| transform | transform-compression-release | center-compressor | 中心压缩器处理输入 | 处理动作本身是重点 |
| transform | transform-compression-release | cloud-to-output | 碎片云收拢为输出 | 零散素材或想法整理成产物 |
| transform | transform-compression-release | release-slab | 压缩后释放结果 slab | 压实后成为可交付结构 |
| rule | rule-threshold-decision | threshold-line | 阈值线 + 当前 marker | 清楚边界判断 |
| rule | rule-threshold-decision | zone-map | 多区域规则图 | 多状态规则:观察、调整、处理 |
| rule | rule-threshold-decision | gauge-marker | 仪表式边界判断 | 等级、风险、质量边界 |
| rule | rule-correct-wrong-example | wrong-right-split | 错误 / 正确左右分区 | 两种做法并列阅读 |
| rule | rule-correct-wrong-example | corrected-example-focus | 错误示例被修正为正确示例 | 强调具体改法 |
| rule | rule-correct-wrong-example | rule-lockup | 最终规则为主视觉 | 字幕重点是可记忆规则 |
| checklist | checklist-risk-scan | side-copy-board | 左文案 + 右检查板 | 多项扫描且标题需解释目的 |
| checklist | checklist-risk-scan | full-board-callout | 检查板占据主画面 | 检查项较多,风险定位为主体动作 |
| checklist | checklist-risk-scan | vertical-scan-rail | 纵向扫描轨道 | 从上到下排查或阶段检查 |
| checklist | checklist-risk-scan | radar-focus | 中心风险 + 环绕检查项 | 少量检查项围绕核心风险 |
| decision | decision-condition-path | forked-path | 分叉路径 | 一个条件分出多条行动路线 |
| decision | decision-condition-path | decision-diamond | 中心判断菱形 + 出口 | 软件教程或流程中的判断点 |
| decision | decision-condition-path | highlighted-route | 完整路径弱态,当前路线高亮 | 路径较长但只聚焦当前选择 |
| decision | decision-criteria-shortlist | funnel-shortlist | 漏斗式筛选 | 候选逐层减少得到推荐 |
| decision | decision-criteria-shortlist | criteria-lanes | 多标准 lane | 标准本身需要被看见 |
| decision | decision-criteria-shortlist | elimination-board | 候选板逐项降权 | 候选清单比漏斗层级更重要 |
| error | error-symptom-diagnosis | symptom-scan | 扫描现象定位异常 | 从表面问题进入诊断 |
| error | error-symptom-diagnosis | root-cause-lock | 锁定根因作为主视觉 | 字幕直接指出原因 |
| error | error-symptom-diagnosis | split-symptom-cause | 现象和原因分区 | 现象和原因都需要阅读 |
| error | error-fix-before-after | three-stage-fix | before → fix → after 三段路径 | 完整展示修正过程 |
| error | error-fix-before-after | repair-spotlight | 放大修正动作 | 改法本身是字幕重点 |
| error | error-fix-before-after | before-after-rebuild | 前后重建对照 | 结构被重排、替换或对齐 |
| formula | formula-input-to-result | ingredient-grid-output | 输入网格汇入结果块 | 多个组成部分共同产出结果 |
| formula | formula-input-to-result | equation-rail | 公式轨道串联输入和结果 | 方法公式、提示词组成、配方结构 |
| formula | formula-input-to-result | mixer-core | 中心混合区合成输出 | 组合、融合、生成动作 |
| formula | formula-key-variable-focus | variable-spotlight | spotlight 锁定关键变量 | 变量明确且需要放大影响 |
| formula | formula-key-variable-focus | factors-orbit | 多因素围绕核心,关键项确认 | 真实因素并列但只有一个关键 |
| formula | formula-key-variable-focus | ranked-dropoff | 因素递减后聚焦关键变量 | “真正影响的是 X” |
| sequence | sequence-operation-steps | command-rail | 命令轨道 | 短操作串、命令顺序、工具步骤 |
| sequence | sequence-operation-steps | current-step-zoom | 当前步骤放大 | 字幕只讲一个关键操作 |
| sequence | sequence-operation-steps | stacked-actions | 纵向堆叠操作动作 | 连续点击、设置、短任务序列 |
| chapter | chapter-current-progress | chapter-stack | 已完成、当前、下一步堆叠 | 长视频章节定位 |
| chapter | chapter-current-progress | progress-rail | 进度 rail + 当前节点 | 长教程中段定位 |
| chapter | chapter-current-progress | current-island | 当前章节孤岛式主块 | 强调“现在进入这里” |
cause-pressure-chokepoint
family: cause
适用:原因、阻力、后果、反馈环、分支影响或“为什么会这样”的解释。
Layout Contract
- prototype 中的 cause、pressure、result 数量只是示例;按真实内容增减,但保持压力到结果的视觉框架。
- 因果必须呈现为压力带 → 阻断点 → 结果区的方向结构。
- cause 是 stacked pressure band,blocker 是圆形 chokepoint,effect 是编辑式结果区。
- accent 只落在最强 cause、chokepoint 或结果关键词。
- 压力源文字必须可读;可以通过尺寸、边线强弱和位置建立层级,但不要把主要 cause 文案压到
var(--text-mute) 加低 opacity 的双重弱化状态。
- 禁止三栏等权大盒子、两个卡片加箭头、无标签色块、底部说明。
Layout Variants
layout_variant 必须选择下列之一。variant 只改变 cause family 内压力、阻断点和结果区的方向; cause、chokepoint 和 result 必须来自真实内容。
left-pressure-right-result:默认左侧压力带 → 中央阻断点 → 右侧结果区。适合清楚的单向因果。
保持压力到结果的方向结构。
center-chokepoint:阻断点居中放大,压力从一侧或多侧汇入。适合瓶颈本身是主焦点的解释。
结果区不能抢走 chokepoint。
diagonal-pressure-flow:斜向压力流穿过阻断点到结果。适合因果带有下坠、失控或被挤压的语义。
不画自由箭头。
Content Adaptation Contract
HTML Skeleton/CSS Skeleton展示核心视觉语法,不是固定数量模板;不要逐字复制示例内容。- 可见内容必须来自 scene-plan 的
visibleContent、headline、body或screenShouldShow;禁止为填满列表、节点、格子、步骤、候选项、检查项或指标而新增概念。 - 按真实内容增减、合并、重排 slot;如果真实内容少于示例,保留主视觉关系并删除多余槽位。
- 压力源、原因项、阻断点和结果项按真实因果链生成;原因不足示例数量时,用更大的主压力区或结果区承载重点,不补虚构原因。
- 被 narration 点名的 cause 项,颜色至少使用
var(--text-2);只有辅助说明.s可以使用
var(--text-mute)。
Motion Direction
适合因果、关系、依赖和从 A 到 B 的解释。
动作组合:
- cause/source 节点先出现。
- 中间的阶段轨道、影响区域或状态 band 被绘制,方向清楚。
- effect/target 区域跟随阶段推进出现。
- 阻断点、关键阶段或影响区用 marker / accent 确认为当前状态。
节奏特点:让观众看到关系被建立,而不是只看到两个卡片和一个箭头。
结构稳定规则:
- connector、impact path、impact band、timeline rail 这类结构线在语义绘制完成后必须稳定保持。
- 禁止对结构线本体使用
yoyo、repeat、反向 scale、左右位移或任何会改变端点、长度、方向、
位置的 ambient 动画。
- 若需要呼吸感,只能作用在 glow、box-shadow、filter、opacity、marker、背景纹理或独立 wrapper 上;
不要让主线伸缩、漂移或来回移动。
- marker / accent 可以轻微 pulse,但 connector 本体应该像编辑图解一样在确认后稳住。
Design Intent
左侧多条 cause 像压力带一样层层堆叠,越靠近阻断点权重越高;中间是橙色圆形 seal,表达卡住的 瓶颈;右侧用竖向 hairline 分隔出结果区,放大后果主句和短解释。画面看起来是流向,而不是 三个并排面板。
HTML Skeleton
<section id="scene_xxx" class="ed-scene" style="opacity: 0;">
<div class="scene-pad ce-layout">
<span class="ed-ghost-type ce-ghost" data-anim="ghost">Cause</span>
<div class="ce-head">
<span class="ed-kicker label-mono ce-kicker" data-anim="kicker">CAUSE MAP</span>
<h1 class="serif-cn ce-title" data-anim="headline">三股阻力,<span class="ed-accent">卡在同一个点</span></h1>
</div>
<div class="ce-flow" data-anim="flow">
<div class="ce-causes" data-anim="causes">
<div class="ce-cause" data-anim="cause"><div class="t">原因一</div><div class="s">短说明</div></div>
<div class="ce-cause" data-anim="cause"><div class="t">原因二</div><div class="s">短说明</div></div>
<div class="ce-cause is-strong" data-anim="cause"><div class="t">关键原因</div><div class="s">短说明</div></div>
</div>
<div class="ce-block" data-anim="blocker">
<span class="ce-arrow">-></span>
<div class="ce-seal">阻断</div>
<span class="label-mono ce-label">CHOKEPOINT</span>
</div>
<div class="ce-result" data-anim="effect">
<div class="label-mono ce-result-k">RESULT</div>
<div class="serif-cn ce-result-h">结果主句</div>
<p>贴靠结果区的解释句。</p>
</div>
</div>
</div>
</section>CSS Skeleton
#scene_xxx .ce-layout {
position: relative;
justify-content: space-between;
}
#scene_xxx .ce-kicker {
display: inline-flex;
align-items: center;
gap: 18px;
margin-bottom: 28px;
color: var(--text-mute);
letter-spacing: 0.32em;
}
#scene_xxx .ce-kicker::before {
content: "";
width: 44px;
height: 2px;
background: var(--accent);
}
#scene_xxx .ce-title {
margin: 0;
font-size: 74px;
line-height: 1.05;
}
#scene_xxx .ce-flow {
flex: 1;
display: grid;
grid-template-columns: 1.1fr 0.5fr 1.1fr;
align-items: center;
}
#scene_xxx .ce-causes {
display: flex;
flex-direction: column;
gap: 18px;
}
#scene_xxx .ce-cause {
position: relative;
padding: 22px 30px;
background: var(--surface);
border-left: 3px solid var(--rule);
}
#scene_xxx .ce-cause:nth-child(1) { border-left-color: var(--rule); }
#scene_xxx .ce-cause:nth-child(2) { border-left-color: var(--rule); }
#scene_xxx .ce-cause.is-strong { border-left-color: var(--accent); }
#scene_xxx .ce-cause .t {
font-family: var(--font-display-cn);
font-size: 32px;
color: var(--text-2);
}
#scene_xxx .ce-cause .s {
margin-top: 8px;
color: var(--text-mute);
font-size: 19px;
}
#scene_xxx .ce-block {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
}
#scene_xxx .ce-arrow {
font-family: var(--font-mono);
font-size: 40px;
color: var(--accent);
}
#scene_xxx .ce-seal {
width: 120px;
height: 120px;
border: 2px solid var(--accent);
border-radius: 50%;
display: grid;
place-items: center;
font-family: var(--font-display-cn);
font-size: 30px;
color: var(--accent);
box-shadow: 0 0 0 9px var(--accent-soft);
}
#scene_xxx .ce-label {
color: var(--text-mute);
}
#scene_xxx .ce-result {
padding-left: 70px;
border-left: var(--rule-w) var(--rule-style) var(--rule);
}
#scene_xxx .ce-result-k {
margin-bottom: 22px;
color: var(--text-mute);
letter-spacing: 0.2em;
}
#scene_xxx .ce-result-h {
font-size: 58px;
line-height: 1.12;
}
#scene_xxx .ce-result p {
max-width: 26ch;
margin: 28px 0 0;
color: var(--text-mute);
font-size: 23px;
line-height: 1.55;
}
#scene_xxx .ce-ghost {
right: 5%;
top: 6%;
font-size: 190px;
}Animation Hooks
ghost, kicker, headline, flow, causes, cause, blocker, effect.
Do Not Change
不要把 cause、blocker、effect 做成三个同权重矩形;不要画自由箭头;不要让解释句脱离结果区; 不要用低 opacity 让主要 cause 文案不可读。
cause-stacked-pressure
family: cause
适用:原因、阻力、后果、反馈环、分支影响或“为什么会这样”的解释。
Layout Contract
- prototype 中的 cause、pressure、result 数量只是示例;按真实内容增减,但保持压力到结果的视觉框架。
- 因果必须呈现为压力带 → 阻断点 → 结果区的方向结构。
- cause 是 stacked pressure band,blocker 是圆形 chokepoint,effect 是编辑式结果区。
- accent 只落在最强 cause、chokepoint 或结果关键词。
- 禁止三栏等权大盒子、两个卡片加箭头、无标签色块、底部说明。
Layout Variants
layout_variant 必须选择下列之一。variant 只改变 cause family 内多重压力如何叠加和压出结果; 压力项与结果必须来自真实内容。
stacked-bands:默认多条压力带叠加。适合多个原因共同压出一个结果。
最强压力或最终结果使用单点 accent。
weight-column:压力项垂直堆叠成重量柱。适合强调负担累积、阻力越来越重。
柱体不能变成普通列表。
pressure-slab:多个压力被压成一块 slab 指向结果。适合表达复杂原因被整体压缩成后果。
slab 仍要能读出真实压力来源。
Content Adaptation Contract
HTML Skeleton/CSS Skeleton展示核心视觉语法,不是固定数量模板;不要逐字复制示例内容。- 可见内容必须来自 scene-plan 的
visibleContent、headline、body或screenShouldShow;禁止为填满列表、节点、格子、步骤、候选项、检查项或指标而新增概念。 - 按真实内容增减、合并、重排 slot;如果真实内容少于示例,保留主视觉关系并删除多余槽位。
- 压力源、原因项、阻断点和结果项按真实因果链生成;原因不足示例数量时,用更大的主压力区或结果区承载重点,不补虚构原因。
Motion Direction
适合因果、关系、依赖和从 A 到 B 的解释。
动作组合:
- cause/source 节点先出现。
- 中间的阶段轨道、影响区域或状态 band 被绘制,方向清楚。
- effect/target 区域跟随阶段推进出现。
- 阻断点、关键阶段或影响区用 marker / accent 确认为当前状态。
节奏特点:让观众看到关系被建立,而不是只看到两个卡片和一个箭头。
结构稳定规则:
- connector、impact path、impact band、timeline rail 这类结构线在语义绘制完成后必须稳定保持。
- 禁止对结构线本体使用
yoyo、repeat、反向 scale、左右位移或任何会改变端点、长度、方向、
位置的 ambient 动画。
- 若需要呼吸感,只能作用在 glow、box-shadow、filter、opacity、marker、背景纹理或独立 wrapper 上;
不要让主线伸缩、漂移或来回移动。
- marker / accent 可以轻微 pulse,但 connector 本体应该像编辑图解一样在确认后稳住。
Use When
原因不是线性链路,而是多重压力累积;需要表达“问题被层层压出来”,不需要明确连接线。
Design Intent
左侧压力源层层错位叠加,越靠近结果越强;右侧是单个 result slab。画面没有连接箭头,靠压力场 的位移、权重和 accent cause 推出结果。
HTML Skeleton
<section id="scene_xxx" class="ed-scene" style="opacity: 0;">
<div class="scene-pad ce2-layout">
<span class="ed-ghost-type ce2-ghost" data-anim="ghost">Weight</span>
<div class="ce2-wrap">
<div class="ce2-head">
<span class="ed-kicker label-mono ce2-kicker" data-anim="kicker">PRESSURE</span>
<h1 class="serif-cn ce2-title" data-anim="headline">多重压力,压出<span class="ed-accent">同一个结果</span></h1>
</div>
<div class="ce2-chain" data-anim="chain">
<div class="ce2-pressure" data-anim="pressure">
<div class="ce2-source" data-anim="source"><span class="idx">01</span><h3>压力一</h3><p>短说明</p></div>
<div class="ce2-source" data-anim="source"><span class="idx">02</span><h3>压力二</h3><p>短说明</p></div>
<div class="ce2-source is-accent" data-anim="source-accent"><span class="idx">03</span><h3>关键压力</h3><p>短说明</p></div>
</div>
<div class="ce2-result" data-anim="effect"><div class="serif-cn rh">结果主句,<br><span class="ed-accent">关键词</span></div></div>
</div>
</div>
</div>
</section>CSS Skeleton
#scene_xxx .ce2-layout { position: relative; }
#scene_xxx .ce2-wrap { height: 100%; display: flex; flex-direction: column; }
#scene_xxx .ce2-head { max-width: 980px; margin-bottom: 42px; }
#scene_xxx .ce2-kicker { margin-bottom: 28px; }
#scene_xxx .ce2-title { margin: 0; font-size: 76px; line-height: 1.06; }
#scene_xxx .ce2-chain { flex: 1; position: relative; display: grid; grid-template-columns: 1fr 420px; gap: 68px; align-items: stretch; }
#scene_xxx .ce2-pressure { position: relative; padding-top: 8px; display: flex; flex-direction: column; justify-content: center; gap: 18px; }
#scene_xxx .ce2-source { position: relative; min-height: 112px; padding: 22px 34px; background: var(--surface-2); border-left: 4px solid var(--rule); }
#scene_xxx .ce2-source:nth-child(2) { margin-left: 70px; opacity: 0.9; }
#scene_xxx .ce2-source:nth-child(3),
#scene_xxx .ce2-source.is-accent { margin-left: 140px; border-left-color: var(--accent); background: var(--accent-soft); }
#scene_xxx .ce2-source .idx { position: absolute; right: 26px; top: 20px; font-family: var(--font-display-en); font-size: 70px; line-height: 0.8; color: var(--text-faint); }
#scene_xxx .ce2-source h3 { margin: 0 0 10px; font-family: var(--font-display-cn); font-size: 36px; color: var(--text-2); letter-spacing: 0.03em; }
#scene_xxx .ce2-source p { margin: 0; max-width: 30ch; font-size: 20px; line-height: 1.45; color: var(--text-mute); }
#scene_xxx .ce2-result { position: relative; align-self: center; padding: 66px 48px 54px; background: var(--surface); border-top: var(--rule-w) var(--rule-style) var(--rule); border-bottom: 2px solid var(--accent); box-shadow: 0 28px 80px rgba(0, 0, 0, 0.45); overflow: hidden; }
#scene_xxx .ce2-result::before { content: ""; position: absolute; left: 48px; top: 34px; width: 9px; height: 9px; border-radius: 50%; background: var(--accent); }
#scene_xxx .ce2-result .rh { font-size: 58px; line-height: 1.12; color: var(--text); letter-spacing: 0.03em; }
#scene_xxx .ce2-ghost { right: 60px; top: 58px; font-size: 188px; }Animation Hooks
ghost, kicker, headline, chain, pressure, source, source-accent, effect.
Do Not Change
不要加连接箭头;不要把压力源做成等宽列表;不要让结果 slab 变成普通右侧说明框。
chapter-current-progress
family: chapter
适用:当前章节、当前进度、长教程中段定位、章节切换、下一步提示。
Layout Contract
- 画面必须明确区分已完成、当前、下一步或后续阶段。
- 当前章节或当前进度必须是主视觉锚点。
- 已完成和后续阶段只做上下文,不与当前阶段同权。
- 适合长视频中的定位场景,不适合普通目录页。
- 禁止把章节做成平均目录列表、底部进度条或页面导航。
Layout Variants
layout_variant 必须选择下列之一。variant 只改变 chapter family 内当前章节和进度上下文的组织方式; 章节、进度和下一步必须来自真实内容。
chapter-stack:默认章节堆叠。适合显示已完成、当前、下一步的层级关系。
当前章节必须是主视觉。
progress-rail:进度 rail 贯穿画面,当前节点放大。适合长教程中段定位。
进度节点只做上下文。
current-island:当前章节作为孤岛式主块,前后阶段退到边缘。适合强调“现在进入这里”。
边缘阶段不能与当前同权。
Content Adaptation Contract
HTML Skeleton/CSS Skeleton展示核心视觉语法,不是固定数量模板;不要逐字复制示例内容。- 可见内容必须来自 scene-plan 的
visibleContent、headline、body或screenShouldShow;禁止为填满列表、节点、格子、步骤、候选项、检查项或指标而新增概念。 - 按真实内容增减、合并、重排 slot;如果真实内容少于示例,保留主视觉关系并删除多余槽位。
- 时间点、章节项和进度节点按真实阶段数量生成;没有明确时间或章节时,不要补虚构年份、版本、阶段或下一步。
Motion Direction
适合在长教程中帮助观众重新获得方向感。
动作组合:
- 阶段组先以弱态建立。
- 已完成阶段轻微确认并降权。
- 当前阶段放大、提亮或被 marker 锁定。
- 下一步阶段以弱态提示,最终焦点停在当前阶段。
节奏特点:不是报目录,而是确认“现在讲到这里”。
Use When
内容是在章节切换、中段回顾、进入下一部分之前,告诉观众已经完成什么、当前正在做什么、下一步会做什么。
Design Intent
右侧是一组阶段柱状区域,当前阶段最高、最亮、拥有 CURRENT 和 You are here 确认。已完成阶段 保留为左侧上下文,后续阶段逐渐降权。
HTML Skeleton
<section id="scene_xxx" class="ed-scene" style="opacity: 0;">
<div class="scene-pad cp-layout">
<span class="ed-ghost-type cp-ghost" data-anim="ghost">Here</span>
<div class="cp-wrap">
<aside class="cp-copy">
<span class="ed-kicker label-mono cp-kicker" data-anim="kicker">CURRENT CHAPTER</span>
<h1 class="serif-cn cp-title" data-anim="headline"><!-- headline --></h1>
<p class="ed-subline cp-sub" data-anim="subline">已完成、当前、下一步保持层级,让长教程重新获得方向感。</p>
</aside>
<main class="cp-stage" data-anim="stage">
<div class="cp-progress">
<div class="cp-steps" data-anim="steps">
<!-- Render only real chapter progress items from visibleContent. Do not pad to five. -->
<div class="cp-step is-done" data-no="<!-- doneItems[0].index -->"><span><!-- doneItems[0].status --></span><b><!-- doneItems[0].text --></b></div>
<div class="cp-step is-current" data-no="<!-- currentItem.index -->"><span><!-- currentItem.status --></span><b><!-- currentItem.text --></b><em><!-- currentItem.note --></em></div>
<div class="cp-step is-future" data-no="<!-- nextItem.index -->"><span><!-- nextItem.status --></span><b><!-- nextItem.text --></b></div>
</div>
</div>
</main>
</div>
</div>
</section>CSS Skeleton
#scene_xxx .cp-layout { position: relative; }
#scene_xxx .cp-wrap { height: 100%; display: grid; grid-template-columns: 0.58fr 1.42fr; gap: 76px; align-items: stretch; }
#scene_xxx .cp-copy { display: flex; flex-direction: column; padding-top: 6px; }
#scene_xxx .cp-kicker { margin-bottom: 34px; }
#scene_xxx .cp-title { margin: 0; font-size: 76px; line-height: 1.08; letter-spacing: 0.045em; }
#scene_xxx .cp-sub { margin-top: 34px; max-width: 28ch; font-size: 23px; line-height: 1.62; }
#scene_xxx .cp-stage { align-self: center; }
#scene_xxx .cp-progress { position: relative; height: 650px; isolation: isolate; }
#scene_xxx .cp-steps { position: absolute; inset: 58px 58px 70px 68px; display: grid; grid-template-columns: 0.78fr 0.9fr 1.38fr 0.9fr 0.78fr; align-items: end; gap: 18px; }
#scene_xxx .cp-step { position: relative; min-height: 260px; padding: 28px 24px; background: linear-gradient(180deg, var(--surface-2), var(--surface-3)); overflow: hidden; }
#scene_xxx .cp-step:nth-child(2) { min-height: 338px; }
#scene_xxx .cp-step:nth-child(4) { min-height: 390px; opacity: 0.48; }
#scene_xxx .cp-step:nth-child(5) { min-height: 470px; opacity: 0.3; }
#scene_xxx .cp-step::before { content: attr(data-no); position: absolute; right: 16px; top: 18px; font-family: var(--font-display-en); font-size: 116px; line-height: 0.8; color: var(--ghost-type-color); }
#scene_xxx .cp-step span { position: relative; z-index: 1; display: block; font-family: var(--font-mono); color: var(--text-mute); letter-spacing: 0.22em; font-size: 14px; }
#scene_xxx .cp-step b { position: relative; z-index: 1; display: block; margin-top: 22px; font-family: var(--font-display-cn); font-size: 34px; line-height: 1.12; letter-spacing: 0.04em; }
#scene_xxx .cp-step.is-current { min-height: 520px; background: radial-gradient(110% 80% at 50% 0%, var(--accent-soft), var(--surface) 66%); box-shadow: 0 44px 120px var(--surface-3); opacity: 1; }
#scene_xxx .cp-step.is-current::before { color: var(--accent-soft); font-size: 150px; }
#scene_xxx .cp-step.is-current b { font-size: 54px; }
#scene_xxx .cp-step.is-current em { position: relative; z-index: 1; display: block; margin-top: 30px; font-style: normal; font-family: var(--font-mono); color: var(--accent); letter-spacing: 0.2em; text-transform: uppercase; }
#scene_xxx .cp-ghost { right: 54px; bottom: 22px; font-size: 210px; }Animation Hooks
ghost, kicker, headline, subline, stage, steps.
Do Not Change
不要把当前章节做成普通目录项;不要让后续阶段同权;不要用底部细进度条替代主视觉。
chart-dominant-marker
family: chart
适用:多个数据点、比例、趋势、排行、矩阵、区间变化或图表解释。
Layout Contract
- prototype 中的数据点、区间和标注数量只是示例;按真实数据增减,但保持 chart 主导的视觉框架。
- 图表主体必须占据画面右侧或主区域的大面积,不能缩成小 inset。
- 左侧文字组负责说结论,图表负责让关系可见。
- accent 只标出一个关键 marker、拐点、阈值或异常区间。
- 禁止用列表、卡片或抽象节点替代真实 chart body。
Layout Variants
layout_variant 必须选择下列之一。variant 只改变 chart family 内图表主区域和结论文字的比例; 数据点、marker 和标签必须来自真实内容。
right-chart-left-claim:默认左侧结论、右侧大图表。适合结论需要标题说明,图表负责证明关系。
marker 只能突出一个关键点。
full-bleed-chart:图表占据几乎全画面,标题和标签贴靠图表边缘。适合趋势或拐点本身最重要的场景。
文案不能遮挡关键曲线。
cropped-curve-marker:曲线被放大裁切,关键 marker 成为主视觉。适合单个峰值、异常点或转折点。
裁切后仍必须能看出方向关系。
Content Adaptation Contract
HTML Skeleton/CSS Skeleton展示核心视觉语法,不是固定数量模板;不要逐字复制示例内容。- 可见内容必须来自 scene-plan 的
visibleContent、headline、body或screenShouldShow;禁止为填满列表、节点、格子、步骤、候选项、检查项或指标而新增概念。 - 按真实内容增减、合并、重排 slot;如果真实内容少于示例,保留主视觉关系并删除多余槽位。
- 数字、刻度、柱、曲线点、marker 和支撑指标必须来自真实内容或 scene-plan 明确改写;没有明确数值时,用关系、趋势或状态表达,不要编造数据。
Motion Direction
适合折线、时间线、阶段推进、增长、衰减和变化方向。
动作组合:
- 主轴或时间线先被绘制。
- 趋势 path 或 sweep mask 沿方向展开。
- 关键转折节点跟随出现。
- 当前阶段、峰值或拐点被 accent 确认为当前结论。
节奏特点:观众应该感觉时间或趋势在推进。
Design Intent
左文右图的编辑式图表解释页。左侧是 kicker、两行以内大标题、短 note 和一个关键读数;右侧是 大图表区域。图表用低权重网格和基础趋势线建立上下文,用橙色线段、点和 marker label 标出 真正要看的位置。
HTML Skeleton
<section id="scene_xxx" class="ed-scene" style="opacity: 0;">
<div class="scene-pad ch-layout">
<span class="ed-ghost-type ch-ghost" data-anim="ghost">Trend</span>
<div class="ch-copy">
<span class="ed-kicker label-mono ch-kicker" data-anim="kicker">TREND</span>
<h1 class="serif-cn ch-title" data-anim="headline">图表结论,<span class="ed-accent">关键变化</span></h1>
<p class="ed-subline ch-note" data-anim="note">贴靠标题的解释句,不写成长段摘要。</p>
<div class="ch-readout" data-anim="readout">
<div class="ed-stat"><!-- stat.value --><span><!-- stat.unit --></span></div>
<div class="ch-readout-label">关键读数说明</div>
</div>
</div>
<div class="ch-chart-wrap" data-anim="chart">
<svg class="ed-svg-layer ch-chart" viewBox="0 0 900 620" aria-hidden="true">
<line class="ed-chart-axis ch-grid" x1="70" y1="120" x2="880" y2="120" />
<line class="ed-chart-axis ch-grid" x1="70" y1="280" x2="880" y2="280" />
<line class="ed-chart-axis ch-grid" x1="70" y1="520" x2="880" y2="520" />
<path class="ch-area" data-anim="chart-area" d="M120,470 L330,440 L540,400 L750,140 L750,520 L120,520 Z" />
<polyline class="ed-path is-muted" data-anim="line-muted" points="120,470 330,440 540,400" />
<polyline class="ed-path" data-anim="line-accent" points="540,400 750,140" />
<circle class="ed-marker ch-dot" data-anim="marker" cx="750" cy="140" r="11" />
<line class="ch-marker-line" data-anim="marker-line" x1="750" y1="140" x2="750" y2="520" />
<text class="ch-flag" data-anim="flag" x="700" y="115">拐点</text>
</svg>
</div>
</div>
</section>CSS Skeleton
#scene_xxx .ch-layout {
position: relative;
display: grid;
grid-template-columns: 1fr 1.55fr;
gap: 96px;
align-items: stretch;
}
#scene_xxx .ch-copy {
display: flex;
flex-direction: column;
}
#scene_xxx .ch-kicker {
display: inline-flex;
align-items: center;
gap: 18px;
margin-bottom: 34px;
color: var(--text-mute);
letter-spacing: 0.32em;
}
#scene_xxx .ch-kicker::before {
content: "";
width: 44px;
height: 2px;
background: var(--accent);
}
#scene_xxx .ch-title {
margin: 0;
font-size: 76px;
line-height: 1.06;
}
#scene_xxx .ch-note {
margin-top: 38px;
max-width: 26ch;
font-size: 26px;
line-height: 1.62;
}
#scene_xxx .ch-readout {
margin-top: auto;
margin-bottom: 80px;
border-top: var(--rule-w) var(--rule-style) var(--rule);
padding-top: 30px;
}
#scene_xxx .ch-readout .ed-stat {
font-family: var(--font-display-en);
font-size: 96px;
line-height: 0.9;
}
#scene_xxx .ch-readout .ed-stat span {
font-size: 0.34em;
color: var(--accent);
margin-left: 10px;
}
#scene_xxx .ch-readout-label {
margin-top: 14px;
color: var(--text-mute);
font-size: var(--type-body-min);
line-height: 1.45;
}
#scene_xxx .ch-chart-wrap {
display: flex;
flex-direction: column;
justify-content: center;
}
#scene_xxx .ch-chart {
width: 100%;
height: 620px;
}
#scene_xxx .ch-grid {
stroke: var(--rule);
}
#scene_xxx .ch-area {
fill: var(--accent-soft);
opacity: 0.55;
}
#scene_xxx .ch-marker-line {
stroke: var(--accent);
stroke-width: 1.5;
stroke-dasharray: 6 7;
opacity: 0.6;
}
#scene_xxx .ch-flag {
fill: var(--accent);
font-family: var(--font-mono);
font-size: 19px;
letter-spacing: 0.14em;
}
#scene_xxx .ch-ghost {
right: 5%;
top: 5%;
font-size: 200px;
}Animation Hooks
ghost, kicker, headline, note, readout, chart, chart-area, line-muted, line-accent, marker, marker-line, flag.
Do Not Change
不要把图表做成小装饰;不要用均分卡片解释趋势;不要出现多个同权重 accent marker。
chart-interval-stability
family: chart
适用:多个数据点、比例、趋势、排行、矩阵、区间变化或图表解释。
Layout Contract
- prototype 中的数据点、区间和标注数量只是示例;按真实数据增减,但保持 chart 主导的视觉框架。
- 图表主体必须占据画面右侧或主区域的大面积,不能缩成小 inset。
- 左侧文字组负责说结论,图表负责让关系可见。
- accent 只标出一个关键 marker、拐点、阈值或异常区间。
- 禁止用列表、卡片或抽象节点替代真实 chart body。
Layout Variants
layout_variant 必须选择下列之一。variant 只改变 chart family 内区间、稳定带和阈值窗口的组织方式; 区间和标注必须来自真实内容或明确的相对关系。
banded-range:默认稳定带 / 区间带主导。适合波动范围、上下限和稳定区间说明。
accent 只锁定当前区间或异常区间。
stacked-intervals:多个区间上下堆叠比较。适合阶段区间、耗时段或多个候选范围对比。
堆叠项数量按真实内容增减。
threshold-window:阈值窗口占据主视觉,当前值或区间落入窗口内外。适合边界、合格区和风险区判断。
不伪造精确阈值。
Content Adaptation Contract
HTML Skeleton/CSS Skeleton展示核心视觉语法,不是固定数量模板;不要逐字复制示例内容。- 可见内容必须来自 scene-plan 的
visibleContent、headline、body或screenShouldShow;禁止为填满列表、节点、格子、步骤、候选项、检查项或指标而新增概念。 - 按真实内容增减、合并、重排 slot;如果真实内容少于示例,保留主视觉关系并删除多余槽位。
- 数字、刻度、柱、曲线点、marker 和支撑指标必须来自真实内容或 scene-plan 明确改写;没有明确数值时,用关系、趋势或状态表达,不要编造数据。
Motion Direction
适合折线、时间线、阶段推进、增长、衰减和变化方向。
动作组合:
- 主轴或时间线先被绘制。
- 趋势 path 或 sweep mask 沿方向展开。
- 关键转折节点跟随出现。
- 当前阶段、峰值或拐点被 accent 确认为当前结论。
节奏特点:观众应该感觉时间或趋势在推进。
Use When
内容强调耗时区间、波动范围、上下限、阶段区间对比,或从不稳定进入稳定。
Design Intent
左侧是结论、note 和稳定区间读数;右侧是大面积区间柱图。每个柱表达上下限范围,只有一个稳定 区间使用 accent。画面答案不是某个点,而是“区间变窄并稳定”。
HTML Skeleton
<section id="scene_xxx" class="ed-scene" style="opacity: 0;">
<div class="scene-pad ch2-layout">
<span class="ed-ghost-type ch2-ghost" data-anim="ghost">Band</span>
<div class="ch2-left">
<span class="ed-kicker label-mono ch2-kicker" data-anim="kicker">STABILITY BAND</span>
<h1 class="serif-cn ch2-title" data-anim="headline">耗时进入<span class="ed-accent">稳定带</span></h1>
<p class="ed-subline ch2-note" data-anim="note">贴靠标题的区间解释。</p>
<div class="ch2-stat" data-anim="readout"><div class="v">18-24</div><div class="l">分钟级稳定输出区间</div></div>
</div>
<div class="ch2-stage" data-anim="chart">
<div class="ch2-plot">
<div class="ch2-scale">
<!-- Render only real scale labels from visibleContent. Do not pad to four. -->
<span><!-- scaleLabels[0] --></span><span><!-- scaleLabels[1] --></span>
</div>
<span class="ch2-gridline g1"></span><span class="ch2-gridline g2"></span><span class="ch2-gridline g3"></span><span class="ch2-gridline g4"></span>
<div class="ch2-bars">
<div class="ch2-bar" data-anim="bar"><div class="ch2-range" style="height: 430px;"><span class="ch2-minmax">6.5h</span></div><div class="label-mono ch2-lab">Manual</div></div>
<div class="ch2-bar" data-anim="bar"><div class="ch2-range" style="height: 310px;"><span class="ch2-minmax">3.2h</span></div><div class="label-mono ch2-lab">Template</div></div>
<div class="ch2-bar" data-anim="bar"><div class="ch2-range" style="height: 205px;"><span class="ch2-minmax">68m</span></div><div class="label-mono ch2-lab">Semi Auto</div></div>
<div class="ch2-bar is-accent" data-anim="bar-accent"><div class="ch2-range" style="height: 94px;"><span class="ch2-minmax">24m</span></div><div class="label-mono ch2-lab">Skill</div></div>
</div>
</div>
</div>
</div>
</section>CSS Skeleton
#scene_xxx .ch2-layout {
position: relative;
display: grid;
grid-template-columns: 0.7fr 1.3fr;
gap: 76px;
align-items: stretch;
}
#scene_xxx .ch2-left { display: flex; flex-direction: column; }
#scene_xxx .ch2-kicker { margin-bottom: 32px; }
#scene_xxx .ch2-title { margin: 0; font-size: 74px; line-height: 1.07; }
#scene_xxx .ch2-note { margin-top: 36px; max-width: 27ch; font-size: 25px; line-height: 1.65; }
#scene_xxx .ch2-stat { margin-top: auto; margin-bottom: 80px; border-top: var(--rule-w) var(--rule-style) var(--rule); padding-top: 30px; }
#scene_xxx .ch2-stat .v { font-family: var(--font-display-en); font-size: 96px; line-height: 0.88; color: var(--text); letter-spacing: -0.03em; }
#scene_xxx .ch2-stat .l { margin-top: 16px; color: var(--text-mute); font-size: var(--type-body-min); line-height: 1.45; }
#scene_xxx .ch2-stage { position: relative; display: flex; align-items: center; }
#scene_xxx .ch2-plot {
position: relative;
width: 100%;
height: 650px;
border-left: var(--rule-w) var(--rule-style) var(--rule);
border-bottom: var(--rule-w) var(--rule-style) var(--rule);
padding: 68px 56px 76px 72px;
}
#scene_xxx .ch2-scale {
position: absolute;
left: 0;
top: 66px;
bottom: 76px;
width: 58px;
display: flex;
flex-direction: column;
justify-content: space-between;
font-family: var(--font-mono);
font-size: 17px;
color: var(--text-mute);
letter-spacing: 0.12em;
}
#scene_xxx .ch2-gridline { position: absolute; left: 72px; right: 56px; height: 1px; background: var(--rule); }
#scene_xxx .ch2-gridline.g1 { top: 118px; }
#scene_xxx .ch2-gridline.g2 { top: 244px; }
#scene_xxx .ch2-gridline.g3 { top: 370px; }
#scene_xxx .ch2-gridline.g4 { top: 496px; }
#scene_xxx .ch2-bars { position: relative; height: 100%; display: grid; grid-template-columns: repeat(4, 1fr); gap: 56px; align-items: end; z-index: 1; }
#scene_xxx .ch2-bar { position: relative; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; }
#scene_xxx .ch2-range { position: relative; width: 100%; border: var(--rule-w) var(--rule-style) var(--rule); background: var(--surface-2); }
#scene_xxx .ch2-range::before,
#scene_xxx .ch2-range::after { content: ""; position: absolute; left: -10px; right: -10px; height: 1px; background: var(--rule); }
#scene_xxx .ch2-range::before { top: 0; }
#scene_xxx .ch2-range::after { bottom: 0; }
#scene_xxx .ch2-bar.is-accent .ch2-range { border-color: var(--accent); background: var(--accent-soft); box-shadow: 0 0 0 7px var(--accent-soft); }
#scene_xxx .ch2-bar.is-accent .ch2-range::before,
#scene_xxx .ch2-bar.is-accent .ch2-range::after { background: var(--accent); }
#scene_xxx .ch2-lab { margin-top: 24px; font-size: 18px; color: var(--text-mute); letter-spacing: 0.14em; }
#scene_xxx .ch2-bar.is-accent .ch2-lab { color: var(--accent); }
#scene_xxx .ch2-minmax { position: absolute; right: 0; top: -34px; font-family: var(--font-display-en); font-size: 42px; line-height: 0.9; color: var(--text-2); letter-spacing: -0.03em; }
#scene_xxx .ch2-bar.is-accent .ch2-minmax { color: var(--text); }
#scene_xxx .ch2-ghost { right: 56px; top: 58px; font-size: 190px; }Animation Hooks
ghost, kicker, headline, note, readout, chart, bar, bar-accent, range.
Do Not Change
不要把区间柱改成折线;不要让多个区间同时 accent;不要把 plot 缩成右下角小图。
checklist-risk-scan
family: checklist
适用:检查项、风险项定位、发布前检查、调试检查、质量复核。
Layout Contract
- 画面必须是一组有秩序的检查项,并有一个明确风险项或当前检查项。
- 风险项应被扫描线、边框、callout 或 marker 锁定。
- 检查项数量可增减,但主视觉应保留“扫描并定位”的关系。
- 扫描线 / 扫描条必须与当前
.cr-item的真实区域对齐;检查项 top、gap、高度或数量变化后,
不能继续使用旧的固定 top 数值。
- 扫描线初始必须隐藏;只有进入第一个检查 cue 时才出现并定位到第一项,后续只移动位置。
- callout 必须贴靠风险项或检查区,不要变成底部注释。
- 禁止做成普通待办清单、完成度面板或平均状态卡片。
Layout Variants
layout_variant 必须选择下列之一。variant 只改变检查关系的构图,不改变 checklist family 的 语义边界;检查项、风险项和 callout 仍只能来自 visibleContent、headline、body 或 screenShouldShow。
side-copy-board:默认左文案、右检查板结构。适合标题需要解释检查目的,同时右侧承载多项扫描。
保留当前 skeleton 的横向扫描线、检查板和贴靠风险项的 callout。
full-board-callout:检查板占据主画面,标题压到上方或上左。适合检查项较多、风险定位是主体动作。
callout 必须贴靠风险行或检查区域内部,不能变成底部说明。
vertical-scan-rail:纵向扫描轨道,风险项沿竖轴定位。适合流程式复核、从上到下排查或阶段检查。
扫描线可改为竖向 rail,但仍必须有当前项 / 风险项的稳定确认。
radar-focus:中心风险对象 + 周围检查项环绕,扫描线聚焦当前风险。适合少量检查项围绕一个核心
风险或质量对象。不要补无来源环绕节点;少项时放大中心和当前风险。
Content Adaptation Contract
HTML Skeleton/CSS Skeleton展示核心视觉语法,不是固定数量模板;不要逐字复制示例内容。- 可见内容必须来自 scene-plan 的
visibleContent、headline、body或screenShouldShow;禁止为填满列表、节点、格子、步骤、候选项、检查项或指标而新增概念。 - 按真实内容增减、合并、重排 slot;如果真实内容少于示例,保留主视觉关系并删除多余槽位。
- 检查项和风险项按真实待检查内容生成;示例中的多项列表不是数量要求,少项时放大当前风险或确认对象。
Motion Direction
适合把“逐项检查”做成有推进感的定位过程。
动作组合:
- 检查板和检查项先建立。
- 扫描线在第一个检查 cue 才从隐藏状态出现,并立即对齐第一条当前项。
- 扫描线沿列表移动时,位置必须根据当前检查项和列表容器的真实 DOM 位置或同源 CSS 变量计算:
scanTop = list.offsetTop + item.offsetTop + item.offsetHeight / 2 - scan.offsetHeight / 2。
- 不要使用
itemHeight * index、top: 318px、固定y偏移等近似值;这些会在 item 高度、
gap 或 variant 改变时产生错位。
- 已确认项降权,风险项被边线或 accent 锁定。
- callout 贴靠风险项出现,最终停在处理建议上。
节奏特点:像质检过程,不像罗列任务。
Use When
内容是在讲检查、复核、排查、发布前确认,尤其适合“这些都没问题,真正风险在这一项”。
Design Intent
右侧是大型检查板,横向扫描线穿过当前风险项。左侧标题解释检查目的,右侧 callout 明确指出风险 和下一步处理动作。
HTML Skeleton
<section id="scene_xxx" class="ed-scene" style="opacity: 0;">
<div class="scene-pad cr-layout">
<span class="ed-ghost-type cr-ghost" data-anim="ghost">Scan</span>
<div class="cr-wrap">
<aside class="cr-copy">
<span class="ed-kicker label-mono cr-kicker" data-anim="kicker">RISK CHECK</span>
<h1 class="serif-cn cr-title" data-anim="headline"><!-- headline --></h1>
<p class="ed-subline cr-sub" data-anim="subline"><!-- subline --></p>
</aside>
<main class="cr-stage" data-anim="stage">
<div class="cr-board">
<div class="cr-beam" data-anim="scan"></div>
<div class="cr-list" data-anim="list">
<!-- Render only real checklist items from visibleContent. Do not pad to four. -->
<div class="cr-item is-ok"><span><!-- checkItems[0].index --></span><b><!-- checkItems[0].text --></b><em><!-- checkItems[0].status --></em></div>
<div class="cr-item is-risk"><span><!-- riskItem.index --></span><b><!-- riskItem.text --></b><em><!-- riskItem.status --></em></div>
</div>
<div class="cr-callout" data-anim="callout"><small><!-- riskItem.kicker --></small><strong><!-- riskItem.text --></strong><p><!-- riskItem.note --></p></div>
</div>
</main>
</div>
</div>
</section>CSS Skeleton
#scene_xxx .cr-layout { position: relative; }
#scene_xxx .cr-wrap { height: 100%; display: grid; grid-template-columns: 0.58fr 1.42fr; gap: 76px; align-items: stretch; }
#scene_xxx .cr-copy { display: flex; flex-direction: column; padding-top: 6px; }
#scene_xxx .cr-kicker { margin-bottom: 34px; }
#scene_xxx .cr-title { margin: 0; font-size: 76px; line-height: 1.08; letter-spacing: 0.045em; }
#scene_xxx .cr-sub { margin-top: 34px; max-width: 28ch; font-size: 23px; line-height: 1.62; }
#scene_xxx .cr-stage { position: relative; align-self: center; }
#scene_xxx .cr-board { position: relative; height: 650px; border: var(--rule-w) var(--rule-style) var(--rule); overflow: hidden; background: linear-gradient(135deg, var(--surface-2), transparent 55%); }
#scene_xxx .cr-beam { position: absolute; left: 0; right: 0; top: var(--scan-top, 0px); height: var(--scan-h, 92px); opacity: 0; border-top: var(--rule-w) var(--rule-style) var(--accent); border-bottom: var(--rule-w) var(--rule-style) var(--accent); background: var(--accent-soft); }
#scene_xxx .cr-list { position: absolute; left: 64px; top: 58px; width: 860px; display: grid; gap: 22px; }
#scene_xxx .cr-item { height: 112px; display: grid; grid-template-columns: 96px 1fr auto; align-items: center; border-bottom: var(--rule-w) var(--rule-style) var(--rule); color: var(--text-2); }
#scene_xxx .cr-item span { font-family: var(--font-mono); font-size: 19px; color: var(--text-mute); }
#scene_xxx .cr-item b { font-family: var(--font-display-cn); font-size: 42px; letter-spacing: 0.04em; }
#scene_xxx .cr-item em { font-style: normal; font-family: var(--font-mono); font-size: 15px; letter-spacing: 0.2em; color: var(--text-mute); text-transform: uppercase; }
#scene_xxx .cr-item.is-risk { color: var(--text); border-color: var(--accent); }
#scene_xxx .cr-item.is-risk em { color: var(--accent); }
#scene_xxx .cr-item.is-dim { opacity: 0.42; }
#scene_xxx .cr-callout { position: absolute; right: 70px; top: 238px; width: 470px; min-height: 260px; border: var(--rule-w) var(--rule-style) var(--accent); padding: 38px; background: var(--surface); box-shadow: 0 30px 90px var(--surface-3); }
#scene_xxx .cr-callout small { font-family: var(--font-mono); color: var(--accent); letter-spacing: 0.26em; text-transform: uppercase; }
#scene_xxx .cr-callout strong { display: block; margin-top: 28px; font-family: var(--font-display-cn); font-size: 48px; line-height: 1.16; letter-spacing: 0.04em; }
#scene_xxx .cr-callout p { margin: 22px 0 0; color: var(--text-mute); font-size: 21px; line-height: 1.55; }
#scene_xxx .cr-ghost { right: 54px; bottom: 22px; font-size: 210px; }Animation Hooks
ghost, kicker, headline, subline, stage, scan, list, check-item, callout.
扫描线 JS 必须从真实行位置读取坐标。注意:q(...) 返回 scoped selector 字符串,不是 DOM element;读取 offsetTop / offsetHeight 时必须用 scene.querySelector(...) 获取 DOM。 因为 .cr-item 位于绝对定位的 .cr-list 内,而 scan beam 位于 .cr-board 内,所以必须加上 list 容器自身的 offsetTop:
const scan = scene.querySelector("[data-anim='scan']");
const list = scene.querySelector(".cr-list");
const items = scene.querySelectorAll("[data-anim='check-item']");
const alignScanTo = (item) => {
const top = list.offsetTop + item.offsetTop + item.offsetHeight / 2 - scan.offsetHeight / 2;
return top;
};
tl.set(q("[data-anim='scan']"), { opacity: 0, top: () => alignScanTo(items[0]) }, at(0));
tl.to(q("[data-anim='scan']"), { opacity: 1, duration: 0.25 }, cue(firstCheckSegment, 0));
tl.to(q("[data-anim='scan']"), { top: () => alignScanTo(items[index]), duration: 0.45, ease: "power2.inOut" }, cue(segment, 80));检查板、列表和标题属于 skeleton,必须在 scene 开始后约 0.8-1.4 秒内建立。不要把整个 stage 延迟到后段 cue 才出现;如果扫描条和 callout 还没出现,检查项本身也应该已经可读, 避免场景前段空白。
Do Not Change
不要把扫描过程变成逐项打勾;不要让风险 callout 远离检查项;不要把所有检查项做成同权卡片; 不要用固定 top / y 偏移模拟扫描条位置;不要让扫描条在第一个检查 cue 之前可见。
compare-asymmetric-recommendation
family: compare
适用:before/after、新旧对比、两种选择、优劣变化。
Layout Contract
- prototype 中的条目、marker 和辅助说明数量只是示例;按真实对比内容增减,但保持分栏主次关系。
- 对比必须是中缝编辑式分栏,左右内容组语义对齐。
- 推荐侧或 after 侧通过 accent、小 badge、亮度和 marker 提权。
- 旧侧 / before 侧可以压缩,但必须可读;不要用过低 opacity 或
var(--text-mute)承载主要反例文字。 - badge 是可选信息,只在内容明确有推荐标签时出现,不要为了装饰自动添加。
- 左右区域不使用大边框盒子;中缝只是一条 hairline。
- 禁止左右 state 大框、差异轴大标签、底部结果 caption。
Layout Variants
layout_variant 必须选择下列之一。variant 只改变 compare family 内推荐侧的权重和证明方式; 推荐点和旧侧上下文必须来自真实内容。
hero-recommendation:推荐侧作为主视觉,旧侧压缩为边栏。适合结论明显偏向新方案。
推荐侧必须有稳定 accent。
narrow-before-wide-after:旧侧 35%、after 侧 65%。适合 before / after 转换,after 是最终记忆点。
窄栏只保留必要旧状态。
proof-stack-recommendation:推荐侧叠放 1-2 个证明点或结果标签。适合推荐理由比旧侧细节更重要的场景。
proof stack 不得伪造指标。
Content Adaptation Contract
HTML Skeleton/CSS Skeleton展示核心视觉语法,不是固定数量模板;不要逐字复制示例内容。- 可见内容必须来自 scene-plan 的
visibleContent、headline、body或screenShouldShow;禁止为填满列表、节点、格子、步骤、候选项、检查项或指标而新增概念。 - 按真实内容增减、合并、重排 slot;如果真实内容少于示例,保留主视觉关系并删除多余槽位。
- 左右两侧的对比项数量可以不对称;只保留真实差异和推荐点,不要为了形成三行列表而补无来源的优缺点。
- “而不是 / 不要 / 旧做法”这类文字虽然降权,但仍是语义对照的一半;字号不应低于 20px,
颜色不应低于 var(--text-2) 的可读层级。
Motion Direction
适合左右对比、问题/解法、旧方案/新方案和 before/after。
动作组合:
- 分割线、差异轴或遮罩先出现。
- 左右两侧从相反方向进入。
- 可比较项按行对齐出现。
- 推荐侧、解法侧或关键差异使用 accent 确认,旧侧可降权。
- 必要时让旧侧轻微降权,新侧稳定保留。
节奏特点:对照关系必须清楚,左右内容不要同时糊成一团。
Use When
已经有明确推荐结论,旧方案只是被压缩展示,新方案才是重点;要表达“不是二选一,而是推荐右侧”。
Design Intent
不对称对比场。左侧旧方案压缩成同宽碎片 chips 和一句弱结论,右侧新方案占更大宽度,拥有大标题、 推荐 badge 和按真实内容生成的结果读数。中缝仍然存在,但权重明显向推荐侧倾斜。
HTML Skeleton
<section id="scene_xxx" class="ed-scene" style="opacity: 0;">
<div class="scene-pad cs2-layout">
<span class="ed-ghost-type cs2-ghost" data-anim="ghost">Better</span>
<div class="cs2-wrap">
<div class="cs2-head">
<span class="ed-kicker label-mono cs2-kicker" data-anim="kicker">COMPARISON</span>
<h1 class="serif-cn cs2-title" data-anim="headline"><!-- headline --></h1>
</div>
<div class="cs2-field" data-anim="field">
<section class="cs2-old" data-anim="before">
<div class="label-mono cs2-label">Before</div>
<div class="chips">
<!-- Render only real old-side chips from visibleContent. Do not pad to three. -->
<span class="chip"><!-- oldItems[0].text --></span>
<span class="chip"><!-- oldItems[1].text --></span>
</div>
<h3><!-- oldConclusion.text --></h3>
</section>
<section class="cs2-new" data-anim="after">
<!-- Optional: render only if the scene-plan explicitly contains a recommendation label. -->
<span class="label-mono cs2-pick" data-anim="pick"><!-- optionalBadge --></span>
<div class="label-mono cs2-label">After</div>
<h2><!-- recommendation.title with accent --></h2>
<div class="cs2-list" data-anim="metrics">
<!-- Render only real recommendation metrics from visibleContent. Do not pad to three. -->
<div><div class="v"><!-- metrics[0].value --></div><div class="l"><!-- metrics[0].label --></div></div>
<div><div class="v"><!-- metrics[1].value --></div><div class="l"><!-- metrics[1].label --></div></div>
</div>
</section>
</div>
</div>
</div>
</section>CSS Skeleton
#scene_xxx .cs2-layout { position: relative; }
#scene_xxx .cs2-wrap { height: 100%; display: flex; flex-direction: column; }
#scene_xxx .cs2-head { margin-bottom: 58px; }
#scene_xxx .cs2-kicker { margin-bottom: 28px; }
#scene_xxx .cs2-title { margin: 0; font-size: 76px; line-height: 1.06; }
#scene_xxx .cs2-field { flex: 1; display: grid; grid-template-columns: 0.72fr 1.28fr; border-top: var(--rule-w) var(--rule-style) var(--rule); }
#scene_xxx .cs2-old { padding: 50px 56px 0 0; border-right: var(--rule-w) var(--rule-style) var(--rule); }
#scene_xxx .cs2-new { position: relative; padding: 46px 0 0 78px; }
#scene_xxx .cs2-label { font-size: 17px; letter-spacing: 0.22em; color: var(--text-mute); margin-bottom: 30px; }
#scene_xxx .cs2-old .chips { display: flex; flex-direction: column; gap: 14px; max-width: 360px; }
#scene_xxx .cs2-old .chip { width: 100%; border: var(--rule-w) var(--rule-style) var(--rule); color: var(--text-2); padding: 14px 18px; font-size: 21px; border-radius: var(--r-sm); transform: rotate(var(--r, 0deg)); background: var(--surface); }
#scene_xxx .cs2-old .chip:nth-child(2) { --r: -2deg; }
#scene_xxx .cs2-old .chip:nth-child(5) { --r: 2deg; }
#scene_xxx .cs2-old h3 { margin: 44px 0 0; font-family: var(--font-display-cn); font-size: 42px; color: var(--text-2); letter-spacing: 0.03em; }
#scene_xxx .cs2-pick { position: absolute; right: 0; top: 44px; color: var(--accent); border: var(--rule-w) var(--rule-style) var(--accent); border-radius: 999px; padding: 8px 18px; font-size: 15px; letter-spacing: 0.2em; }
#scene_xxx .cs2-new .cs2-label { color: var(--accent); }
#scene_xxx .cs2-new h2 { max-width: 780px; margin: 0 0 42px; font-family: var(--font-display-cn); font-size: 86px; line-height: 1.02; letter-spacing: 0.04em; color: var(--text); }
#scene_xxx .cs2-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 40px; border-top: var(--rule-w) var(--rule-style) var(--rule); padding-top: 34px; }
#scene_xxx .cs2-list .v { font-family: var(--font-display-en); font-size: 74px; color: var(--text); line-height: 0.9; letter-spacing: -0.03em; }
#scene_xxx .cs2-list .l { margin-top: 14px; font-size: 21px; line-height: 1.45; color: var(--text-2); }
#scene_xxx .cs2-ghost { left: 58%; bottom: 28px; transform: translateX(-50%); font-size: 190px; }Animation Hooks
ghost, kicker, headline, field, before, after, pick(optional), metrics.
Do Not Change
不要把左右重新均分;不要把推荐 badge 放到底部;不要把旧方案扩展成与新方案同权; 不要把 before / old side 的核心文字压暗到不可读。
compare-multi-option-board
family: compare
适用:3-4 个方案、工具、模型、模板或路径横向比较。
Layout Contract
- 画面必须有 3-4 个真实候选,并用同一组标准或同一阅读路径比较。
- 推荐候选可以提权,但其他候选必须保留可比较上下文。
- 适合多对象比较,不适合 before/after 二分或筛选漏斗。
- 禁止普通卡片表格、平均营销卡片或只展示推荐项。
Layout Variants
option-columns:A/B/C 多候选横向列。适合候选都需要被看见。winner-highlight:推荐候选提权。适合有明确推荐结论。criteria-row-board:底部或中部标准 rail 对齐候选。适合标准本身需要可见。
Content Adaptation Contract
HTML Skeleton/CSS Skeleton展示核心视觉语法,不是固定数量模板;不要逐字复制示例内容。- 可见内容必须来自 scene-plan 的
visibleContent、headline、body或screenShouldShow。 - 按真实内容增减、合并、重排 slot;不要为了填满候选列、标准 rail 或推荐理由而新增概念。
- 候选、标准和推荐理由必须来自真实内容。
- 候选少于 3 个时优先使用
compare-editorial-split或compare-asymmetric-recommendation。 - 不要为形成 A/B/C 而新增无来源候选。
Motion Direction
- 候选列先并列建立。
- 标准 rail 或比较点随后对齐出现。
- 推荐项被 accent、badge 或亮度确认。
节奏特点:横向比较,而不是筛选出结果。
Use When
内容是在比较多个工具、模板、模型、流程或方案,并需要观众理解为什么某一项更合适。
Design Intent
左侧标题说明比较目的,右侧多个候选列使用同一结构。推荐列更高、更亮或带 badge,底部标准 rail 让比较维度清楚。
HTML Skeleton
<section id="scene_xxx" class="ed-scene" style="opacity: 0;">
<div class="scene-pad cm-layout">
<span class="ed-ghost-type cm-ghost" data-anim="ghost">Options</span>
<div class="cm-wrap">
<aside class="cm-copy"><span class="ed-kicker label-mono cm-kicker" data-anim="kicker"><!-- kicker --></span><h1 class="serif-cn cm-title" data-anim="headline"><!-- headline --></h1><p class="ed-subline cm-sub" data-anim="subline"><!-- subline --></p></aside>
<main class="cm-stage" data-anim="stage">
<div class="cm-options" data-anim="options">
<!-- Render only real options. Do not pad to three. -->
<section class="cm-option"><small><!-- option.label --></small><h2><!-- option.title --></h2><ul><li><!-- option.point --></li></ul></section>
<section class="cm-option is-winner"><small><!-- option.label --></small><h2><!-- option.title --></h2></section>
</div>
<div class="cm-criteria" data-anim="criteria"><span><!-- criterion.text --></span></div>
</main>
</div>
</div>
</section>CSS Skeleton
#scene_xxx .cm-wrap { height: 100%; display: grid; grid-template-columns: 0.58fr 1.42fr; gap: 76px; }
#scene_xxx .cm-title { margin: 0; font-size: 76px; line-height: 1.08; }
#scene_xxx .cm-stage { align-self: center; height: 650px; display: grid; grid-template-rows: 1fr 86px; gap: 28px; }
#scene_xxx .cm-options { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; }
#scene_xxx .cm-option { padding: 34px 32px; border-left: var(--rule-w) var(--rule-style) var(--rule); background: var(--surface-2); }
#scene_xxx .cm-option h2 { margin: 42px 0 28px; font-family: var(--font-display-cn); font-size: 44px; line-height: 1.12; }
#scene_xxx .cm-option.is-winner { border-color: var(--accent); background: var(--accent-soft); }
#scene_xxx .cm-criteria { border-top: var(--rule-w) var(--rule-style) var(--rule); display: flex; align-items: center; justify-content: space-around; }Animation Hooks
ghost, kicker, headline, subline, stage, options, criteria.
Do Not Change
不要把多候选强行改成左右二分;不要只保留推荐项;不要为凑列数新增候选。
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
function fail(error, extra = {}) {
console.error(JSON.stringify({ success: false, error, ...extra }, null, 2));
process.exit(1);
}
function parseCreatorNumber(creatorId) {
const match = /^creator-(\d+)$/.exec(String(creatorId).trim());
if (!match) fail(`invalid creatorId: ${creatorId}`);
const number = Number(match[1]);
if (!Number.isInteger(number) || number <= 0) fail(`invalid creator number: ${creatorId}`);
return number;
}
function main() {
const [storyboardPathArg, creatorId, scenesPerCreatorArg, outputPathArg] = process.argv.slice(2);
if (!storyboardPathArg || !creatorId || !scenesPerCreatorArg || !outputPathArg) {
fail("usage: node generate-creator-scenes.mjs <storyboardPath> <creatorId> <scenesPerCreator> <outputPath>");
}
const storyboardPath = path.resolve(storyboardPathArg);
const outputPath = path.resolve(outputPathArg);
const scenesPerCreator = Number(scenesPerCreatorArg);
if (!Number.isInteger(scenesPerCreator) || scenesPerCreator <= 0) fail(`invalid scenesPerCreator: ${scenesPerCreatorArg}`);
if (!fs.existsSync(storyboardPath)) fail(`storyboard not found: ${storyboardPath}`);
const storyboard = JSON.parse(fs.readFileSync(storyboardPath, "utf8"));
if (!storyboard || !Array.isArray(storyboard.scenes)) fail(`invalid storyboard: ${storyboardPath}`);
const creatorNumber = parseCreatorNumber(creatorId);
const startIndex = (creatorNumber - 1) * scenesPerCreator;
const scenesData = storyboard.scenes.slice(startIndex, startIndex + scenesPerCreator);
if (scenesData.length === 0) {
fail(`creator has no scenes: ${creatorId}`, { sceneCount: storyboard.scenes.length, scenesPerCreator });
}
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(scenesData, null, 2), "utf8");
console.log(JSON.stringify({ success: true, creatorId, outputPath, sceneIds: scenesData.map((scene) => scene.id) }, null, 2));
}
main();
{
"id": "dark-editorial",
"name": "Dark Editorial",
"nameZh": "暗色风格",
"description": "Warm espresso-black video theme with fire-orange accent, Chinese serif headlines, italic display serif details, mono metadata, hairlines, generous negative space, and cinematic motion.",
"descriptionZh": "暖黑 espresso 底、火橙强调、中文衬线大标题、英文 italic serif、等宽元信息、细线和大留白,适合高级感暗调 Editorial 视频。",
"mood": [
"dark",
"editorial",
"serif",
"cinematic",
"premium"
],
"bestFor": [
"深度解释",
"产品叙事",
"AI 工作流",
"技术观点",
"高级感 Editorial 视频"
],
"preview": {
"shell": "#0d0b09",
"surface": "#1a1714",
"text": "#f5f0e5",
"accent": "#ff4a2b"
}
}