幻灯片编写契约
超帧幻灯片是一个普通的超帧合成——场景、剪辑、GSAP时间线——加上一个额外的成分:一个JSON岛,它声明哪些场景是幻灯片以及它们如何连接。播放器的SlideshowController读取该岛,并将连续的GSAP时间线转换为离散的、可导航的幻灯片组。
首先阅读 /hyperframes-core 以了解基本合成契约(剪辑、轨道、data-*属性、确定性规则)。此技能仅涵盖新增内容:岛的模式、幻灯片编写规则、片段、分支、验证以及包裹组件。
输出——一个可导航的幻灯片组,而非线性的 MP4
幻灯片的输出是正在运行的幻灯片组:使用 hyperframes present <project-dir>(或工作室演示模式)提供服务——播放器的SlideshowController读取该岛并驱动导航、片段、分支和演示者模式。请参阅下面的 演示和交接。
不要使用 hyperframes render 将幻灯片渲染成单个 MP4。 一个幻灯片组是作为几个顶级场景合成(每个幻灯片一个 data-composition-id)编写的,且没有主根合成来包裹它们,因此 render 仅解析第一个合成并输出一个静默截断的 MP4(例如,一个 40 秒的幻灯片组只出了 6 秒)。线性主线导出(仅主幻灯片,排除分支序列)已被推迟——在该功能发布之前,支持的输出是实时的 present 幻灯片组和每个幻灯片的 snapshot 静态图像。如果用户现在需要线性 MP4,请说明此限制,而不是指导 render 去处理该幻灯片组。
意图确认
如果用户明确要求幻灯片、幻灯片放映或超帧幻灯片,则继续使用此技能。
如果该技能是从一个邻近的请求触发的,例如“演示文稿”、“推介幻灯片”、“幻灯片组”、“交互式幻灯片组”或“转换此页面”,请在编写前暂停,并在询问确认前简要说明选择。简要解释超帧幻灯片意味着一个包含离散幻灯片、内置导航和演示者模式、可编辑的演讲者备注、共享媒体处理以及交接前验证的可运行幻灯片组。对于源页面转换,还要提及目标是保留原始页面的视觉设计、交互、动画和媒体行为,同时将页面移动转换为幻灯片到幻灯片的过渡。
然后提出一个简短的确认问题:
您希望将其作为超帧幻灯片吗?
在环境提供是/否选择 UI 时使用;否则以纯文本形式提问。
在用户回答“是”之前,不要实现幻灯片。如果他们回答“否”,请停止使用此技能并切换到适当的非幻灯片工作流程。
两个组成部分
1. 场景——以常规方式声明
每个幻灯片都对应一个场景。使用 data-composition-id、data-start、data-duration 和 data-label 声明场景:
<div
data-composition-id="problem"
data-start="0"
data-duration="8"
data-label="问题陈述"
data-width="1920"
data-height="1080"
>
<!-- 剪辑放在这里 -->
</div>
分支幻灯片(只能通过热点到达,被排除在主线路之外)的声明方式完全相同——它们只是出现在岛的 slideSequences 条目中,而不是主 slides 数组中。
2. JSON 岛——每个合成一个脚本块
在合成 HTML 中精确添加一个 <script type="application/hyperframes-slideshow+json"> 块。它包含所有幻灯片元数据:
<script type="application/hyperframes-slideshow+json">
{
"slides": [...],
"slideSequences": [...]
}
</script>
该岛是幻灯片顺序、备注、片段驻留点、热点和分支序列的唯一真实来源。将其放在 <body> 顶部附近、场景 div 之前,以便于查找。
不要将幻灯片清单隐藏在一个备用的 <script type="application/json"> 块加上运行时代码后面来创建该岛。present 命令静态读取合成 HTML,并期望真实的 application/hyperframes-slideshow+json 岛已经存在。
模式
SlideshowManifest(顶层岛对象)
{
"slides": [
/* SlideRef[] — 主线路,按顺序排列 */
],
"slideSequences": [
/* SlideSequence[] — 离线的分支序列 */
]
}
SlideRef
{
"sceneId": "problem",
"notes": "以痛点开头,而非公司介绍。",
"fragments": [3.5, 5.2, 7.0],
"hotspots": [
/* SlideHotspot[] */
],
"ttsScript": null,
"ttsAudioUrl": null,
"ttsDurationMs": null
}
| 字段 | 必需 | 备注 |
|---|---|---|
sceneId | 是 | 必须与场景的 data-composition-id 完全匹配(或提供明确的 startTime/endTime)。精查规则通过 data-composition-id 解析场景。 |
notes | 否 | 仅演示者可见的文本。从不向观众显示。 |
fragments | 否 | 幻灯片 [start, end] 范围内的时间(秒)数组——请参阅下文的片段。 |
hotspots | 否 | 触发分支的交互式覆盖层——请参阅下文的导航。 |
startTime | 否 | 可选。覆盖匹配场景的时间边界;默认为场景的 start/end。 |
endTime | 否 | 可选。覆盖匹配场景的时间边界;默认为场景的 start/end。 |
ttsScript, ttsAudioUrl, ttsDurationMs | 否 | 预留。 模式字段已存在,但 TTS 播放尚未接入。除非正在为将来构建预填充,否则省略。 |
SlideHotspot
{
"id": "h1",
"label": "我们是如何计算这个的?",
"target": "market-deep-dive",
"region": { "x": 60, "y": 10, "w": 35, "h": 20 }
}
| 字段 | 必需 | 备注 |
|---|---|---|
id | 是 | 在幻灯片内唯一。 |
label | 是 | 向观众显示的工具提示/按钮文本。 |
target | 是 | 必须匹配 slideSequences 中的 SlideSequence.id。 |
region | 否 | 占幻灯片百分比的边界框:{x, y, w, h},取值范围 0–100。省略则将热点渲染为一个全屏标签按钮。 |
SlideSequence
{
"id": "market-deep-dive",
"label": "市场容量计算方法",
"slides": [{ "sceneId": "mkt-1" }, { "sceneId": "mkt-2" }]
}
序列内部的 slides 使用与主线路相同的 SlideRef 形状。允许使用片段和嵌套热点。
幻灯片编写规则
这些是硬性约束,而非建议。违反规则的幻灯片在被审阅者看到时将被直接替换。
- 标题必须是完整的句子主张,而非标签。 写“中小企业每周花费14小时进行手动排班”,而不是“排班问题”。该句子应在忽略视觉元素的情况下独立成句。
- 每张幻灯片一个想法 + 一个视觉元素。 如果您想添加第二个项目符号集群或第二个图表,请拆分幻灯片。
- 把重点放在最前面。 最有力的观点放在最前面——无论是在幻灯片上还是在幻灯片组顺序中。投资人从左到右、从上到下阅读,然后停下来。
- 仅采用自下而上的市场容量估算。 不要不经计算就写“500亿美元的市场总规模(TAM)”。从单位经济向上构建:账户数 × 年合同价值,或交易笔数 × 抽成率。
- 最小字体为 30pt 等效值。 在 1920×1080 分辨率下,标题为 72–96px;正文为 48px。对于任何观众必须阅读的文本,切勿低于 40px。
移植源页面
当将现有页面转换为幻灯片时,源保真度是契约的一部分。除非用户明确要求重新设计,否则不要用简化的近似代替源特定的部件。
- 尽可能紧密地保留原始页面的视觉设计、动画语言、交互行为、媒体行为和演示功能。当幻灯片系统支持演示者模式时,使用共享的可编辑备注行为,而非特定于幻灯片组的实现来包含演讲者备注。
- 尽可能精确地从源 DOM/CSS/JS 移植机械视觉元素:自定义播放器、画布可视化工具、时间线、播放头、谱干、展开的圆形、悬停状态和其他交互细节应在转换中保留。
- 将原生
<video>/<audio>元素视为任何自定义媒体 chrome、画布可视化工具、波形、节拍网格或播放头的唯一真实来源。接入源的媒体事件(play、pause、timeupdate、seeking、seeked、ended、ratechange、volumechange)并从media.currentTime派生视觉状态;不要运行一个可能与实际播放漂移的独立计时器。 - 每个带有
src的已复制<video>或<audio>在精查之前都必须具有超帧时间属性:data-start和data-duration,以及当应保留可听原生音频时的data-has-audio="true"。对于幻灯片特定的媒体,使用场景的时间范围;对于可能从多个聚焦幻灯片播放的用户控制的证据视频,使用整个幻灯片组范围。不要在媒体上保留preload="none";使用metadata或auto。 - 在验证之前解析源字体令牌。如果要保留自定义源字体,请为本地/捕获的字体文件添加
@font-face规则。如果使用系统回退,请将令牌化声明(如font-family: var(--f-body))替换为具体的、渲染安全的堆栈(如system-ui, sans-serif或ui-monospace, monospace);不要将var(...)保留为字体系列值。 - 审计源中非典型的页面移动,特别是由滚动、滚轮、触摸、哈希状态、调整大小或
requestAnimationFrame循环驱动的行为。将具有平移/缩放“世界”层的固定视口、视差、固定面板、水平滚动器、滚动擦洗时间线、部分捕捉和缩放到元素相机视为源行为。滚动通常是源的触发转换,因此通过提取其进度停止点、缓和曲线和相机/焦点状态来保留转换,然后将该运动通过时间线位置、片段或可重用的播放器/挂接钩子重新托管到幻灯片导航上。跳转到幻灯片驻留点的独立包装仍然需要一个显式的导航-相机过渡挂钩;仅计算每张幻灯片的相机变换是不够的。不要在幻灯片内部模拟文字上的页面向下滚动过渡;观看者应感受到从一个焦点到另一个焦点的相机移动/缩放,而不是看到网页被滚动。保持每个幻灯片到幻灯片的相机移动连续:避免中间路由停止,除非源在相同边界处明显这样做,否则这些停止会反转 x/y 方向或缩放。在着陆前四处梭动的过渡比更简单的直接焦点移动更糟糕。 - 保留源的媒体裁剪语义。将屏幕截图、推文/社交帖子、产品 UI 截图、图表、文档、代码、排行榜以及任何包含可读文本的图像视为内容证据,而非装饰性媒体:在稳定框架内使用源宽高比(
height: auto)或object-fit: contain。仅当源本身这样做时,或用于故意装饰/背景/电影缩略图时,才使用object-fit: cover。在将这些截图放入幻灯片后,检查所有四个边缘是否有被截断的文本、徽标、控件或标题;对有意义内容的可见裁剪是一个错误,除非源本身进行了裁剪。 - 如果某个行为是幻灯片通用的,则将其放入播放器/控制器或可重用的技能片段中。不要使用一次性幻灯片脚本来解决它。
- 堆叠的场景帧绝不能阻止活动幻灯片上的交互。隐藏帧需要同时有视觉隐藏和事件门控:
.scene-frame {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.scene-frame.is-active {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
如果可见性是强制驱动的,请在可见性控制器中设置所有三个属性(opacity、visibility 和 pointerEvents)。仅靠 opacity: 0 仍会留下一个可能吞噬点击的不可见图层。
片段:幻灯片内的揭示驻留点
片段是幻灯片 [start, end] 范围内的绝对合成时间线时间(秒),控制器应在该点保持一个揭示状态。
工作原理:
- 播放器进入一个分片幻灯片——直接跳转到
fragments[0]并保持。 - 用户按“下一页”(或 →)——控制器跳转到
fragments[1]并保持。 - 在最后一个片段之后,“下一页”前进到下一张幻灯片。
- 没有片段的幻灯片进入幻灯片内部的一个静止帧,通常是其中点,而不精确地在
slide.end。
片段时间必须落在 [start, end] 范围内
...
[为翻译而截断]