投影片撰寫合約
HyperFrames 投影片是一個標準的 HyperFrames 組合——場景、片段、GSAP 時間軸——外加一個額外成分:一個JSON 孤島,用來宣告哪些場景是投影片以及它們如何連接。播放器的 SlideshowController 會讀取這個孤島,並將連續的 GSAP 時間軸轉換成一個離散、可導航的 deck。
請先閱讀 /hyperframes-core,了解基礎組合合約(片段、軌道、data-* 屬性、確定性規則)。此技能僅涵蓋新增部分:孤島結構、投影片撰寫規則、片段、分支、驗證以及包裝元件。
輸出——可導航的 deck,而非線性 MP4
投影片的輸出是運作中的 deck:使用 hyperframes present <project-dir>(或 Studio 簡報模式)來提供服務——播放器的 SlideshowController 會讀取孤島並驅動導航、片段、分支和簡報者模式。請參閱下方的簡報與交接。
請勿對投影片執行 hyperframes render 以產生單一 MP4。 Deck 是以數個頂層場景組合(每個投影片一個 data-composition-id)來撰寫,且沒有包裝它們的主根組合,因此 render 只會解析第一個組合,並產生一個無聲截斷的 MP4(例如,一個 40 秒的 deck 只產生 6 秒)。線性的主線輸出(僅主投影片,排除分支序列)是暫緩的——在此功能推出前,支援的輸出是即時的 present deck 和每個投影片的 snapshot 靜態影像。如果使用者今天需要線性 MP4,請說明此限制,而不是將 render 指向 deck。
意圖確認
如果使用者明確要求投影片、幻燈片或 HyperFrames 投影片,則繼續使用此技能。
如果此技能是從相關請求(例如「簡報」、「募資簡報」、「deck」、「互動 deck」或「轉換此頁面」)觸發,請在撰寫前暫停,說明選擇,然後再要求確認。簡短說明 HyperFrames 投影片是一個可運行的 deck,包含離散投影片、內建導航和簡報者模式、可編輯的講者備註、共享媒體處理,以及交接前的驗證。對於來源頁面轉換,也請提及目標是保留原始頁面的視覺設計、互動、動畫和媒體行為,同時將頁面移動轉換為投影片間的轉場。
然後提出簡短的確認問題:
您要將此做成 HyperFrames 投影片嗎?
當環境提供時,使用是/否選擇 UI;否則以純文字提出問題。
在使用者回答「是」之前,請勿實作投影片。如果他們回答「否」,請停止使用此技能並切換到適當的非投影片工作流程。
兩個部分
1. 場景——以標準方式宣告
每個投影片都由一個場景支援。使用 data-composition-id、data-start、data-duration 和 data-label 來宣告場景:
<div
data-composition-id="problem"
data-start="0"
data-duration="8"
data-label="The problem"
data-width="1920"
data-height="1080"
>
<!-- 片段放置於此 -->
</div>
分支投影片(僅能透過熱點到達,排除在主線之外)以完全相同的方式宣告——它們只會出現在孤島的 slideSequences 條目中,而不是主 slides 陣列中。
2. JSON 孤島——每個組合一個 script 區塊
在組合 HTML 中加入一個 <script type="application/hyperframes-slideshow+json"> 區塊。它包含所有投影片後設資料:
<script type="application/hyperframes-slideshow+json">
{
"slides": [...],
"slideSequences": [...]
}
</script>
孤島是投影片順序、備註、片段停頓點、熱點和分支序列的唯一真實來源。將其放置在 <body> 頂部附近,在場景 div 之前,以便於尋找。
請勿將投影片 manifest 隱藏在替代的 <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)。Lint 規則會透過 data-composition-id 解析場景。 |
notes | 否 | 僅供簡報者使用的文字。絕不向觀眾顯示。 |
fragments | 否 | 投影片 [start, end] 範圍內的時間陣列(秒)——請參閱下方的「片段」。 |
hotspots | 否 | 觸發分支的互動疊加層——請參閱下方的「分支」。 |
startTime | 否 | 可選。覆寫相符場景的時間邊界;預設為場景的開始/結束。 |
endTime | 否 | 可選。覆寫相符場景的時間邊界;預設為場景的開始/結束。 |
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 小時在手動排程上」,而非「排程問題」。該句子應在忽略視覺元素的情況下也能獨立存在。
- 每個投影片一個想法 + 一個視覺元素。 如果您想加入第二個要點叢集或第二個圖表,請拆分投影片。
- 以重點開場。 最有力的觀點放在最前面——在投影片上和 deck 順序中皆是如此。投資者從左到右、從上到下閱讀,他們會停下來。
- 僅使用由下而上的市場規模估算。 絕不寫「500 億美元 TAM」而不展示計算過程。從單位經濟學向上建立:帳戶數 × 年合約價值,或交易數 × 抽成率。
- 最小字體相當於 30pt。 在 1920×1080 解析度下,標題為 72–96px;內文為 48px。觀眾必須閱讀的任何文字,絕不低於 40px。
移植來源頁面
將現有頁面轉換為投影片時,來源保真度是合約的一部分。除非使用者明確要求重新設計,否則請勿用簡化的近似品取代來源特定的 widget。
- 盡可能保留原始頁面的視覺設計、動畫語言、互動行為、媒體行為和簡報輔助功能。當投影片系統支援簡報者模式時,請使用共享的可編輯備註行為來包含講者備註,而不是使用特定於 deck 的實作。
- 盡可能精確地移植來源 DOM/CSS/JS 中的機械式視覺元素:自訂播放器、canvas 視覺化工具、時間軸、播放頭、軸、展開圓形、懸停狀態以及其他互動細節都應在轉換後保留。
- 將原生
<video>/<audio>元素視為任何自訂媒體 chrome、canvas 視覺化工具、波形、節奏網格或播放頭的真正來源。連接來源的媒體事件(play、pause、timeupdate、seeking、seeked、ended、ratechange、volumechange)並從media.currentTime衍生視覺狀態;不要執行一個可能與實際播放不同步的獨立計時器。 - 每個複製且帶有
src的<video>或<audio>都必須在 lint 之前具有 HyperFrames 時間屬性:data-start和data-duration,以及在應保留可聽到的原生音訊時加上data-has-audio="true"。對於特定投影片的媒體,使用場景的時間範圍;對於使用者可能從多個焦點投影片播放的、由使用者控制的證據影片,則使用全 deck 範圍。請勿在媒體上保留preload="none";請使用metadata或auto。 - 在驗證之前解析來源字型 token。如果保留自訂來源字型,請為當地端/擷取的字型檔案新增
@font-face規則。如果使用系統備用字型,請將 token 化宣告(例如font-family: var(--f-body))替換為具體的渲染安全堆疊(例如system-ui, sans-serif或ui-monospace, monospace);請勿將var(...)保留為字型家族值。 - 稽核來源中非典型的頁面移動,特別是受捲動、滾輪、觸控、hash 狀態、調整大小或 requestAnimationFrame 迴圈驅動的行為。將具有平移/縮放「世界」圖層的固定視窗、視差效果、固定面板、水平捲動器、捲動清除時間軸、章節對齊和縮放至元素的相機視為來源行為。捲動通常是來源的轉場觸發器,因此請透過提取其進度停止點、緩動效果和相機/焦點狀態來保留轉場,然後透過時間軸位置、片段或可重複使用的播放器/掛鉤,將該動畫重新託管在投影片導航上。能夠跳到投影片停頓點的獨立包裝器仍然需要明確的導航-相機轉場掛鉤;僅計算每個投影片的相機轉換是不夠的。請勿在投影片內部模擬字面上的頁面「向下捲動」轉場;觀看者應該感受到從一個焦點到另一個焦點的相機移動/縮放,而不是看到一個網頁被捲動。保持每個投影片到投影片的相機移動連續:避免在相同邊界反向 x/y 方向或縮放的中間路由停頓點,除非來源明顯這麼做。一個在抵達前快速移動的轉場比一個更簡單的直接焦點移動更糟糕。
- 保留來源的媒體裁切語義。將螢幕截圖、推文/社交媒體貼文、產品 UI 擷圖、圖表、文件、程式碼、排行榜以及任何具有可讀文字的影像視為內容證據,而非裝飾性媒體:在穩定的框架內使用來源的長寬比(
height: auto)或object-fit: contain。僅在來源使用object-fit: cover時,或對於刻意裝飾性/背景/電影感縮圖時,才使用object-fit: cover。將這些擷取內容放入投影片後,請檢查所有四個邊緣是否有截斷的文字、標誌、控制項或說明文字;對有意義內容的可見裁切是一個錯誤,除非來源本身就有裁切。 - 如果某個行為是投影片通用的,請將其放入播放器/控制器或可重複使用的技能片段中。請勿使用一次性 deck 腳本來解決問題。
- 堆疊的場景框架絕不能阻止活躍投影片上的互動。隱藏框架需要同時具備視覺隱藏和事件阻擋:
.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] 範圍內
…
[為翻譯而截斷]