takomi 2.1.45 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (679) hide show
  1. package/.pi/README.md +5 -5
  2. package/.pi/agents/architect.md +2 -2
  3. package/.pi/agents/designer.md +2 -2
  4. package/.pi/agents/worker.md +32 -0
  5. package/.pi/extensions/oauth-router/README.md +3 -3
  6. package/.pi/extensions/oauth-router/config.ts +34 -34
  7. package/.pi/extensions/oauth-router/report-ui.ts +205 -205
  8. package/.pi/extensions/oauth-router/scripts/vibe-verify.py +5 -5
  9. package/.pi/extensions/takomi-context-manager/diagnostics.ts +560 -560
  10. package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +15 -103
  11. package/.pi/extensions/takomi-context-manager/policy-tools.ts +93 -93
  12. package/.pi/extensions/takomi-context-manager/skill-categories.d.ts +11 -11
  13. package/.pi/extensions/takomi-context-manager/skill-tools.ts +208 -208
  14. package/.pi/extensions/takomi-context-manager/tool-renderers.ts +112 -112
  15. package/.pi/extensions/takomi-runtime/command-text.ts +5 -4
  16. package/.pi/extensions/takomi-runtime/commands.ts +17 -17
  17. package/.pi/extensions/takomi-runtime/gate-provenance.ts +24 -24
  18. package/.pi/extensions/takomi-runtime/index.ts +278 -86
  19. package/.pi/extensions/takomi-runtime/model-routing-defaults.ts +168 -232
  20. package/.pi/extensions/takomi-runtime/profile.ts +9 -8
  21. package/.pi/extensions/takomi-runtime/routing-policy.ts +84 -65
  22. package/.pi/extensions/takomi-runtime/shared.ts +7 -30
  23. package/.pi/extensions/takomi-runtime/tool-renderers.ts +234 -234
  24. package/.pi/extensions/takomi-runtime/workflow-catalog.ts +54 -54
  25. package/.pi/extensions/takomi-subagents/agents.ts +5 -0
  26. package/.pi/extensions/takomi-subagents/async-lifecycle.ts +401 -401
  27. package/.pi/extensions/takomi-subagents/detached-results.ts +940 -940
  28. package/.pi/extensions/takomi-subagents/index.ts +4 -0
  29. package/.pi/extensions/takomi-subagents/native-render.ts +253 -250
  30. package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +7 -1
  31. package/.pi/extensions/takomi-subagents/result-heartbeat.ts +55 -55
  32. package/.pi/extensions/takomi-subagents/subagent-ux.ts +162 -162
  33. package/.pi/extensions/takomi-subagents/tool-runner.ts +65 -1
  34. package/.pi/settings.json +39 -36
  35. package/.pi/takomi/model-routing.md +6 -0
  36. package/.pi/takomi-profile.json +8 -4
  37. package/README.md +1 -1
  38. package/assets/.agent/skills/embedded-captions/CATALOG.md +93 -93
  39. package/assets/.agent/skills/embedded-captions/SKILL.md +286 -286
  40. package/assets/.agent/skills/embedded-captions/assets/brand/CDPR-fankit-terms.txt +35 -35
  41. package/assets/.agent/skills/embedded-captions/assets/brand/cyberpunk-widths.json +115 -115
  42. package/assets/.agent/skills/embedded-captions/assets/fonts/char-widths.json +2211 -2211
  43. package/assets/.agent/skills/embedded-captions/assets/strokefonts/HersheyScript1.svg +241 -241
  44. package/assets/.agent/skills/embedded-captions/assets/strokefonts/HersheyScriptMed.svg +241 -241
  45. package/assets/.agent/skills/embedded-captions/dna/README.md +148 -148
  46. package/assets/.agent/skills/embedded-captions/dna/chrome.json +68 -68
  47. package/assets/.agent/skills/embedded-captions/dna/cream.json +69 -69
  48. package/assets/.agent/skills/embedded-captions/dna/documentary.json +62 -62
  49. package/assets/.agent/skills/embedded-captions/dna/editorial.json +70 -70
  50. package/assets/.agent/skills/embedded-captions/dna/glitch.json +82 -82
  51. package/assets/.agent/skills/embedded-captions/dna/ink.json +65 -65
  52. package/assets/.agent/skills/embedded-captions/dna/keynote.json +62 -62
  53. package/assets/.agent/skills/embedded-captions/dna/loud.json +75 -75
  54. package/assets/.agent/skills/embedded-captions/dna/neon.json +58 -58
  55. package/assets/.agent/skills/embedded-captions/dna/velocity.json +89 -89
  56. package/assets/.agent/skills/embedded-captions/modes/cinematic/README.md +48 -48
  57. package/assets/.agent/skills/embedded-captions/modes/cinematic/_archive/champion/spec.md +98 -98
  58. package/assets/.agent/skills/embedded-captions/modes/cinematic/_archive/champion/template.html +188 -188
  59. package/assets/.agent/skills/embedded-captions/modes/cinematic/_archive/memory-wall/spec.md +134 -134
  60. package/assets/.agent/skills/embedded-captions/modes/cinematic/_archive/memory-wall/template.html +179 -179
  61. package/assets/.agent/skills/embedded-captions/modes/cinematic/_archive/portrait-header/spec.md +81 -81
  62. package/assets/.agent/skills/embedded-captions/modes/cinematic/_archive/portrait-header/template.html +170 -170
  63. package/assets/.agent/skills/embedded-captions/modes/cinematic/cinematic-cream/spec.md +16 -16
  64. package/assets/.agent/skills/embedded-captions/modes/cinematic/cinematic-cream/template.html +226 -226
  65. package/assets/.agent/skills/embedded-captions/modes/cinematic/engine.html +355 -355
  66. package/assets/.agent/skills/embedded-captions/modes/standard/_anatomy.md +233 -233
  67. package/assets/.agent/skills/embedded-captions/modes/standard/_motion.md +192 -192
  68. package/assets/.agent/skills/embedded-captions/modes/standard/fonts/build-fonts-css.cjs +97 -97
  69. package/assets/.agent/skills/embedded-captions/references/aesthetic-principles.md +168 -168
  70. package/assets/.agent/skills/embedded-captions/references/anti-patterns.md +188 -188
  71. package/assets/.agent/skills/embedded-captions/references/bespoke-vs-presets.md +178 -178
  72. package/assets/.agent/skills/embedded-captions/references/caption-grouping.md +130 -130
  73. package/assets/.agent/skills/embedded-captions/references/composition-craft.md +522 -522
  74. package/assets/.agent/skills/embedded-captions/references/direction-catalog.md +138 -138
  75. package/assets/.agent/skills/embedded-captions/references/example-renders/champion.html +371 -371
  76. package/assets/.agent/skills/embedded-captions/references/example-renders/memory-wall.html +334 -334
  77. package/assets/.agent/skills/embedded-captions/references/failure-modes.md +145 -145
  78. package/assets/.agent/skills/embedded-captions/references/layout-heuristics.md +238 -238
  79. package/assets/.agent/skills/embedded-captions/references/motion-vocabulary.md +148 -148
  80. package/assets/.agent/skills/embedded-captions/references/rail.md +68 -68
  81. package/assets/.agent/skills/embedded-captions/references/reference-bar.md +47 -47
  82. package/assets/.agent/skills/embedded-captions/references/scene-types.md +94 -94
  83. package/assets/.agent/skills/embedded-captions/references/test-set.md +66 -66
  84. package/assets/.agent/skills/embedded-captions/references/typographic-moves.md +266 -266
  85. package/assets/.agent/skills/embedded-captions/references/typography-presets.md +63 -63
  86. package/assets/.agent/skills/embedded-captions/scripts/audio-envelope.cjs +95 -95
  87. package/assets/.agent/skills/embedded-captions/scripts/check-occlusion.cjs +250 -250
  88. package/assets/.agent/skills/embedded-captions/scripts/check-overflow.cjs +194 -194
  89. package/assets/.agent/skills/embedded-captions/scripts/check-rail-climax.cjs +233 -233
  90. package/assets/.agent/skills/embedded-captions/scripts/check-timing.cjs +173 -173
  91. package/assets/.agent/skills/embedded-captions/scripts/fill-timings.cjs +124 -124
  92. package/assets/.agent/skills/embedded-captions/scripts/fit-fonts.cjs +158 -158
  93. package/assets/.agent/skills/embedded-captions/scripts/gen-stroke-path.py +46 -46
  94. package/assets/.agent/skills/embedded-captions/scripts/inject-fonts.cjs +169 -169
  95. package/assets/.agent/skills/embedded-captions/scripts/lib-dna.cjs +201 -201
  96. package/assets/.agent/skills/embedded-captions/scripts/make-cinematic.cjs +1205 -1205
  97. package/assets/.agent/skills/embedded-captions/scripts/make-composition.cjs +404 -404
  98. package/assets/.agent/skills/embedded-captions/scripts/make-theme.cjs +8781 -8781
  99. package/assets/.agent/skills/embedded-captions/scripts/matte.cjs +279 -279
  100. package/assets/.agent/skills/embedded-captions/scripts/measure-layout.cjs +293 -293
  101. package/assets/.agent/skills/embedded-captions/scripts/prepare.sh +39 -39
  102. package/assets/.agent/skills/embedded-captions/scripts/preview-frames.cjs +268 -268
  103. package/assets/.agent/skills/embedded-captions/scripts/render-and-composite.sh +469 -469
  104. package/assets/.agent/skills/embedded-captions/scripts/render-theme.sh +34 -34
  105. package/assets/.agent/skills/embedded-captions/scripts/safe-zones.cjs +813 -813
  106. package/assets/.agent/skills/embedded-captions/scripts/transcribe.cjs +332 -332
  107. package/assets/.agent/skills/embedded-captions/themes/PORTING.md +49 -49
  108. package/assets/.agent/skills/embedded-captions/themes/README.md +244 -244
  109. package/assets/.agent/skills/embedded-captions/themes/anchor.json +27 -27
  110. package/assets/.agent/skills/embedded-captions/themes/arcade.json +51 -51
  111. package/assets/.agent/skills/embedded-captions/themes/aurora.json +41 -41
  112. package/assets/.agent/skills/embedded-captions/themes/biolume.json +35 -35
  113. package/assets/.agent/skills/embedded-captions/themes/brush.json +74 -74
  114. package/assets/.agent/skills/embedded-captions/themes/chalkboard.json +52 -52
  115. package/assets/.agent/skills/embedded-captions/themes/dossier.json +56 -56
  116. package/assets/.agent/skills/embedded-captions/themes/graffiti.json +62 -62
  117. package/assets/.agent/skills/embedded-captions/themes/hologram.json +41 -41
  118. package/assets/.agent/skills/embedded-captions/themes/inkwater.json +43 -43
  119. package/assets/.agent/skills/embedded-captions/themes/laser.json +46 -46
  120. package/assets/.agent/skills/embedded-captions/themes/lastpage.json +33 -33
  121. package/assets/.agent/skills/embedded-captions/themes/neonsign.json +53 -53
  122. package/assets/.agent/skills/embedded-captions/themes/nightcity.json +56 -56
  123. package/assets/.agent/skills/embedded-captions/themes/ordnance.json +54 -54
  124. package/assets/.agent/skills/embedded-captions/themes/papercut.json +37 -37
  125. package/assets/.agent/skills/embedded-captions/themes/popup.json +46 -46
  126. package/assets/.agent/skills/embedded-captions/themes/ransom.json +62 -62
  127. package/assets/.agent/skills/embedded-captions/themes/scoreboard.json +54 -54
  128. package/assets/.agent/skills/embedded-captions/themes/spectrum.json +46 -46
  129. package/assets/.agent/skills/embedded-captions/themes/stardust.json +29 -29
  130. package/assets/.agent/skills/embedded-captions/themes/stomp.json +43 -43
  131. package/assets/.agent/skills/embedded-captions/themes/terminal.json +49 -49
  132. package/assets/.agent/skills/embedded-captions/themes/thunder.json +44 -44
  133. package/assets/.agent/skills/embedded-captions/themes/transit.json +44 -44
  134. package/assets/.agent/skills/embedded-captions/themes/vhs.json +47 -47
  135. package/assets/.agent/skills/faceless-explainer/SKILL.md +209 -209
  136. package/assets/.agent/skills/faceless-explainer/references/cut-catalog.md +215 -215
  137. package/assets/.agent/skills/faceless-explainer/references/motion-language.md +156 -156
  138. package/assets/.agent/skills/faceless-explainer/references/story-design.md +248 -248
  139. package/assets/.agent/skills/faceless-explainer/references/visual-design.md +146 -146
  140. package/assets/.agent/skills/faceless-explainer/scripts/assemble-index.mjs +570 -570
  141. package/assets/.agent/skills/faceless-explainer/scripts/audio.mjs +253 -253
  142. package/assets/.agent/skills/faceless-explainer/scripts/build-frame.mjs +536 -536
  143. package/assets/.agent/skills/faceless-explainer/scripts/captions.mjs +508 -508
  144. package/assets/.agent/skills/faceless-explainer/scripts/lib/assets.mjs +55 -55
  145. package/assets/.agent/skills/faceless-explainer/scripts/lib/dimensions.mjs +45 -45
  146. package/assets/.agent/skills/faceless-explainer/scripts/lib/storyboard.mjs +249 -249
  147. package/assets/.agent/skills/faceless-explainer/scripts/lib/tokens.mjs +204 -204
  148. package/assets/.agent/skills/faceless-explainer/scripts/lib/transition-registry.mjs +38 -38
  149. package/assets/.agent/skills/faceless-explainer/scripts/lib/transitions.json +71 -71
  150. package/assets/.agent/skills/faceless-explainer/scripts/transitions.mjs +320 -320
  151. package/assets/.agent/skills/faceless-explainer/sub-agents/frame-worker.md +75 -75
  152. package/assets/.agent/skills/figma/SKILL.md +121 -121
  153. package/assets/.agent/skills/general-video/SKILL.md +143 -143
  154. package/assets/.agent/skills/hyperframes/SKILL.md +164 -164
  155. package/assets/.agent/skills/hyperframes-animation/SKILL.md +84 -84
  156. package/assets/.agent/skills/hyperframes-animation/adapters/animate-text.md +64 -64
  157. package/assets/.agent/skills/hyperframes-animation/adapters/animejs.md +114 -114
  158. package/assets/.agent/skills/hyperframes-animation/adapters/css-animations.md +143 -143
  159. package/assets/.agent/skills/hyperframes-animation/adapters/gsap-easing-and-stagger.md +118 -118
  160. package/assets/.agent/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md +96 -96
  161. package/assets/.agent/skills/hyperframes-animation/adapters/gsap-transforms-and-perf.md +96 -96
  162. package/assets/.agent/skills/hyperframes-animation/adapters/gsap.md +105 -105
  163. package/assets/.agent/skills/hyperframes-animation/adapters/html-in-canvas-patterns.md +507 -507
  164. package/assets/.agent/skills/hyperframes-animation/adapters/lottie.md +117 -117
  165. package/assets/.agent/skills/hyperframes-animation/adapters/three.md +131 -131
  166. package/assets/.agent/skills/hyperframes-animation/adapters/typegpu.md +178 -178
  167. package/assets/.agent/skills/hyperframes-animation/adapters/waapi.md +101 -101
  168. package/assets/.agent/skills/hyperframes-animation/blueprints/comparison-split.md +27 -27
  169. package/assets/.agent/skills/hyperframes-animation/blueprints/constellation-hub.md +44 -44
  170. package/assets/.agent/skills/hyperframes-animation/blueprints/cta-morph-press.md +28 -28
  171. package/assets/.agent/skills/hyperframes-animation/blueprints/cursor-ui-demo.md +57 -57
  172. package/assets/.agent/skills/hyperframes-animation/blueprints/dataviz-countup.md +46 -46
  173. package/assets/.agent/skills/hyperframes-animation/blueprints/device-surface-showcase.md +53 -53
  174. package/assets/.agent/skills/hyperframes-animation/blueprints/grid-card-assemble.md +66 -66
  175. package/assets/.agent/skills/hyperframes-animation/blueprints/kinetic-type-beats.md +78 -78
  176. package/assets/.agent/skills/hyperframes-animation/blueprints/logo-assemble-lockup.md +69 -69
  177. package/assets/.agent/skills/hyperframes-animation/blueprints/overwhelm-surround.md +28 -28
  178. package/assets/.agent/skills/hyperframes-animation/blueprints/spatial-pan-stations.md +37 -37
  179. package/assets/.agent/skills/hyperframes-animation/blueprints/ticker-takeover.md +29 -29
  180. package/assets/.agent/skills/hyperframes-animation/blueprints/titlecard-reveal.md +42 -42
  181. package/assets/.agent/skills/hyperframes-animation/blueprints/typewriter-reveal.md +51 -51
  182. package/assets/.agent/skills/hyperframes-animation/blueprints/video-text-pivot.md +30 -30
  183. package/assets/.agent/skills/hyperframes-animation/blueprints-index.md +146 -146
  184. package/assets/.agent/skills/hyperframes-animation/examples/brand-reveal-assemble-zoom.html +382 -382
  185. package/assets/.agent/skills/hyperframes-animation/examples/comparison-split-cards.html +649 -649
  186. package/assets/.agent/skills/hyperframes-animation/examples/concept-demo-decode-pan.html +520 -520
  187. package/assets/.agent/skills/hyperframes-animation/examples/cta-morph-press.html +468 -468
  188. package/assets/.agent/skills/hyperframes-animation/examples/cta-orbit-collapse.html +1298 -1298
  189. package/assets/.agent/skills/hyperframes-animation/examples/demo-page-scroll-spotlight.html +759 -759
  190. package/assets/.agent/skills/hyperframes-animation/examples/hook-counter-burst.html +729 -729
  191. package/assets/.agent/skills/hyperframes-animation/examples/messaging-multi-phrase.html +352 -352
  192. package/assets/.agent/skills/hyperframes-animation/examples/metric-video-text-pivot.html +779 -779
  193. package/assets/.agent/skills/hyperframes-animation/examples/problem-mockup-overwhelm.html +1374 -1374
  194. package/assets/.agent/skills/hyperframes-animation/examples/proof-logo-chain.html +861 -861
  195. package/assets/.agent/skills/hyperframes-animation/examples/takeover-ticker-displace.html +347 -347
  196. package/assets/.agent/skills/hyperframes-animation/examples/workflow-approve-press.html +606 -606
  197. package/assets/.agent/skills/hyperframes-animation/rules/3d-page-scroll.md +227 -227
  198. package/assets/.agent/skills/hyperframes-animation/rules/3d-text-depth-layers.md +297 -297
  199. package/assets/.agent/skills/hyperframes-animation/rules/ai-tracking-box.md +382 -382
  200. package/assets/.agent/skills/hyperframes-animation/rules/ambient-glow-bloom.md +305 -305
  201. package/assets/.agent/skills/hyperframes-animation/rules/asr-keyword-glow.md +286 -286
  202. package/assets/.agent/skills/hyperframes-animation/rules/avatar-cloud-network.md +371 -371
  203. package/assets/.agent/skills/hyperframes-animation/rules/camera-cursor-tracking.md +246 -246
  204. package/assets/.agent/skills/hyperframes-animation/rules/card-morph-anchor.md +267 -267
  205. package/assets/.agent/skills/hyperframes-animation/rules/center-outward-expansion.md +227 -227
  206. package/assets/.agent/skills/hyperframes-animation/rules/context-sensitive-cursor.md +257 -257
  207. package/assets/.agent/skills/hyperframes-animation/rules/coordinate-target-zoom.md +332 -332
  208. package/assets/.agent/skills/hyperframes-animation/rules/counting-dynamic-scale.md +283 -283
  209. package/assets/.agent/skills/hyperframes-animation/rules/css-marker-patterns.md +373 -373
  210. package/assets/.agent/skills/hyperframes-animation/rules/cursor-click-ripple.md +262 -262
  211. package/assets/.agent/skills/hyperframes-animation/rules/depth-of-field-blur.md +313 -313
  212. package/assets/.agent/skills/hyperframes-animation/rules/depth-scatter-assemble.md +303 -303
  213. package/assets/.agent/skills/hyperframes-animation/rules/discrete-text-sequence.md +273 -273
  214. package/assets/.agent/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +307 -307
  215. package/assets/.agent/skills/hyperframes-animation/rules/gsap-effects.md +299 -299
  216. package/assets/.agent/skills/hyperframes-animation/rules/hacker-flip-3d.md +223 -223
  217. package/assets/.agent/skills/hyperframes-animation/rules/kinetic-beat-slam.md +183 -183
  218. package/assets/.agent/skills/hyperframes-animation/rules/motion-blur-streak.md +328 -328
  219. package/assets/.agent/skills/hyperframes-animation/rules/multi-phase-camera.md +273 -273
  220. package/assets/.agent/skills/hyperframes-animation/rules/orbit-3d-entry.md +301 -301
  221. package/assets/.agent/skills/hyperframes-animation/rules/physics-press-reaction.md +350 -350
  222. package/assets/.agent/skills/hyperframes-animation/rules/press-release-spring.md +296 -296
  223. package/assets/.agent/skills/hyperframes-animation/rules/reactive-displacement.md +277 -277
  224. package/assets/.agent/skills/hyperframes-animation/rules/scale-swap-transition.md +298 -298
  225. package/assets/.agent/skills/hyperframes-animation/rules/sine-wave-loop.md +278 -278
  226. package/assets/.agent/skills/hyperframes-animation/rules/split-tilt-cards.md +277 -277
  227. package/assets/.agent/skills/hyperframes-animation/rules/spring-pop-entrance.md +273 -273
  228. package/assets/.agent/skills/hyperframes-animation/rules/stat-bars-and-fills.md +156 -156
  229. package/assets/.agent/skills/hyperframes-animation/rules/svg-icon-enrichment.md +329 -329
  230. package/assets/.agent/skills/hyperframes-animation/rules/svg-path-draw.md +274 -274
  231. package/assets/.agent/skills/hyperframes-animation/rules/vertical-spring-ticker.md +239 -239
  232. package/assets/.agent/skills/hyperframes-animation/rules/viewport-change.md +349 -349
  233. package/assets/.agent/skills/hyperframes-animation/rules-index.md +86 -86
  234. package/assets/.agent/skills/hyperframes-animation/scripts/animation-map.mjs +606 -606
  235. package/assets/.agent/skills/hyperframes-animation/scripts/package-loader.mjs +288 -288
  236. package/assets/.agent/skills/hyperframes-animation/scripts/package-loader.test.mjs +62 -62
  237. package/assets/.agent/skills/hyperframes-animation/techniques.md +507 -507
  238. package/assets/.agent/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +167 -167
  239. package/assets/.agent/skills/hyperframes-animation/transitions/catalog.md +127 -127
  240. package/assets/.agent/skills/hyperframes-animation/transitions/css-3d.md +12 -12
  241. package/assets/.agent/skills/hyperframes-animation/transitions/css-blur.md +51 -51
  242. package/assets/.agent/skills/hyperframes-animation/transitions/css-cover.md +43 -43
  243. package/assets/.agent/skills/hyperframes-animation/transitions/css-destruction.md +95 -95
  244. package/assets/.agent/skills/hyperframes-animation/transitions/css-dissolve.md +66 -66
  245. package/assets/.agent/skills/hyperframes-animation/transitions/css-distortion.md +45 -45
  246. package/assets/.agent/skills/hyperframes-animation/transitions/css-grid.md +10 -10
  247. package/assets/.agent/skills/hyperframes-animation/transitions/css-light.md +49 -49
  248. package/assets/.agent/skills/hyperframes-animation/transitions/css-mechanical.md +30 -30
  249. package/assets/.agent/skills/hyperframes-animation/transitions/css-other.md +25 -25
  250. package/assets/.agent/skills/hyperframes-animation/transitions/css-push.md +41 -41
  251. package/assets/.agent/skills/hyperframes-animation/transitions/css-radial.md +37 -37
  252. package/assets/.agent/skills/hyperframes-animation/transitions/css-scale.md +24 -24
  253. package/assets/.agent/skills/hyperframes-animation/transitions/overview.md +153 -153
  254. package/assets/.agent/skills/hyperframes-cli/SKILL.md +113 -113
  255. package/assets/.agent/skills/hyperframes-cli/references/doctor-browser.md +45 -45
  256. package/assets/.agent/skills/hyperframes-cli/references/init-and-scaffold.md +51 -51
  257. package/assets/.agent/skills/hyperframes-cli/references/lambda.md +132 -132
  258. package/assets/.agent/skills/hyperframes-cli/references/lint-validate-inspect.md +121 -121
  259. package/assets/.agent/skills/hyperframes-cli/references/preview-render.md +164 -164
  260. package/assets/.agent/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -75
  261. package/assets/.agent/skills/hyperframes-core/SKILL.md +78 -78
  262. package/assets/.agent/skills/hyperframes-core/references/composition-patterns.md +263 -263
  263. package/assets/.agent/skills/hyperframes-core/references/data-attributes.md +70 -70
  264. package/assets/.agent/skills/hyperframes-core/references/determinism-rules.md +68 -68
  265. package/assets/.agent/skills/hyperframes-core/references/full-screen-motion.md +62 -62
  266. package/assets/.agent/skills/hyperframes-core/references/minimal-composition.md +66 -66
  267. package/assets/.agent/skills/hyperframes-core/references/script-format.md +49 -49
  268. package/assets/.agent/skills/hyperframes-core/references/storyboard-format.md +95 -95
  269. package/assets/.agent/skills/hyperframes-core/references/sub-compositions.md +237 -237
  270. package/assets/.agent/skills/hyperframes-core/references/subagent-dispatch.md +41 -41
  271. package/assets/.agent/skills/hyperframes-core/references/tailwind.md +125 -125
  272. package/assets/.agent/skills/hyperframes-core/references/tracks-and-clips.md +76 -76
  273. package/assets/.agent/skills/hyperframes-core/references/variables-and-media.md +90 -90
  274. package/assets/.agent/skills/hyperframes-creative/SKILL.md +70 -70
  275. package/assets/.agent/skills/hyperframes-creative/frame-presets/biennale-yellow/FRAME.md +289 -289
  276. package/assets/.agent/skills/hyperframes-creative/frame-presets/biennale-yellow/caption-skin.html +221 -221
  277. package/assets/.agent/skills/hyperframes-creative/frame-presets/biennale-yellow/frame-showcase.html +1214 -1214
  278. package/assets/.agent/skills/hyperframes-creative/frame-presets/blockframe/FRAME.md +275 -275
  279. package/assets/.agent/skills/hyperframes-creative/frame-presets/blockframe/caption-skin.html +224 -224
  280. package/assets/.agent/skills/hyperframes-creative/frame-presets/blockframe/frame-showcase.html +1347 -1347
  281. package/assets/.agent/skills/hyperframes-creative/frame-presets/blue-professional/FRAME.md +307 -307
  282. package/assets/.agent/skills/hyperframes-creative/frame-presets/blue-professional/caption-skin.html +223 -223
  283. package/assets/.agent/skills/hyperframes-creative/frame-presets/blue-professional/frame-showcase.html +1372 -1372
  284. package/assets/.agent/skills/hyperframes-creative/frame-presets/bold-poster/FRAME.md +275 -275
  285. package/assets/.agent/skills/hyperframes-creative/frame-presets/bold-poster/caption-skin.html +230 -230
  286. package/assets/.agent/skills/hyperframes-creative/frame-presets/bold-poster/frame-showcase.html +1140 -1140
  287. package/assets/.agent/skills/hyperframes-creative/frame-presets/broadside/FRAME.md +284 -284
  288. package/assets/.agent/skills/hyperframes-creative/frame-presets/broadside/caption-skin.html +225 -225
  289. package/assets/.agent/skills/hyperframes-creative/frame-presets/broadside/frame-showcase.html +1166 -1166
  290. package/assets/.agent/skills/hyperframes-creative/frame-presets/capsule/FRAME.md +284 -284
  291. package/assets/.agent/skills/hyperframes-creative/frame-presets/capsule/caption-skin.html +224 -224
  292. package/assets/.agent/skills/hyperframes-creative/frame-presets/capsule/frame-showcase.html +1465 -1465
  293. package/assets/.agent/skills/hyperframes-creative/frame-presets/cartesian/FRAME.md +278 -278
  294. package/assets/.agent/skills/hyperframes-creative/frame-presets/cartesian/caption-skin.html +222 -222
  295. package/assets/.agent/skills/hyperframes-creative/frame-presets/cartesian/frame-showcase.html +1263 -1263
  296. package/assets/.agent/skills/hyperframes-creative/frame-presets/claude/FRAME.md +277 -277
  297. package/assets/.agent/skills/hyperframes-creative/frame-presets/claude/caption-skin.html +224 -224
  298. package/assets/.agent/skills/hyperframes-creative/frame-presets/claude/frame-showcase.html +1439 -1439
  299. package/assets/.agent/skills/hyperframes-creative/frame-presets/cobalt-grid/FRAME.md +265 -265
  300. package/assets/.agent/skills/hyperframes-creative/frame-presets/cobalt-grid/caption-skin.html +238 -238
  301. package/assets/.agent/skills/hyperframes-creative/frame-presets/cobalt-grid/frame-showcase.html +1468 -1468
  302. package/assets/.agent/skills/hyperframes-creative/frame-presets/coral/FRAME.md +314 -314
  303. package/assets/.agent/skills/hyperframes-creative/frame-presets/coral/caption-skin.html +226 -226
  304. package/assets/.agent/skills/hyperframes-creative/frame-presets/coral/frame-showcase.html +1268 -1268
  305. package/assets/.agent/skills/hyperframes-creative/frame-presets/creative-mode/FRAME.md +333 -333
  306. package/assets/.agent/skills/hyperframes-creative/frame-presets/creative-mode/caption-skin.html +227 -227
  307. package/assets/.agent/skills/hyperframes-creative/frame-presets/creative-mode/frame-showcase.html +1330 -1330
  308. package/assets/.agent/skills/hyperframes-creative/frame-presets/daisy-days/FRAME.md +274 -274
  309. package/assets/.agent/skills/hyperframes-creative/frame-presets/daisy-days/caption-skin.html +230 -230
  310. package/assets/.agent/skills/hyperframes-creative/frame-presets/daisy-days/frame-showcase.html +1444 -1444
  311. package/assets/.agent/skills/hyperframes-creative/frame-presets/editorial-forest/FRAME.md +265 -265
  312. package/assets/.agent/skills/hyperframes-creative/frame-presets/editorial-forest/caption-skin.html +223 -223
  313. package/assets/.agent/skills/hyperframes-creative/frame-presets/editorial-forest/frame-showcase.html +1273 -1273
  314. package/assets/.agent/skills/hyperframes-creative/palettes/bold-energetic.md +14 -14
  315. package/assets/.agent/skills/hyperframes-creative/palettes/clean-corporate.md +14 -14
  316. package/assets/.agent/skills/hyperframes-creative/palettes/dark-premium.md +14 -14
  317. package/assets/.agent/skills/hyperframes-creative/palettes/jewel-rich.md +14 -14
  318. package/assets/.agent/skills/hyperframes-creative/palettes/monochrome.md +14 -14
  319. package/assets/.agent/skills/hyperframes-creative/palettes/nature-earth.md +14 -14
  320. package/assets/.agent/skills/hyperframes-creative/palettes/neon-electric.md +14 -14
  321. package/assets/.agent/skills/hyperframes-creative/palettes/pastel-soft.md +14 -14
  322. package/assets/.agent/skills/hyperframes-creative/palettes/warm-editorial.md +14 -14
  323. package/assets/.agent/skills/hyperframes-creative/references/audio-reactive.md +76 -76
  324. package/assets/.agent/skills/hyperframes-creative/references/beat-direction.md +164 -164
  325. package/assets/.agent/skills/hyperframes-creative/references/composition-patterns.md +199 -199
  326. package/assets/.agent/skills/hyperframes-creative/references/data-in-motion.md +19 -19
  327. package/assets/.agent/skills/hyperframes-creative/references/design-adherence.md +19 -19
  328. package/assets/.agent/skills/hyperframes-creative/references/design-picker.md +123 -123
  329. package/assets/.agent/skills/hyperframes-creative/references/design-spec.md +55 -55
  330. package/assets/.agent/skills/hyperframes-creative/references/house-style.md +73 -73
  331. package/assets/.agent/skills/hyperframes-creative/references/motion-principles.md +150 -150
  332. package/assets/.agent/skills/hyperframes-creative/references/narration.md +92 -92
  333. package/assets/.agent/skills/hyperframes-creative/references/prompt-expansion.md +68 -68
  334. package/assets/.agent/skills/hyperframes-creative/references/typography.md +218 -218
  335. package/assets/.agent/skills/hyperframes-creative/references/video-composition.md +63 -63
  336. package/assets/.agent/skills/hyperframes-creative/references/visual-styles.md +457 -457
  337. package/assets/.agent/skills/hyperframes-creative/scripts/contrast-report.mjs +353 -353
  338. package/assets/.agent/skills/hyperframes-creative/scripts/extract-audio-data.py +188 -188
  339. package/assets/.agent/skills/hyperframes-creative/scripts/package-loader.mjs +288 -288
  340. package/assets/.agent/skills/hyperframes-creative/scripts/package-loader.test.mjs +62 -62
  341. package/assets/.agent/skills/hyperframes-creative/templates/design-picker.html +1432 -1432
  342. package/assets/.agent/skills/hyperframes-keyframes/SKILL.md +237 -237
  343. package/assets/.agent/skills/hyperframes-keyframes/agents/openai.yaml +4 -4
  344. package/assets/.agent/skills/hyperframes-keyframes/references/keyframe-patterns.md +106 -106
  345. package/assets/.agent/skills/hyperframes-media/SKILL.md +97 -97
  346. package/assets/.agent/skills/hyperframes-media/assets/sfx/CREDITS.md +35 -35
  347. package/assets/.agent/skills/hyperframes-media/assets/sfx/manifest.json +97 -97
  348. package/assets/.agent/skills/hyperframes-media/references/bgm.md +72 -72
  349. package/assets/.agent/skills/hyperframes-media/references/captions/authoring.md +159 -159
  350. package/assets/.agent/skills/hyperframes-media/references/captions/motion.md +87 -87
  351. package/assets/.agent/skills/hyperframes-media/references/captions/transcript-handling.md +97 -97
  352. package/assets/.agent/skills/hyperframes-media/references/remove-background.md +143 -143
  353. package/assets/.agent/skills/hyperframes-media/references/requirements.md +29 -29
  354. package/assets/.agent/skills/hyperframes-media/references/sfx.md +42 -42
  355. package/assets/.agent/skills/hyperframes-media/references/transcribe.md +52 -52
  356. package/assets/.agent/skills/hyperframes-media/references/tts-to-captions.md +24 -24
  357. package/assets/.agent/skills/hyperframes-media/references/tts.md +135 -135
  358. package/assets/.agent/skills/hyperframes-media/scripts/audio.mjs +293 -293
  359. package/assets/.agent/skills/hyperframes-media/scripts/heygen-tts.mjs +121 -121
  360. package/assets/.agent/skills/hyperframes-media/scripts/lib/bgm.mjs +235 -235
  361. package/assets/.agent/skills/hyperframes-media/scripts/lib/concurrency.mjs +14 -14
  362. package/assets/.agent/skills/hyperframes-media/scripts/lib/concurrency.test.mjs +41 -41
  363. package/assets/.agent/skills/hyperframes-media/scripts/lib/heygen.mjs +131 -131
  364. package/assets/.agent/skills/hyperframes-media/scripts/lib/sfx.mjs +128 -128
  365. package/assets/.agent/skills/hyperframes-media/scripts/lib/tts.mjs +298 -298
  366. package/assets/.agent/skills/hyperframes-media/scripts/lib/tts.spawn.test.mjs +97 -97
  367. package/assets/.agent/skills/hyperframes-media/scripts/lib/tts.test.mjs +66 -66
  368. package/assets/.agent/skills/hyperframes-media/scripts/lyria-recipe.py +128 -128
  369. package/assets/.agent/skills/hyperframes-media/scripts/wait-bgm.mjs +167 -167
  370. package/assets/.agent/skills/hyperframes-registry/SKILL.md +101 -101
  371. package/assets/.agent/skills/hyperframes-registry/examples/add-block.md +51 -51
  372. package/assets/.agent/skills/hyperframes-registry/examples/add-component.md +73 -73
  373. package/assets/.agent/skills/hyperframes-registry/references/contributing.md +166 -166
  374. package/assets/.agent/skills/hyperframes-registry/references/demo-html-pattern.md +54 -54
  375. package/assets/.agent/skills/hyperframes-registry/references/discovery.md +215 -215
  376. package/assets/.agent/skills/hyperframes-registry/references/install-locations.md +45 -45
  377. package/assets/.agent/skills/hyperframes-registry/references/templates.md +417 -417
  378. package/assets/.agent/skills/hyperframes-registry/references/wiring-blocks.md +61 -61
  379. package/assets/.agent/skills/hyperframes-registry/references/wiring-components.md +39 -39
  380. package/assets/.agent/skills/media-use/SKILL.md +124 -124
  381. package/assets/.agent/skills/media-use/scripts/eval.mjs +369 -369
  382. package/assets/.agent/skills/media-use/scripts/lib/adopt.mjs +112 -112
  383. package/assets/.agent/skills/media-use/scripts/lib/bgm-provider.mjs +20 -20
  384. package/assets/.agent/skills/media-use/scripts/lib/brand-provider.mjs +59 -59
  385. package/assets/.agent/skills/media-use/scripts/lib/cache.mjs +114 -114
  386. package/assets/.agent/skills/media-use/scripts/lib/freeze.mjs +26 -26
  387. package/assets/.agent/skills/media-use/scripts/lib/heygen-search.mjs +51 -51
  388. package/assets/.agent/skills/media-use/scripts/lib/image-provider.mjs +44 -44
  389. package/assets/.agent/skills/media-use/scripts/lib/index-gen.mjs +63 -63
  390. package/assets/.agent/skills/media-use/scripts/lib/manifest.mjs +91 -91
  391. package/assets/.agent/skills/media-use/scripts/lib/manifest.test.mjs +293 -293
  392. package/assets/.agent/skills/media-use/scripts/lib/probe.mjs +39 -39
  393. package/assets/.agent/skills/media-use/scripts/lib/probe.test.mjs +31 -31
  394. package/assets/.agent/skills/media-use/scripts/lib/providers.mjs +29 -29
  395. package/assets/.agent/skills/media-use/scripts/lib/sfx-provider.mjs +23 -23
  396. package/assets/.agent/skills/media-use/scripts/resolve.mjs +247 -247
  397. package/assets/.agent/skills/media-use/scripts/resolve.test.mjs +247 -247
  398. package/assets/.agent/skills/motion-graphics/SKILL.md +172 -172
  399. package/assets/.agent/skills/motion-graphics/agents/builder.md +40 -40
  400. package/assets/.agent/skills/motion-graphics/agents/director.md +53 -53
  401. package/assets/.agent/skills/motion-graphics/agents/finalize.md +17 -17
  402. package/assets/.agent/skills/motion-graphics/catalog-map.md +40 -40
  403. package/assets/.agent/skills/motion-graphics/categories/asset-fusion/module.md +37 -37
  404. package/assets/.agent/skills/motion-graphics/categories/charts/module.md +21 -21
  405. package/assets/.agent/skills/motion-graphics/categories/kinetic-type/module.md +19 -19
  406. package/assets/.agent/skills/motion-graphics/categories/logo-reveal/module.md +17 -17
  407. package/assets/.agent/skills/motion-graphics/categories/lower-thirds/module.md +16 -16
  408. package/assets/.agent/skills/motion-graphics/categories/maps/bake-basemap.mjs +273 -273
  409. package/assets/.agent/skills/motion-graphics/categories/maps/module.md +61 -61
  410. package/assets/.agent/skills/motion-graphics/categories/news/module.md +56 -56
  411. package/assets/.agent/skills/motion-graphics/categories/stat/module.md +21 -21
  412. package/assets/.agent/skills/motion-graphics/categories/tweet/module.md +16 -16
  413. package/assets/.agent/skills/motion-graphics/categories/webpage/module.md +21 -21
  414. package/assets/.agent/skills/motion-graphics/grounding/PROTOCOL.md +64 -64
  415. package/assets/.agent/skills/motion-graphics/grounding/locate.mjs +227 -227
  416. package/assets/.agent/skills/motion-graphics/phases/source/guide.md +21 -21
  417. package/assets/.agent/skills/motion-graphics/references/builder-contract.md +38 -38
  418. package/assets/.agent/skills/motion-graphics/references/motion-vocabulary.md +43 -43
  419. package/assets/.agent/skills/motion-graphics/references/shot-plan-ir.md +54 -54
  420. package/assets/.agent/skills/motion-graphics/samples/asset-fusion/_ref-circle-highlight.html +306 -306
  421. package/assets/.agent/skills/music-to-video/SKILL.md +197 -197
  422. package/assets/.agent/skills/music-to-video/references/frame-skeleton.md +100 -100
  423. package/assets/.agent/skills/music-to-video/references/montage.md +58 -58
  424. package/assets/.agent/skills/music-to-video/references/motion-primitive-catalog.md +75 -75
  425. package/assets/.agent/skills/music-to-video/references/motion-primitives/3d-card-flip/index.html +154 -154
  426. package/assets/.agent/skills/music-to-video/references/motion-primitives/assets/gsap.min.js +11 -11
  427. package/assets/.agent/skills/music-to-video/references/motion-primitives/bg-flow-field/index.html +326 -326
  428. package/assets/.agent/skills/music-to-video/references/motion-primitives/binary-decrypt/index.html +120 -120
  429. package/assets/.agent/skills/music-to-video/references/motion-primitives/blur-resolve/index.html +95 -95
  430. package/assets/.agent/skills/music-to-video/references/motion-primitives/braam-punch/index.html +135 -135
  431. package/assets/.agent/skills/music-to-video/references/motion-primitives/chromatic-split/index.html +146 -146
  432. package/assets/.agent/skills/music-to-video/references/motion-primitives/chrome-sweep/index.html +103 -103
  433. package/assets/.agent/skills/music-to-video/references/motion-primitives/counting-punch/index.html +154 -154
  434. package/assets/.agent/skills/music-to-video/references/motion-primitives/crash-zoom-in/index.html +155 -155
  435. package/assets/.agent/skills/music-to-video/references/motion-primitives/datamosh-smear/index.html +166 -166
  436. package/assets/.agent/skills/music-to-video/references/motion-primitives/directional-fill/index.html +133 -133
  437. package/assets/.agent/skills/music-to-video/references/motion-primitives/dolly-zoom/index.html +137 -137
  438. package/assets/.agent/skills/music-to-video/references/motion-primitives/electric-arc/index.html +144 -144
  439. package/assets/.agent/skills/music-to-video/references/motion-primitives/flash-cut/index.html +132 -132
  440. package/assets/.agent/skills/music-to-video/references/motion-primitives/gooey-metaball/index.html +241 -241
  441. package/assets/.agent/skills/music-to-video/references/motion-primitives/hard-cut/index.html +117 -117
  442. package/assets/.agent/skills/music-to-video/references/motion-primitives/hypercut-whip/index.html +79 -79
  443. package/assets/.agent/skills/music-to-video/references/motion-primitives/iris-open/index.html +104 -104
  444. package/assets/.agent/skills/music-to-video/references/motion-primitives/kinetic-letter-in/index.html +92 -92
  445. package/assets/.agent/skills/music-to-video/references/motion-primitives/liquid-morph/index.html +131 -131
  446. package/assets/.agent/skills/music-to-video/references/motion-primitives/mask-reveal/index.html +109 -109
  447. package/assets/.agent/skills/music-to-video/references/motion-primitives/mosaic-pack/index.html +124 -124
  448. package/assets/.agent/skills/music-to-video/references/motion-primitives/neon-flicker/index.html +108 -108
  449. package/assets/.agent/skills/music-to-video/references/motion-primitives/outline-to-fill/index.html +117 -117
  450. package/assets/.agent/skills/music-to-video/references/motion-primitives/palette-flip/index.html +132 -132
  451. package/assets/.agent/skills/music-to-video/references/motion-primitives/particle-burst/index.html +141 -141
  452. package/assets/.agent/skills/music-to-video/references/motion-primitives/pixel-dissolve/index.html +121 -121
  453. package/assets/.agent/skills/music-to-video/references/motion-primitives/radial-burst-lines/index.html +160 -160
  454. package/assets/.agent/skills/music-to-video/references/motion-primitives/screen-shake/index.html +113 -113
  455. package/assets/.agent/skills/music-to-video/references/motion-primitives/slot-machine-reveal/index.html +130 -130
  456. package/assets/.agent/skills/music-to-video/references/motion-primitives/spotlight-sweep/index.html +137 -137
  457. package/assets/.agent/skills/music-to-video/references/motion-primitives/staggered-exit/index.html +117 -117
  458. package/assets/.agent/skills/music-to-video/references/motion-primitives/text-spectral-rays/USAGE.md +42 -42
  459. package/assets/.agent/skills/music-to-video/references/motion-primitives/text-spectral-rays/index.html +313 -313
  460. package/assets/.agent/skills/music-to-video/references/motion-primitives/text-wave-distort/index.html +136 -136
  461. package/assets/.agent/skills/music-to-video/references/motion-primitives/tile-mosaic/index.html +144 -144
  462. package/assets/.agent/skills/music-to-video/references/motion-primitives/typewriter-reveal/index.html +122 -122
  463. package/assets/.agent/skills/music-to-video/references/motion-primitives/word-grid-burst/index.html +126 -126
  464. package/assets/.agent/skills/music-to-video/references/planning.md +104 -104
  465. package/assets/.agent/skills/music-to-video/references/storyboard-format.md +94 -94
  466. package/assets/.agent/skills/music-to-video/references/template-catalog.md +106 -106
  467. package/assets/.agent/skills/music-to-video/references/templates/card-flyby/index.html +527 -527
  468. package/assets/.agent/skills/music-to-video/references/templates/card-flyby/program.json +13 -13
  469. package/assets/.agent/skills/music-to-video/references/templates/held-message-living-field/index.html +485 -485
  470. package/assets/.agent/skills/music-to-video/references/templates/held-text-strobe-burst/index.html +302 -302
  471. package/assets/.agent/skills/music-to-video/references/templates/intro-kinetic-cascade/index.html +610 -610
  472. package/assets/.agent/skills/music-to-video/references/templates/intro-kinetic-cascade/program.json +41 -41
  473. package/assets/.agent/skills/music-to-video/references/templates/logo-split-lockup-pulse/index.html +596 -596
  474. package/assets/.agent/skills/music-to-video/references/templates/poster-tile-mosaic/index.html +589 -589
  475. package/assets/.agent/skills/music-to-video/references/templates/poster-tile-mosaic/program.json +25 -25
  476. package/assets/.agent/skills/music-to-video/references/templates/roll-flipbook-word-cycle/index.html +267 -267
  477. package/assets/.agent/skills/music-to-video/references/templates/split-anchor-word-slot/index.html +594 -594
  478. package/assets/.agent/skills/music-to-video/references/templates/split-anchor-word-slot/program.json +39 -39
  479. package/assets/.agent/skills/music-to-video/references/templates/typewriter-phrase-keyword-shuffle/index.html +238 -238
  480. package/assets/.agent/skills/music-to-video/scripts/analyze-beatgrid.py +531 -531
  481. package/assets/.agent/skills/music-to-video/scripts/assemble-index.mjs +218 -218
  482. package/assets/.agent/skills/music-to-video/scripts/lib/storyboard.mjs +249 -249
  483. package/assets/.agent/skills/music-to-video/scripts/stage-assets.mjs +58 -58
  484. package/assets/.agent/skills/music-to-video/scripts/validate-plan.mjs +161 -161
  485. package/assets/.agent/skills/music-to-video/sub-agents/frame-worker.md +75 -75
  486. package/assets/.agent/skills/pr-to-video/SKILL.md +240 -240
  487. package/assets/.agent/skills/pr-to-video/references/code-vocabulary.md +111 -111
  488. package/assets/.agent/skills/pr-to-video/references/cut-catalog.md +215 -215
  489. package/assets/.agent/skills/pr-to-video/references/motion-language.md +156 -156
  490. package/assets/.agent/skills/pr-to-video/references/story-design.md +211 -211
  491. package/assets/.agent/skills/pr-to-video/references/visual-design.md +164 -164
  492. package/assets/.agent/skills/pr-to-video/scripts/assemble-index.mjs +570 -570
  493. package/assets/.agent/skills/pr-to-video/scripts/audio.mjs +253 -253
  494. package/assets/.agent/skills/pr-to-video/scripts/build-frame.mjs +536 -536
  495. package/assets/.agent/skills/pr-to-video/scripts/captions.mjs +508 -508
  496. package/assets/.agent/skills/pr-to-video/scripts/fetch-people-avatars.mjs +157 -157
  497. package/assets/.agent/skills/pr-to-video/scripts/fetch-pr.mjs +164 -164
  498. package/assets/.agent/skills/pr-to-video/scripts/ingest.mjs +539 -539
  499. package/assets/.agent/skills/pr-to-video/scripts/lib/assets.mjs +55 -55
  500. package/assets/.agent/skills/pr-to-video/scripts/lib/dimensions.mjs +45 -45
  501. package/assets/.agent/skills/pr-to-video/scripts/lib/storyboard.mjs +249 -249
  502. package/assets/.agent/skills/pr-to-video/scripts/lib/tokens.mjs +204 -204
  503. package/assets/.agent/skills/pr-to-video/scripts/lib/transition-registry.mjs +38 -38
  504. package/assets/.agent/skills/pr-to-video/scripts/lib/transitions.json +71 -71
  505. package/assets/.agent/skills/pr-to-video/scripts/transitions.mjs +320 -320
  506. package/assets/.agent/skills/pr-to-video/sub-agents/frame-worker.md +95 -95
  507. package/assets/.agent/skills/product-launch-video/SKILL.md +208 -208
  508. package/assets/.agent/skills/product-launch-video/references/cut-catalog.md +220 -220
  509. package/assets/.agent/skills/product-launch-video/references/motion-language.md +156 -156
  510. package/assets/.agent/skills/product-launch-video/references/story-design.md +373 -373
  511. package/assets/.agent/skills/product-launch-video/references/visual-design.md +126 -126
  512. package/assets/.agent/skills/product-launch-video/scripts/assemble-index.mjs +570 -570
  513. package/assets/.agent/skills/product-launch-video/scripts/audio.mjs +253 -253
  514. package/assets/.agent/skills/product-launch-video/scripts/build-frame.mjs +536 -536
  515. package/assets/.agent/skills/product-launch-video/scripts/captions.mjs +508 -508
  516. package/assets/.agent/skills/product-launch-video/scripts/lib/assets.mjs +55 -55
  517. package/assets/.agent/skills/product-launch-video/scripts/lib/dimensions.mjs +45 -45
  518. package/assets/.agent/skills/product-launch-video/scripts/lib/storyboard.mjs +249 -249
  519. package/assets/.agent/skills/product-launch-video/scripts/lib/tokens.mjs +204 -204
  520. package/assets/.agent/skills/product-launch-video/scripts/lib/transition-registry.mjs +38 -38
  521. package/assets/.agent/skills/product-launch-video/scripts/lib/transitions.json +71 -71
  522. package/assets/.agent/skills/product-launch-video/scripts/stage-assets.mjs +39 -39
  523. package/assets/.agent/skills/product-launch-video/scripts/transitions.mjs +320 -320
  524. package/assets/.agent/skills/product-launch-video/sub-agents/frame-worker.md +75 -75
  525. package/assets/.agent/skills/remotion/SKILL.md +357 -38
  526. package/assets/.agent/skills/remotion/rules/3d.md +5 -5
  527. package/assets/.agent/skills/remotion/rules/assets/text-animations-word-highlight.tsx +1 -6
  528. package/assets/.agent/skills/remotion/rules/audio-visualization.md +198 -0
  529. package/assets/.agent/skills/remotion/rules/audio.md +1 -4
  530. package/assets/.agent/skills/remotion/rules/calculate-metadata.md +48 -18
  531. package/assets/.agent/skills/remotion/rules/compositions.md +16 -24
  532. package/assets/.agent/skills/remotion/rules/display-captions.md +79 -21
  533. package/assets/.agent/skills/remotion/rules/effects.md +235 -0
  534. package/assets/.agent/skills/remotion/rules/ffmpeg.md +34 -0
  535. package/assets/.agent/skills/remotion/rules/get-audio-duration.md +13 -13
  536. package/assets/.agent/skills/remotion/rules/get-video-dimensions.md +1 -1
  537. package/assets/.agent/skills/remotion/rules/get-video-duration.md +12 -10
  538. package/assets/.agent/skills/remotion/rules/gifs.md +21 -18
  539. package/assets/.agent/skills/remotion/rules/google-fonts.md +72 -0
  540. package/assets/.agent/skills/remotion/rules/html-in-canvas.md +122 -0
  541. package/assets/.agent/skills/remotion/rules/images.md +5 -64
  542. package/assets/.agent/skills/remotion/rules/import-srt-captions.md +9 -7
  543. package/assets/.agent/skills/remotion/rules/light-leaks.md +73 -0
  544. package/assets/.agent/skills/remotion/rules/local-fonts.md +65 -0
  545. package/assets/.agent/skills/remotion/rules/lottie.md +10 -8
  546. package/assets/.agent/skills/remotion/rules/maplibre.md +458 -0
  547. package/assets/.agent/skills/remotion/rules/measuring-dom-nodes.md +0 -1
  548. package/assets/.agent/skills/remotion/rules/measuring-text.md +2 -5
  549. package/assets/.agent/skills/remotion/rules/parameters.md +21 -10
  550. package/assets/.agent/skills/remotion/rules/sequencing.md +35 -9
  551. package/assets/.agent/skills/remotion/rules/sfx.md +51 -0
  552. package/assets/.agent/skills/remotion/rules/silence-detection.md +71 -0
  553. package/assets/.agent/skills/remotion/rules/subtitles.md +36 -0
  554. package/assets/.agent/skills/remotion/rules/tailwind.md +2 -2
  555. package/assets/.agent/skills/remotion/rules/timing.md +89 -106
  556. package/assets/.agent/skills/remotion/rules/transcribe-captions.md +58 -7
  557. package/assets/.agent/skills/remotion/rules/transitions.md +117 -42
  558. package/assets/.agent/skills/remotion/rules/transparent-videos.md +106 -0
  559. package/assets/.agent/skills/remotion/rules/trimming.md +1 -3
  560. package/assets/.agent/skills/remotion/rules/video-layout.md +68 -0
  561. package/assets/.agent/skills/remotion/rules/voiceover.md +99 -0
  562. package/assets/.agent/skills/remotion-real-ui-video/SKILL.md +264 -264
  563. package/assets/.agent/skills/remotion-real-ui-video/references/anti-patterns.md +114 -114
  564. package/assets/.agent/skills/remotion-real-ui-video/references/completion-checklist.md +75 -75
  565. package/assets/.agent/skills/remotion-real-ui-video/references/real-ui-bridge-example.md +93 -93
  566. package/assets/.agent/skills/remotion-to-hyperframes/SKILL.md +127 -127
  567. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/run.sh +249 -249
  568. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/README.md +41 -41
  569. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/expected.json +26 -26
  570. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/index.html +61 -61
  571. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/package.json +14 -14
  572. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/remotion.config.ts +13 -13
  573. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/Root.tsx +13 -13
  574. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/TitleCard.tsx +34 -34
  575. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/index.ts +4 -4
  576. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/tsconfig.json +15 -15
  577. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/README.md +54 -54
  578. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/expected.json +29 -29
  579. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/index.html +118 -118
  580. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/package.json +14 -14
  581. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/remotion.config.ts +13 -13
  582. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/src/MultiScene.tsx +105 -105
  583. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/src/Root.tsx +13 -13
  584. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/src/index.ts +4 -4
  585. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/tsconfig.json +15 -15
  586. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/setup.sh +34 -34
  587. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/README.md +85 -85
  588. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/expected.json +41 -41
  589. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/index.html +269 -269
  590. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/package.json +15 -15
  591. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/remotion.config.ts +13 -13
  592. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/Root.tsx +27 -27
  593. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/Stargazed.tsx +37 -37
  594. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/components/AnimatedNumber.tsx +23 -23
  595. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/components/StatCard.tsx +59 -59
  596. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/components/UnderlinedText.tsx +47 -47
  597. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/index.ts +4 -4
  598. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/scenes/OutroScene.tsx +18 -18
  599. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/scenes/StatsScene.tsx +34 -34
  600. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/scenes/TitleScene.tsx +55 -55
  601. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/tsconfig.json +15 -15
  602. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/README.md +51 -51
  603. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/01-use-state.tsx +29 -29
  604. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/02-use-effect-deps.tsx +29 -29
  605. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/03-async-metadata.tsx +39 -39
  606. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/04-third-party-react.tsx +30 -30
  607. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/05-lambda-config.tsx +38 -38
  608. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/06-warnings-only.tsx +36 -36
  609. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/07-custom-hook.tsx +28 -28
  610. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/08-mixed.tsx +41 -41
  611. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/expected.json +85 -85
  612. package/assets/.agent/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/validate.sh +110 -110
  613. package/assets/.agent/skills/remotion-to-hyperframes/references/api-map.md +142 -142
  614. package/assets/.agent/skills/remotion-to-hyperframes/references/escape-hatch.md +115 -115
  615. package/assets/.agent/skills/remotion-to-hyperframes/references/eval.md +140 -140
  616. package/assets/.agent/skills/remotion-to-hyperframes/references/fonts.md +112 -112
  617. package/assets/.agent/skills/remotion-to-hyperframes/references/limitations.md +136 -136
  618. package/assets/.agent/skills/remotion-to-hyperframes/references/lottie.md +121 -121
  619. package/assets/.agent/skills/remotion-to-hyperframes/references/media.md +149 -149
  620. package/assets/.agent/skills/remotion-to-hyperframes/references/parameters.md +167 -167
  621. package/assets/.agent/skills/remotion-to-hyperframes/references/sequencing.md +195 -195
  622. package/assets/.agent/skills/remotion-to-hyperframes/references/timing.md +165 -165
  623. package/assets/.agent/skills/remotion-to-hyperframes/references/transitions.md +114 -114
  624. package/assets/.agent/skills/remotion-to-hyperframes/scripts/frame_strip.sh +107 -107
  625. package/assets/.agent/skills/remotion-to-hyperframes/scripts/lint_source.py +358 -358
  626. package/assets/.agent/skills/remotion-to-hyperframes/scripts/render_diff.sh +103 -103
  627. package/assets/.agent/skills/remotion-to-hyperframes/scripts/tests/fixtures/blocker.tsx +50 -50
  628. package/assets/.agent/skills/remotion-to-hyperframes/scripts/tests/fixtures/clean.tsx +46 -46
  629. package/assets/.agent/skills/remotion-to-hyperframes/scripts/tests/smoke.sh +90 -90
  630. package/assets/.agent/skills/shared-resend-portfolio/SKILL.md +124 -124
  631. package/assets/.agent/skills/slideshow/SKILL.md +550 -550
  632. package/assets/.agent/skills/slideshow/references/standalone-harness.md +1020 -1020
  633. package/assets/.agent/skills/talking-head-recut/NOTICE.md +39 -39
  634. package/assets/.agent/skills/talking-head-recut/SKILL.md +1191 -1191
  635. package/assets/.agent/skills/talking-head-recut/assets/vendor/gsap.min.js +10 -10
  636. package/assets/.agent/skills/talking-head-recut/references/DESIGN_INDEX.md +145 -145
  637. package/assets/.agent/skills/talking-head-recut/references/frames/clean.html +92 -92
  638. package/assets/.agent/skills/talking-head-recut/references/frames/hairline.html +171 -171
  639. package/assets/.agent/skills/talking-head-recut/references/frames/polaroid.html +131 -131
  640. package/assets/.agent/skills/talking-head-recut/references/layouts/overlay.html +164 -164
  641. package/assets/.agent/skills/talking-head-recut/references/layouts/pip.html +143 -143
  642. package/assets/.agent/skills/talking-head-recut/references/layouts/split.html +121 -121
  643. package/assets/.agent/skills/talking-head-recut/references/layouts/stack.html +109 -109
  644. package/assets/.agent/skills/talking-head-recut/references/styles/academic.html +172 -172
  645. package/assets/.agent/skills/talking-head-recut/references/styles/audit.html +170 -170
  646. package/assets/.agent/skills/talking-head-recut/references/styles/editorial.html +189 -189
  647. package/assets/.agent/skills/talking-head-recut/references/styles/geom.html +181 -181
  648. package/assets/.agent/skills/talking-head-recut/references/styles/minimal.html +130 -130
  649. package/assets/.agent/skills/talking-head-recut/references/styles/spotlight.html +142 -142
  650. package/assets/.agent/skills/talking-head-recut/references/styles/swiss.html +188 -188
  651. package/assets/.agent/skills/talking-head-recut/references/styles/terminal.html +196 -196
  652. package/assets/.agent/skills/talking-head-recut/references/styles/whiteboard.html +170 -170
  653. package/assets/.agent/skills/talking-head-recut/references/styles/xhs.html +206 -206
  654. package/assets/.agent/skills/website-to-video/SKILL.md +145 -145
  655. package/assets/.agent/skills/website-to-video/assets/sfx/CREDITS.md +35 -35
  656. package/assets/.agent/skills/website-to-video/assets/sfx/manifest.json +97 -97
  657. package/assets/.agent/skills/website-to-video/references/beat-builder-guide.md +263 -263
  658. package/assets/.agent/skills/website-to-video/references/capabilities.md +713 -713
  659. package/assets/.agent/skills/website-to-video/references/step-0-capture.md +55 -55
  660. package/assets/.agent/skills/website-to-video/references/step-1-design.md +333 -333
  661. package/assets/.agent/skills/website-to-video/references/step-2-brief.md +178 -178
  662. package/assets/.agent/skills/website-to-video/references/step-3-storyboard.md +572 -572
  663. package/assets/.agent/skills/website-to-video/references/step-4-vo.md +230 -230
  664. package/assets/.agent/skills/website-to-video/references/step-5-build.md +483 -483
  665. package/assets/.agent/skills/website-to-video/references/step-6-validate.md +341 -341
  666. package/assets/.agent/skills/website-to-video/scripts/w2h-verify.mjs +759 -759
  667. package/package.json +6 -5
  668. package/src/pi-takomi-core/orchestration.ts +39 -21
  669. package/src/pi-takomi-core/routing.ts +8 -8
  670. package/src/pi-takomi-core/types.ts +25 -5
  671. package/src/pi-takomi-core/workflows.ts +86 -86
  672. package/src/takomi-stats.js +5 -5
  673. package/assets/.agent/skills/remotion/rules/animations.md +0 -29
  674. package/assets/.agent/skills/remotion/rules/assets.md +0 -78
  675. package/assets/.agent/skills/remotion/rules/can-decode.md +0 -75
  676. package/assets/.agent/skills/remotion/rules/charts.md +0 -58
  677. package/assets/.agent/skills/remotion/rules/extract-frames.md +0 -229
  678. package/assets/.agent/skills/remotion/rules/fonts.md +0 -152
  679. package/assets/.agent/skills/remotion/rules/maps.md +0 -403
@@ -1,1020 +1,1020 @@
1
- # Standalone HyperFrames Slideshow Harness
2
-
3
- ## 1. Interim framing — why this exists
4
-
5
- These patterns are a **temporary workaround** for standalone demos. The durable solution is engine-hosted: a future `hyperframes preview --slideshow` / studio present mode will host the composition over the real HyperFrames engine, which drives seek-timelines frame-by-frame, owns the gesture frame, and reads the slideshow island directly from the composition. When that path ships, most of what follows collapses.
6
-
7
- Until then, a standalone slideshow opened via the bare player bundle must work around three facts:
8
-
9
- 1. The composition must expose a seekable `window.__timelines.root` timeline. Anything outside that seek path, such as Three.js loops or imperative entrance effects, must be self-driving.
10
- 2. `<hyperframes-slideshow>` reads the slideshow island from its **own innerHTML** (the wrapper element), not from the composition the player loads. The island must be duplicated into the wrapper.
11
- 3. The composition runs in the player's **iframe**; user keypresses and pointer events land on the **parent page**. Wrapper-owned SFX/global audio should live in the parent, where the activation token is reliable. Normal slide media stays in the composition and is stopped by the slideshow player on slide exit.
12
-
13
- Do not treat these as the blessed authoring model. When the engine-hosted path ships, compositions authored the normal way will just work.
14
-
15
- **Living reference implementations:**
16
-
17
- - `registry/examples/airbnb-deck/index.html` + `demo.html` — full pattern set (Three.js, fragments, SFX, branch slide)
18
- - `registry/examples/startup-pitch/index.html` — minimal version (no 3D), good starting point
19
-
20
- ---
21
-
22
- ## 2. The parent wrapper (`index.html` for deliverables, `demo.html` in examples)
23
-
24
- The parent page hosts the two dist bundles, wraps the components, duplicates the island, and owns all audio.
25
-
26
- For public or user-facing generated projects, make this wrapper the root `index.html` so opening the project in a browser runs the slideshow. Put the raw HyperFrames composition in a separate path such as `composition/index.html`. In repo examples you may still see this file called `demo.html`; that name is a reference pattern, not the preferred handoff for a standalone deck.
27
-
28
- ```html
29
- <!doctype html>
30
- <html lang="en">
31
- <head>
32
- <meta charset="UTF-8" />
33
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
34
- <title>My Deck — Slideshow Demo</title>
35
-
36
- <!--
37
- Load both bundles from packages/player/dist.
38
- The global builds register <hyperframes-player> and <hyperframes-slideshow>
39
- as custom elements — no import map needed.
40
- -->
41
- <script src="../../../packages/player/dist/hyperframes-player.global.js"></script>
42
- <script src="../../../packages/player/dist/slideshow/hyperframes-slideshow.global.js"></script>
43
-
44
- <style>
45
- *,
46
- *::before,
47
- *::after {
48
- box-sizing: border-box;
49
- margin: 0;
50
- padding: 0;
51
- }
52
- html,
53
- body {
54
- width: 100%;
55
- height: 100%;
56
- overflow: hidden;
57
- background: #111;
58
- }
59
- </style>
60
- </head>
61
- <body>
62
- <!--
63
- tabindex="0" is critical — <hyperframes-slideshow> binds keydown
64
- (ArrowLeft/Right, Space, Backspace) to itself. Without tabindex the
65
- element cannot receive focus and arrow keys are dead.
66
- -->
67
- <hyperframes-slideshow
68
- tabindex="0"
69
- style="display: block; position: relative; width: 100vw; height: 100vh"
70
- >
71
- <hyperframes-player
72
- interactive
73
- style="position: absolute; inset: 0"
74
- src="composition/index.html"
75
- ></hyperframes-player>
76
-
77
- <!--
78
- DUPLICATED ISLAND — keep in sync with the island inside index.html.
79
- <hyperframes-slideshow> reads from its own innerHTML, not from the
80
- composition the player loads. Every time slides/fragments/hotspots/
81
- sequences change in index.html, update this copy too.
82
- -->
83
- <script type="application/hyperframes-slideshow+json">
84
- {
85
- "slides": [
86
- { "sceneId": "scene-one", "notes": "..." },
87
- {
88
- "sceneId": "scene-two",
89
- "notes": "...",
90
- "fragments": [8.3, 8.6]
91
- }
92
- ],
93
- "slideSequences": [
94
- {
95
- "id": "branch-one",
96
- "label": "Branch label",
97
- "slides": [{ "sceneId": "branch-scene", "notes": "..." }]
98
- }
99
- ]
100
- }
101
- </script>
102
- </hyperframes-slideshow>
103
- <!-- The built-in slideshow nav capsule renders Present; do not add a wrapper-level button. -->
104
-
105
- <!-- Audio player lives here — see Section 6 -->
106
- </body>
107
- </html>
108
- ```
109
-
110
- `interactive` is required for decks with clickable page content or media controls. Without it, iframe pointer events are disabled by the player shell and a click on the composition can be interpreted as a player play/pause toggle instead of a slide interaction.
111
-
112
- ### Per-slide `autoplay`
113
-
114
- Add `"autoplay": true` to a slide in the island to play that slide's first `<video>` from the start when the presenter lands on it. The slideshow still holds — it never auto-advances — so the presenter clicks Next when ready; autoplay only saves a manual play click into the composition.
115
-
116
- ```json
117
- {
118
- "sceneId": "cold-open",
119
- "autoplay": true,
120
- "notes": "Promo plays on enter; click Next when it ends."
121
- }
122
- ```
123
-
124
- Use `autoplay` when the video **is** the slide's primary content and its natural end is the cue to advance — a cold-open promo, a demo clip you let run and then move on from. Do **not** use it for background/ambient loops or for footage the presenter talks over; those should start on the presenter's own cue (a click on the clip's controls via `interactive`), not automatically. One autoplay clip per slide (the first `<video>` in the scene).
125
-
126
- ### Presenter media bridge for interactive media
127
-
128
- Presenter/audience mode syncs slide position through a deck-scoped `BroadcastChannel`. If the presenter is expected to play, pause, seek, mute, or change rate on media inside the composition, mirror those native media events over the same channel. Keep the media element as the source of truth; do not mirror a custom button's private state.
129
-
130
- Audible playback has one extra browser constraint: a `BroadcastChannel` message does not carry the presenter's user activation into the audience window. The audience window should try presenter-driven playback muted first, because browsers usually allow muted autoplay; it may still reject `media.play()` even while it accepts remote `currentTime` updates. If that happens, do not keep chasing presenter `timeupdate` messages; show an audience-side unlock control, store the latest play intent, and retry playback from that intent after the audience window receives a click/key gesture.
131
-
132
- ```js
133
- (function () {
134
- if (typeof BroadcastChannel === "undefined") return;
135
-
136
- var sender =
137
- new URLSearchParams(location.search).get("mode") === "audience" ? "audience" : "presenter";
138
- var channel = new BroadcastChannel("hf-slideshow:" + location.pathname);
139
- var applyingRemote = false;
140
- var lastTimeBroadcast = 0;
141
- var pendingPlayByKey = {};
142
- var blockedPlayByKey = {};
143
- var mutedPlaybackByKey = {};
144
- var unlockButton = null;
145
-
146
- function frameDocument() {
147
- var player = document.querySelector("hyperframes-player");
148
- var frame = player && player.iframeElement;
149
- try {
150
- return frame && frame.contentDocument ? frame.contentDocument : null;
151
- } catch {
152
- return null;
153
- }
154
- }
155
- function mediaNodes() {
156
- var doc = frameDocument();
157
- return doc ? Array.from(doc.querySelectorAll("video,audio")) : [];
158
- }
159
- function mediaKey(el, index) {
160
- return el.id ? "id:" + el.id : el.tagName.toLowerCase() + ":" + index;
161
- }
162
- function findMedia(key) {
163
- return mediaNodes().find(function (el, index) {
164
- return mediaKey(el, index) === key;
165
- });
166
- }
167
- function syncMediaState(el, msg, allowTimeSync) {
168
- if (typeof msg.playbackRate === "number") el.playbackRate = msg.playbackRate;
169
- if (typeof msg.volume === "number") el.volume = Math.max(0, Math.min(1, msg.volume));
170
- if (sender === "audience" && mutedPlaybackByKey[msg.key]) {
171
- el.muted = true;
172
- } else if (typeof msg.muted === "boolean") {
173
- el.muted = msg.muted;
174
- }
175
- if (
176
- allowTimeSync &&
177
- typeof msg.currentTime === "number" &&
178
- Math.abs((el.currentTime || 0) - msg.currentTime) > 0.35
179
- ) {
180
- el.currentTime = Math.max(0, msg.currentTime);
181
- }
182
- }
183
- function hasBlockedPlay() {
184
- return Object.keys(blockedPlayByKey).length > 0;
185
- }
186
- function hideAudienceUnlockIfClear() {
187
- if (hasBlockedPlay() || !unlockButton) return;
188
- unlockButton.remove();
189
- unlockButton = null;
190
- }
191
- function showAudienceUnlock() {
192
- if (sender !== "audience" || unlockButton) return;
193
- unlockButton = document.createElement("button");
194
- unlockButton.type = "button";
195
- unlockButton.textContent = "Enable audience media";
196
- unlockButton.style.cssText =
197
- "position:fixed;left:50%;bottom:96px;transform:translateX(-50%);z-index:100000;border:0;border-radius:999px;padding:12px 18px;background:#fff;color:#111827;box-shadow:0 10px 32px rgba(0,0,0,.28);font:700 14px/1 system-ui,sans-serif;cursor:pointer;";
198
- unlockButton.addEventListener("click", retryBlockedPlays);
199
- document.body.appendChild(unlockButton);
200
- }
201
- function rememberBlockedPlay(msg) {
202
- pendingPlayByKey[msg.key] = msg;
203
- blockedPlayByKey[msg.key] = true;
204
- showAudienceUnlock();
205
- }
206
- function clearBlockedPlay(key) {
207
- delete blockedPlayByKey[key];
208
- hideAudienceUnlockIfClear();
209
- }
210
- function tryPlay(el, msg) {
211
- if (sender === "audience") mutedPlaybackByKey[msg.key] = true;
212
- syncMediaState(el, msg, true);
213
- if (sender === "audience") el.muted = true;
214
- try {
215
- var playResult = el.play();
216
- if (playResult && typeof playResult.then === "function") {
217
- playResult
218
- .then(function () {
219
- clearBlockedPlay(msg.key);
220
- })
221
- .catch(function () {
222
- rememberBlockedPlay(msg);
223
- });
224
- } else {
225
- clearBlockedPlay(msg.key);
226
- }
227
- } catch (e) {
228
- rememberBlockedPlay(msg);
229
- }
230
- }
231
- function retryBlockedPlays() {
232
- wireMedia();
233
- applyingRemote = true;
234
- try {
235
- Object.keys(pendingPlayByKey).forEach(function (key) {
236
- var msg = pendingPlayByKey[key];
237
- var el = findMedia(key);
238
- if (el && msg) tryPlay(el, msg);
239
- });
240
- } finally {
241
- setTimeout(function () {
242
- applyingRemote = false;
243
- }, 300);
244
- }
245
- }
246
- function publish(el, index, action) {
247
- if (sender !== "presenter") return;
248
- if (applyingRemote) return;
249
- if (action === "timeupdate") {
250
- var now = performance.now();
251
- if (now - lastTimeBroadcast < 450 && !el.paused) return;
252
- lastTimeBroadcast = now;
253
- }
254
- channel.postMessage({
255
- type: "media",
256
- sender,
257
- key: mediaKey(el, index),
258
- action,
259
- currentTime: el.currentTime || 0,
260
- paused: el.paused,
261
- ended: el.ended,
262
- muted: el.muted,
263
- volume: el.volume,
264
- playbackRate: el.playbackRate,
265
- });
266
- }
267
- function wireMedia() {
268
- mediaNodes().forEach(function (el, index) {
269
- if (el.dataset.hfPresenterMediaSync === "1") return;
270
- el.dataset.hfPresenterMediaSync = "1";
271
- [
272
- "play",
273
- "pause",
274
- "seeking",
275
- "seeked",
276
- "ratechange",
277
- "volumechange",
278
- "ended",
279
- "timeupdate",
280
- ].forEach(function (name) {
281
- el.addEventListener(name, function () {
282
- publish(el, index, name);
283
- });
284
- });
285
- });
286
- }
287
- channel.addEventListener("message", function (event) {
288
- var msg = event.data;
289
- if (!msg || msg.type !== "media" || msg.sender === sender) return;
290
- if (sender === "audience" && blockedPlayByKey[msg.key] && msg.action === "timeupdate") {
291
- pendingPlayByKey[msg.key] = msg;
292
- showAudienceUnlock();
293
- return;
294
- }
295
- var el = findMedia(msg.key);
296
- if (!el) return;
297
- applyingRemote = true;
298
- try {
299
- if (
300
- msg.action === "play" ||
301
- (sender === "audience" && msg.action === "timeupdate" && msg.paused === false && el.paused)
302
- ) {
303
- pendingPlayByKey[msg.key] = msg;
304
- tryPlay(el, msg);
305
- } else {
306
- syncMediaState(el, msg, true);
307
- }
308
- if (msg.action === "pause" || msg.action === "ended") {
309
- delete pendingPlayByKey[msg.key];
310
- delete mutedPlaybackByKey[msg.key];
311
- clearBlockedPlay(msg.key);
312
- el.pause();
313
- }
314
- } catch {
315
- } finally {
316
- setTimeout(function () {
317
- applyingRemote = false;
318
- }, 300);
319
- }
320
- });
321
- wireMedia();
322
- window.addEventListener("load", wireMedia);
323
- window.addEventListener("keydown", retryBlockedPlays, true);
324
- window.addEventListener("pointerdown", retryBlockedPlays, true);
325
- setInterval(wireMedia, 1000);
326
- })();
327
- ```
328
-
329
- ### Custom media visualizers
330
-
331
- For waveform, beat-grid, canvas, or timeline players, wire visual state to the native media element. This keeps native controls, custom controls, presenter sync, slide-exit cleanup, and global mute in one event path.
332
-
333
- ```js
334
- function wireMediaDrivenVisualizer(media, renderFrame, fireCrossedEvents) {
335
- var mediaFrame = 0;
336
- var lastTime = media.currentTime || 0;
337
-
338
- function update() {
339
- var time = media.currentTime || 0;
340
- if (Math.abs(time - lastTime) < 1.5 && time >= lastTime) {
341
- fireCrossedEvents(lastTime, time);
342
- }
343
- lastTime = time;
344
- renderFrame(time, media);
345
- }
346
-
347
- function start() {
348
- if (!media.requestVideoFrameCallback || mediaFrame) return;
349
- mediaFrame = media.requestVideoFrameCallback(function () {
350
- mediaFrame = 0;
351
- update();
352
- if (!media.paused && !media.ended) start();
353
- });
354
- }
355
-
356
- media.addEventListener("play", start);
357
- media.addEventListener("playing", start);
358
- media.addEventListener("pause", update);
359
- media.addEventListener("ended", update);
360
- media.addEventListener("timeupdate", update);
361
- media.addEventListener("seeking", function () {
362
- lastTime = media.currentTime || 0;
363
- renderFrame(lastTime, media);
364
- });
365
- media.addEventListener("seeked", update);
366
- media.addEventListener("ratechange", update);
367
- media.addEventListener("volumechange", update);
368
- renderFrame(media.currentTime || 0, media);
369
- }
370
- ```
371
-
372
- Do not use `requestAnimationFrame` inside compositions for media sync; composition lint rejects wall-clock loops. Prefer `HTMLVideoElement.requestVideoFrameCallback()` for smooth video-tied updates and rely on native `timeupdate`/seek events as the fallback.
373
-
374
- Use a dedicated wiring marker such as `data-media-sync-wired`. Do not reuse a marker like `data-wired` for both "timeline DOM already rendered" and "media event listeners attached"; pre-rendered timeline HTML will otherwise skip listener setup.
375
-
376
- ### Editable presenter notes
377
-
378
- The shared `<hyperframes-slideshow>` presenter already renders speaker notes as an editable textarea and stores edits in `localStorage`. Do not add deck-specific note editors when the shared player is available.
379
-
380
- For interim custom wrappers that cannot use the shared presenter chrome, use this deterministic storage contract exactly so notes migrate cleanly:
381
-
382
- ```js
383
- const NOTES_STORAGE_PREFIX = "hf-slideshow:presenter-notes:v1:";
384
-
385
- function notesDeckKey(slideshowEl) {
386
- const explicit = slideshowEl.getAttribute("notes-storage-key");
387
- if (explicit && explicit.trim()) return explicit.trim();
388
-
389
- const playerSrc = slideshowEl.querySelector("hyperframes-player")?.getAttribute("src") || "";
390
- let resolvedPlayerSrc = playerSrc;
391
- try {
392
- resolvedPlayerSrc = new URL(playerSrc, location.href).href;
393
- } catch {}
394
-
395
- return `${location.origin}${location.pathname}|${document.title}|${resolvedPlayerSrc}`;
396
- }
397
-
398
- function notesStorageKey(slideshowEl, position, slide) {
399
- return `${NOTES_STORAGE_PREFIX}${JSON.stringify([
400
- notesDeckKey(slideshowEl),
401
- position.sequenceId,
402
- position.slideIndex,
403
- slide.sceneId || "",
404
- ])}`;
405
- }
406
-
407
- function readPresenterNotes(slideshowEl, position, slide) {
408
- const key = notesStorageKey(slideshowEl, position, slide);
409
- try {
410
- const stored = localStorage.getItem(key);
411
- return stored == null ? slide.notes || "" : stored;
412
- } catch {
413
- return slide.notes || "";
414
- }
415
- }
416
-
417
- function wirePresenterNotes(textarea, slideshowEl, position, slide) {
418
- const key = notesStorageKey(slideshowEl, position, slide);
419
- textarea.value = readPresenterNotes(slideshowEl, position, slide);
420
- textarea.addEventListener("input", function () {
421
- try {
422
- localStorage.setItem(key, textarea.value);
423
- } catch {}
424
- });
425
- }
426
- ```
427
-
428
- Clearing the textarea must save an empty string, not remove the local value, because a presenter may intentionally blank a manifest note for their run. Use `notes-storage-key="stable-deck-id"` on `<hyperframes-slideshow>` when a standalone demo has a stable project id; otherwise the fallback key isolates by page, title, and player `src`.
429
-
430
- ---
431
-
432
- ## 3. Playhead-driven scene visibility
433
-
434
- Without the engine, scenes are driven by a `root` GSAP timeline that the composition manages on its own clock. The visibility controller reads `window.__timelines.root.time()` via that timeline's `onUpdate` callback and sets `opacity` accordingly. Only the active scene is visible.
435
-
436
- The key insight: scene backgrounds must be `transparent` (not opaque) if you want a Three.js canvas behind them; the body/html background and scene inline `background` set the visual fill.
437
-
438
- For converted source pages, preserve the original page's visual design, motion language, interactive behavior, media behavior, and presentation affordances as closely as practical. Port source-specific widgets exactly where practical: custom canvas players, waveform/timeline decorations, expanding rings, playheads, hover states, and event wiring are source material, not optional polish. Also audit for atypical page movement: scroll-scrubbed cameras, parallax, pinned sections, horizontal scrollers, section snapping, translated/scaled world layers, or zoom-to-element navigation. Scroll is often the source's transition trigger, so extract the scroll-progress stops, easing, and camera/focus states, then re-host that motion on slideshow navigation through timeline positions, fragments, or reusable harness hooks. Do not recreate the browser's literal page-scroll-down motion inside a slide; translate it into camera travel/zoom from one focus area to the next. If the same mechanical behavior appears across decks, move it into the player or this harness instead of copying a fragile one-off script.
439
-
440
- ### Navigation camera transitions for converted pages
441
-
442
- When a source page uses scroll to move a translated/scaled world, slideshow navigation usually seeks directly to each slide's hold frame. That seek bypasses any in-timeline interpolation near the scene boundary, so a deck can compute the right camera positions and still appear to jump. Add an explicit standalone navigation transition for manual slide changes, while keeping normal HyperFrames timeline seeks static and deterministic.
443
-
444
- Use this pattern only for direct-open/presenter slideshow UI. Do not depend on CSS transitions for rendered video output; rendered compositions must still be correct when seeking a single frame.
445
-
446
- ```css
447
- #world {
448
- transform-origin: 0 0;
449
- will-change: transform;
450
- transition:
451
- transform 760ms cubic-bezier(0.22, 1, 0.36, 1),
452
- opacity 0.3s ease;
453
- }
454
-
455
- #world.hf-camera-static {
456
- transition: opacity 0.3s ease;
457
- }
458
- ```
459
-
460
- ```js
461
- var currentCamera = null;
462
- var currentSlideIndex = null;
463
-
464
- function cameraTransform(cam) {
465
- return "translate(" + cam.tx + "px," + cam.ty + "px) scale(" + cam.s + ")";
466
- }
467
-
468
- function setWorldCamera(world, cam, animate) {
469
- if (!cam) return;
470
- if (!animate) world.classList.add("hf-camera-static");
471
- world.style.transform = cameraTransform(cam);
472
- world.style.opacity = "1";
473
- if (!animate) {
474
- world.getBoundingClientRect();
475
- world.classList.remove("hf-camera-static");
476
- }
477
- currentCamera = cam;
478
- }
479
-
480
- function slideIndexAtTime(t, slideDuration, slideCount) {
481
- return Math.max(0, Math.min(slideCount - 1, Math.floor(t / slideDuration)));
482
- }
483
-
484
- function updateCameraForTime(t, opts) {
485
- var nextSlideIndex = slideIndexAtTime(t, SLIDE_DURATION, SLIDES.length);
486
- var jumpedBetweenSlides =
487
- currentSlideIndex !== null &&
488
- nextSlideIndex !== currentSlideIndex &&
489
- Math.abs(t - lastTime) > 1.2;
490
- var animateCamera = Boolean(
491
- window.__hfCameraTransitionsEnabled && jumpedBetweenSlides && !(opts && opts.staticCamera),
492
- );
493
- var cam = cameraAtTime(t);
494
- setWorldCamera(world, cam, animateCamera);
495
- currentSlideIndex = nextSlideIndex;
496
- }
497
- ```
498
-
499
- During measurement, temporarily remove the transform with `hf-camera-static`, compute all element union rects, then restore `currentCamera` without animation. On initial load, resize, and validation-style seeks, call `updateCameraForTime(t, { staticCamera: true })`. In the standalone wrapper, set `iframe.contentWindow.__hfCameraTransitionsEnabled = true` after the player iframe is available. That keeps the exported composition seekable while letting presenter navigation glide between focal points.
500
-
501
- Before validation, resolve source font variables. HyperFrames lint accepts concrete generic stacks such as `system-ui, sans-serif` and `ui-monospace, monospace`, or real `@font-face` declarations pointing at local font files. It does not accept `font-family: var(--f-body)` / `var(--f-mono)` as a render-safe family.
502
-
503
- ```html
504
- <!-- In index.html (composition) -->
505
-
506
- <!-- Shared scene CSS — all scenes start hidden -->
507
- <style>
508
- .scene-frame {
509
- position: absolute;
510
- top: 0;
511
- left: 0;
512
- width: 1920px;
513
- height: 1080px;
514
- overflow: hidden;
515
- opacity: 0; /* hidden at rest — visibility controller shows the active one */
516
- visibility: hidden; /* opacity:0 alone still lets invisible frames block clicks */
517
- pointer-events: none; /* inactive scenes must not swallow events */
518
- }
519
- </style>
520
-
521
- <!--
522
- content-visible-at-rest: mark the first scene's elements with their
523
- final non-hidden state so the deck is not blank before the controller
524
- fires. The controller calls updateVisibility(0) synchronously on load.
525
-
526
- If using Three.js canvas behind scenes, set background: transparent
527
- here and let the 3D canvas + body color supply the fill.
528
- -->
529
- <div
530
- id="scene-cover"
531
- class="scene-frame clip"
532
- data-composition-id="cover"
533
- data-start="0"
534
- data-duration="9"
535
- style="background: transparent"
536
- >
537
- <!-- content here -->
538
- </div>
539
-
540
- <div
541
- id="scene-problem"
542
- class="scene-frame clip"
543
- data-composition-id="problem"
544
- data-start="9"
545
- data-duration="9"
546
- style="background: transparent"
547
- >
548
- <!-- content here -->
549
- </div>
550
-
551
- <!-- Root timeline — spans the full composition duration -->
552
- <script>
553
- (function () {
554
- window.__timelines = window.__timelines || {};
555
- var tl = gsap.timeline({ paused: true });
556
- // A single to() for the full duration establishes the seekable range
557
- tl.to({}, { duration: 108 }); // replace 108 with your total seconds
558
- window.__timelines["root"] = tl;
559
- })();
560
- </script>
561
-
562
- <!-- Visibility controller -->
563
- <script>
564
- (function () {
565
- var scenes = [
566
- { id: "scene-cover", start: 0, end: 9 },
567
- { id: "scene-problem", start: 9, end: 18 },
568
- // ... all scenes including branch scenes
569
- ];
570
-
571
- var lastActiveId = null;
572
-
573
- function updateVisibility(t) {
574
- for (var i = 0; i < scenes.length; i++) {
575
- var s = scenes[i];
576
- var el = document.getElementById(s.id);
577
- if (!el) continue;
578
- var active = t >= s.start && t < s.end;
579
- el.style.opacity = active ? "1" : "0";
580
- el.style.visibility = active ? "visible" : "hidden";
581
- el.style.pointerEvents = active ? "auto" : "none";
582
-
583
- if (active && lastActiveId !== s.id) {
584
- lastActiveId = s.id;
585
- fireEntrance(el); // see Section 4
586
- }
587
- }
588
- // fragment reveals here — see Section 4
589
- }
590
-
591
- window.__hfSetTime = updateVisibility;
592
-
593
- // Show first slide immediately — avoids blank on load
594
- updateVisibility(0);
595
-
596
- // Hook the root timeline so every seek drives visibility
597
- var root = window.__timelines && window.__timelines["root"];
598
- if (root) {
599
- root.eventCallback("onUpdate", function () {
600
- updateVisibility(root.time());
601
- });
602
- }
603
- })();
604
- </script>
605
- ```
606
-
607
- ---
608
-
609
- ## 4. Imperative entrances on slide-activate
610
-
611
- The engine-hosted path drives GSAP seek-timelines frame by frame. Without it, seek-timeline tweens never fire. Instead, fire imperative `gsap.from()` calls each time a scene becomes active — these run on GSAP's own ticker and are independent of any playhead.
612
-
613
- Fragment reveals use playhead-crossing: the visibility controller checks whether the playhead has passed each fragment's hold-time and fires an animation on the first crossing. Bunch fragment hold-times near the scene start (within the first 300–500 ms of the scene) so successive ArrowRight presses feel like snappy sequential reveals rather than long waits.
614
-
615
- ```js
616
- // --- Entrance animations ---
617
-
618
- function fireEntrance(sceneEl) {
619
- // [data-anim] marks elements that should entrance on slide-activate.
620
- // Add data-anim to eyebrows, headlines, subheads, and card grids.
621
- var animEls = sceneEl.querySelectorAll("[data-anim]");
622
- if (!animEls.length) return;
623
- gsap.from(animEls, {
624
- opacity: 0,
625
- y: 28,
626
- duration: 0.4,
627
- stagger: 0.07,
628
- ease: "power2.out",
629
- overwrite: true, // cancel any in-flight animation on rapid slide changes
630
- });
631
- }
632
-
633
- // --- Fragment reveals ---
634
-
635
- // Fragment config: times in absolute composition timeline seconds,
636
- // bunched near the scene start for snappy successive reveals.
637
- var fragments = [
638
- { time: 9.3, id: "prob-item1", revealed: false },
639
- { time: 9.6, id: "prob-item2", revealed: false },
640
- ];
641
-
642
- function revealFragment(id) {
643
- var el = document.getElementById(id);
644
- if (!el) return;
645
- gsap.fromTo(
646
- el,
647
- { opacity: 0, x: -24 },
648
- { opacity: 1, x: 0, duration: 0.35, ease: "power2.out", overwrite: true },
649
- );
650
- }
651
-
652
- // Inside updateVisibility(t):
653
- for (var f = 0; f < fragments.length; f++) {
654
- if (!fragments[f].revealed && t >= fragments[f].time) {
655
- fragments[f].revealed = true;
656
- revealFragment(fragments[f].id);
657
- }
658
- }
659
-
660
- // On problem scene re-entry, reset all fragment states:
661
- if (active && lastActiveId !== s.id && s.id === "scene-problem") {
662
- for (var f = 0; f < fragments.length; f++) {
663
- fragments[f].revealed = false;
664
- var pEl = document.getElementById(fragments[f].id);
665
- if (pEl) gsap.set(pEl, { opacity: 0, clearProps: "transform" });
666
- }
667
- }
668
- ```
669
-
670
- Fragment items start with `opacity: 0` in CSS. The visibility controller reveals them; the entrance driver does not touch them until crossing.
671
-
672
- ---
673
-
674
- ## 5. The scenes bootstrap postMessage
675
-
676
- `<hyperframes-slideshow>` must know each scene's time range to map a `sceneId` to a playhead position. Without the engine injecting this at runtime, the composition must post it manually after load.
677
-
678
- Post the manifest from the composition (index.html), not the parent wrapper:
679
-
680
- ```js
681
- // In index.html — post after a brief delay so the parent frame has settled
682
- (function () {
683
- var FPS = 30;
684
- var totalSeconds = 108; // match your composition's data-duration
685
- var totalFrames = totalSeconds * FPS;
686
-
687
- var scenes = [
688
- // EVERY scene — including branch scenes — must appear here.
689
- // id must match data-composition-id; start/duration in seconds.
690
- { id: "cover", start: 0, duration: 9 },
691
- { id: "problem", start: 9, duration: 9 },
692
- { id: "solution", start: 18, duration: 9 },
693
- // ... all main-line scenes ...
694
- // branch scene — listed last, NOT in main slides array in the island
695
- { id: "market-sizing", start: 99, duration: 9 },
696
- ];
697
-
698
- function postTimeline() {
699
- parent.postMessage(
700
- {
701
- source: "hf-preview",
702
- type: "timeline",
703
- durationInFrames: totalFrames,
704
- scenes: scenes,
705
- },
706
- "*",
707
- );
708
- }
709
-
710
- // ~300ms delay after load to let the parent settle
711
- if (document.readyState === "complete") {
712
- setTimeout(postTimeline, 300);
713
- } else {
714
- window.addEventListener("load", function () {
715
- setTimeout(postTimeline, 300);
716
- });
717
- }
718
- })();
719
- ```
720
-
721
- Omitting any scene (including branch scenes) from this manifest means the slideshow component cannot seek to it. Include every scene declared in the HTML, even scenes only reachable via a hotspot.
722
-
723
- ---
724
-
725
- ## 6. Audio/SFX — built-in mute control via `<hyperframes-slideshow sound>`
726
-
727
- Wrapper-owned SFX should live in the parent page. Browsers enforce user-activation for AudioContext and HTMLAudioElement.play() — an iframe without its own activation (i.e., the user never clicked inside it) is often autoplay-blocked. The user's keypress lands on the parent, so the parent is the reliable frame for click/transition sound effects.
728
-
729
- Normal slide media should stay in the composition. The slideshow player now stops slide media automatically on slide/sequence changes by calling `hyperframes-player.stopMedia()`, which pauses iframe `<video>` / `<audio>`, runtime WebAudio, and parent proxies adopted from iframe media. Same-slide fragment reveals do not stop media, and global/deck-level parent audio such as `audio-src` is left alone. Do not hand-roll per-slide cleanup scripts for regular video/audio players.
730
-
731
- Every copied `<video>` / `<audio>` with a `src` must be timed for HyperFrames ownership:
732
-
733
- ```html
734
- <video
735
- src="assets/demo.mp4"
736
- controls
737
- playsinline
738
- preload="metadata"
739
- data-start="0"
740
- data-duration="96"
741
- data-has-audio="true"
742
- ></video>
743
- ```
744
-
745
- Use `data-has-audio="true"` only for audible media. Muted autoplay loops can omit it. Do not leave `preload="none"` in converted compositions.
746
-
747
- Implementation detail: iframe media elements belong to the iframe's DOM realm. Fallback code in the parent page/player must not use the parent page's `el instanceof HTMLMediaElement` check for iframe nodes; in real browsers that fails and leaves videos audible. Use `el.ownerDocument.defaultView.HTMLMediaElement` or a tag/duck-type guard before setting `muted` or calling `pause()`.
748
-
749
- ### Mute toggle — built-in chrome control
750
-
751
- Add the `sound` boolean attribute to `<hyperframes-slideshow>` in demo.html. The component renders a speaker/speaker-muted SVG button as the **leftmost item in the nav capsule**, styled identically to the prev/next ghost buttons. No separate mute button in the composition.
752
-
753
- ```html
754
- <hyperframes-slideshow tabindex="0" sound style="..."> ... </hyperframes-slideshow>
755
- ```
756
-
757
- The component:
758
-
759
- - Tracks `muted` state (default `false`); exposes a `muted` getter
760
- - Reflects to a `data-hf-muted` attribute on the host when muted
761
- - Applies mute globally to child `<hyperframes-player>` media and top-level page `<audio>` / `<video>` elements
762
- - Dispatches `CustomEvent("hf-sound", { detail: { muted }, bubbles: true, composed: true })` on every toggle
763
- - Browser-checks the actual iframe media state after changes; every composition `<video>` / `<audio>` should report `muted: true` after clicking the nav mute button
764
-
765
- Wrapper-owned `new Audio(...)` objects are not attached to the DOM, so the parent audio player must mirror the `hf-sound` event onto each clip:
766
-
767
- ```js
768
- var muted = false;
769
- var slideshow = document.querySelector("hyperframes-slideshow");
770
- if (slideshow) {
771
- slideshow.addEventListener("hf-sound", function (e) {
772
- muted = e.detail && e.detail.muted === true;
773
- Object.keys(clips).forEach(function (name) {
774
- clips[name].muted = muted;
775
- });
776
- });
777
- }
778
- // In message handler:
779
- if (muted) return; // skip play
780
- ```
781
-
782
- If `sound` is **not** present on `<hyperframes-slideshow>` (decks without audio), the mute control is hidden — the capsule shows only nav.
783
-
784
- ### Composition: post cues unconditionally
785
-
786
- The composition posts sfx cues **unconditionally** — it does not track mute state. The parent gates on `muted`:
787
-
788
- **In the composition (index.html):**
789
-
790
- ```js
791
- // Post an sfx cue at transition points — unconditionally.
792
- // The parent audio player gates on the slideshow component's mute state.
793
- function playSfx(name) {
794
- try {
795
- parent.postMessage({ type: "hf-sfx", name: name }, "*");
796
- } catch (e) {}
797
- }
798
-
799
- // Fire at scene transitions:
800
- // playSfx("advance") — moving to the next main-line slide
801
- // playSfx("back") — returning from a branch
802
- // playSfx("branch-enter") — entering a branch
803
- // playSfx("fragment") — a fragment item is revealed
804
- ```
805
-
806
- Do NOT add a mute button inside the composition. The `#sfx-mute` coral button pattern is removed; the nav capsule in the parent chrome owns mute.
807
-
808
- **In the parent (demo.html):**
809
-
810
- ```html
811
- <script>
812
- (function () {
813
- // Audio elements are preloaded here, in the frame that receives user gestures.
814
- var clips = {
815
- advance: new Audio("sfx/advance.mp3"),
816
- fragment: new Audio("sfx/fragment.mp3"),
817
- "branch-enter": new Audio("sfx/branch-enter.mp3"),
818
- back: new Audio("sfx/back.mp3"),
819
- };
820
- clips.advance.volume = 0.45;
821
- clips.fragment.volume = 0.4;
822
- clips["branch-enter"].volume = 0.4;
823
- clips.back.volume = 0.4;
824
- Object.keys(clips).forEach(function (k) {
825
- clips[k].preload = "auto";
826
- });
827
-
828
- // Track mute state from the slideshow component's hf-sound event.
829
- var muted = false;
830
- var slideshow = document.querySelector("hyperframes-slideshow");
831
- if (slideshow) {
832
- slideshow.addEventListener("hf-sound", function (e) {
833
- muted = e.detail && e.detail.muted === true;
834
- });
835
- }
836
-
837
- var unlocked = false;
838
-
839
- function unlock() {
840
- if (unlocked) return;
841
- unlocked = true;
842
- // Prime wrapper-owned SFX clips: play muted then immediately pause/reset.
843
- // This moves the clip into the "allowed" state so later plays are instant.
844
- Object.keys(clips).forEach(function (name) {
845
- var el = clips[name];
846
- var v = el.volume;
847
- el.volume = 0;
848
- el.play()
849
- .then(function () {
850
- el.pause();
851
- el.currentTime = 0;
852
- el.volume = v;
853
- })
854
- .catch(function () {
855
- el.volume = v;
856
- });
857
- });
858
- }
859
-
860
- // Unlock on the first user gesture in the parent frame.
861
- window.addEventListener("keydown", unlock, true);
862
- window.addEventListener("pointerdown", unlock, true);
863
- window.addEventListener("click", unlock, true);
864
-
865
- window.addEventListener("message", function (e) {
866
- var d = e.data;
867
- if (!d || d.type !== "hf-sfx") return;
868
- // Gate on mute state — the component owns this.
869
- if (muted) return;
870
- var el = clips[d.name];
871
- if (!el || !unlocked) return;
872
- try {
873
- el.currentTime = 0;
874
- el.play().catch(function () {});
875
- } catch (err) {}
876
- });
877
- })();
878
- </script>
879
- ```
880
-
881
- **Sourcing SFX files:** use the HeyGen MCP `search_audio_sounds` tool with `type=sound_effects` and keywords like "whoosh", "click", "transition". Download the results to a local `sfx/` directory next to `demo.html` and reference them by relative path. Do not fetch SFX at render time — the HyperFrames determinism rule forbids runtime network requests; pre-download and commit them.
882
-
883
- ---
884
-
885
- ## 7. Three.js (optional)
886
-
887
- Add a Three.js scene behind the slides for ambient motion. The key rules:
888
-
889
- - **Own rAF loop** — do not integrate with the HF seek timeline. Three.js runs its own `requestAnimationFrame` loop independent of playhead position.
890
- - **One persistent canvas** — create the canvas once; update geometry/materials in-place per scene.
891
- - **Guard renderer creation** — WebGL may be unavailable (software-GL environments, some CI contexts). Create the renderer inside try/catch once; if it fails, hide the canvas and expose no-op stubs. Do not spam `console.error` — silence it during creation and restore it in `finally`.
892
- - **Full-bleed, behind content** — fix the canvas at `z-index: 0`, `pointer-events: none`, behind scene frames at `z-index: 1`.
893
- - **Transparent scene frames** — set scene backgrounds to `transparent` so the 3D canvas shows through. Use a radial-gradient scrim on the text container (not the scene frame itself) to keep type legible while letting 3D show in the margins.
894
- - **Expose a mood hook** — export `window.__threeApplyMood(sceneKey)` so the visibility controller can switch particle colors, toggle sub-objects, or change the clear color when the active scene changes.
895
-
896
- ```js
897
- // In index.html — Three.js setup (module script)
898
- import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js";
899
-
900
- var canvas = document.getElementById("three-canvas");
901
- var renderer = null;
902
- var _err = console.error;
903
- console.error = function () {}; // silence THREE's multi-line GPU error during init
904
- try {
905
- renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
906
- } catch (e) {
907
- // renderer stays null
908
- } finally {
909
- console.error = _err;
910
- }
911
-
912
- if (!renderer) {
913
- // Graceful degradation — branded layout is the fallback.
914
- canvas.style.display = "none";
915
- window.__threeApplyMood = function () {};
916
- // Do NOT start the rAF loop.
917
- } else {
918
- renderer.setSize(1920, 1080);
919
- renderer.setPixelRatio(1);
920
- canvas.style.cssText =
921
- "position:fixed;top:0;left:0;width:1920px;height:1080px;z-index:0;pointer-events:none";
922
-
923
- var scene = new THREE.Scene();
924
- var camera = new THREE.PerspectiveCamera(60, 1920 / 1080, 0.1, 1000);
925
- camera.position.set(0, 0, 5);
926
-
927
- // --- build your particle system, meshes, etc. here ---
928
-
929
- // Mood config: map sceneId → visual state (colors, sub-object visibility, bg color)
930
- var MOODS = {
931
- cover: {
932
- /* particle color, opacity, bg ... */
933
- },
934
- problem: {
935
- /* ... */
936
- },
937
- // one entry per scene key
938
- };
939
-
940
- window.__threeApplyMood = function (sceneKey) {
941
- var m = MOODS[sceneKey] || MOODS["cover"];
942
- // update geometry attributes, material opacity, sub-group visibility, etc.
943
- };
944
- window.__threeApplyMood("cover"); // apply initial state
945
-
946
- // --- own rAF loop ---
947
- var lastTime = null;
948
- function animate(ts) {
949
- requestAnimationFrame(animate);
950
- if (lastTime === null) lastTime = ts;
951
- var dt = Math.min((ts - lastTime) / 1000, 0.05);
952
- lastTime = ts;
953
- // update particles, rotate objects, etc.
954
- renderer.render(scene, camera);
955
- }
956
- requestAnimationFrame(animate);
957
- }
958
- ```
959
-
960
- **CSS for transparent scene frames + scrim:**
961
-
962
- ```css
963
- /* Three.js canvas — always behind everything */
964
- #three-canvas {
965
- position: fixed;
966
- top: 0;
967
- left: 0;
968
- width: 1920px;
969
- height: 1080px;
970
- z-index: 0;
971
- pointer-events: none;
972
- }
973
-
974
- /* Scene frames are transparent so the 3D canvas shows through */
975
- .scene-frame {
976
- position: absolute;
977
- top: 0;
978
- left: 0;
979
- width: 1920px;
980
- height: 1080px;
981
- background: transparent; /* NOT opaque — 3D would be occluded */
982
- z-index: 1;
983
- }
984
-
985
- /* Scrim on the TEXT container — not the scene frame.
986
- Radial gradient: opaque in the center where text is, transparent at edges
987
- so 3D shows in the whitespace margins. */
988
- .slide-inner.scrim-light {
989
- background: radial-gradient(
990
- ellipse 75% 80% at 50% 50%,
991
- rgba(255, 255, 255, 0.88) 30%,
992
- rgba(255, 255, 255, 0.6) 65%,
993
- rgba(255, 255, 255, 0) 100%
994
- );
995
- }
996
- ```
997
-
998
- ---
999
-
1000
- ## 8. Foot-gun checklist
1001
-
1002
- | Failure | Symptom | One-line fix |
1003
- | ----------------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
1004
- | Island not duplicated in wrapper | Slideshow chrome never renders; no slide counter, no prev/next | Copy the `<script type="application/hyperframes-slideshow+json">` block verbatim into the `<hyperframes-slideshow>` element in demo.html |
1005
- | Wrapper SFX in the iframe | Click/transition sounds silent | Move SFX Audio elements and unlock logic to demo.html; post `{type:'hf-sfx',name}` from index.html |
1006
- | No self-clock in composition | All scene frames stacked / wrong slide visible at load | Add the root GSAP timeline (`window.__timelines["root"]`) and the `onUpdate` visibility controller as shown in Section 3 |
1007
- | Content opacity:0 with no engine | Blank slides — `[data-anim]` elements invisible at rest | Call `updateVisibility(0)` synchronously after defining the controller so the first slide is shown immediately |
1008
- | Keydown bound to the element without focus | ArrowLeft/Right dead | Add `tabindex="0"` to `<hyperframes-slideshow>` so it can receive keyboard focus |
1009
- | Opaque scene background occluding Three.js canvas | 3D never visible | Set `background: transparent` on `.scene-frame`; put the visual fill on the text scrim container instead |
1010
- | WebGL renderer creation spams errors in headless envs | Console noise, rAF loop starts anyway | Silence `console.error` during `new THREE.WebGLRenderer(...)`, restore in `finally`, guard the rAF start on `renderer !== null` |
1011
- | Branch scene missing from postMessage manifest | Hotspot navigates but slide is blank | Include every scene — main line and branch — in the `scenes` array of the `postTimeline()` message |
1012
- | Prominent 3D/content in nav-capsule zone | Bright element bleeds behind/beside the nav pill | Keep the bottom-right ~360×140px region clear; add a background-matching gradient overlay on any slide whose 3D mood is bright in that corner |
1013
- | Custom media visualizer uses its own timer | Canvas/playhead drifts from the actual video or native controls | Drive visual state from media events and `media.currentTime`; do not use an independent `setTimeout` clock |
1014
- | One `data-wired` flag means two different things | Pre-rendered timeline HTML skips media listener setup | Use separate markers such as `data-timeline-rendered` and `data-media-sync-wired` |
1015
- | Presenter media events are not bridged | Audience follows slides but not play/pause/seek/mute | Mirror native media events over `BroadcastChannel("hf-slideshow:" + location.pathname)` in standalone wrappers with interactive media |
1016
- | Remote play is blocked in the audience window | Audience media time jumps but video never plays | Try muted playback first; if `media.play()` rejects, show an audience unlock button and ignore live `timeupdate` chasing until playback succeeds |
1017
- | Audience muted autoplay publishes back to presenter | Presenter audio starts, then mutes or cuts out | Publish media events only from presenter mode; audience mute is a local browser-autoplay workaround, not shared media state |
1018
- | Copied media lacks HyperFrames timing | Lint errors on untimed media; preview/render diverge | Add `data-start`, `data-duration`, and `data-has-audio="true"` when audible; avoid `preload="none"` |
1019
- | Source font CSS variables kept as font-family values | StaticGuard font-family contract errors | Replace with concrete render-safe stacks or add local `@font-face` declarations |
1020
- | Converted scroll/camera source jumps between slides | Per-slide focal points are correct but manual navigation snaps | Add a standalone navigation-camera transition hook; disable it for measurement, initial load, resize, and render/validation seeks |
1
+ # Standalone HyperFrames Slideshow Harness
2
+
3
+ ## 1. Interim framing — why this exists
4
+
5
+ These patterns are a **temporary workaround** for standalone demos. The durable solution is engine-hosted: a future `hyperframes preview --slideshow` / studio present mode will host the composition over the real HyperFrames engine, which drives seek-timelines frame-by-frame, owns the gesture frame, and reads the slideshow island directly from the composition. When that path ships, most of what follows collapses.
6
+
7
+ Until then, a standalone slideshow opened via the bare player bundle must work around three facts:
8
+
9
+ 1. The composition must expose a seekable `window.__timelines.root` timeline. Anything outside that seek path, such as Three.js loops or imperative entrance effects, must be self-driving.
10
+ 2. `<hyperframes-slideshow>` reads the slideshow island from its **own innerHTML** (the wrapper element), not from the composition the player loads. The island must be duplicated into the wrapper.
11
+ 3. The composition runs in the player's **iframe**; user keypresses and pointer events land on the **parent page**. Wrapper-owned SFX/global audio should live in the parent, where the activation token is reliable. Normal slide media stays in the composition and is stopped by the slideshow player on slide exit.
12
+
13
+ Do not treat these as the blessed authoring model. When the engine-hosted path ships, compositions authored the normal way will just work.
14
+
15
+ **Living reference implementations:**
16
+
17
+ - `registry/examples/airbnb-deck/index.html` + `demo.html` — full pattern set (Three.js, fragments, SFX, branch slide)
18
+ - `registry/examples/startup-pitch/index.html` — minimal version (no 3D), good starting point
19
+
20
+ ---
21
+
22
+ ## 2. The parent wrapper (`index.html` for deliverables, `demo.html` in examples)
23
+
24
+ The parent page hosts the two dist bundles, wraps the components, duplicates the island, and owns all audio.
25
+
26
+ For public or user-facing generated projects, make this wrapper the root `index.html` so opening the project in a browser runs the slideshow. Put the raw HyperFrames composition in a separate path such as `composition/index.html`. In repo examples you may still see this file called `demo.html`; that name is a reference pattern, not the preferred handoff for a standalone deck.
27
+
28
+ ```html
29
+ <!doctype html>
30
+ <html lang="en">
31
+ <head>
32
+ <meta charset="UTF-8" />
33
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
34
+ <title>My Deck — Slideshow Demo</title>
35
+
36
+ <!--
37
+ Load both bundles from packages/player/dist.
38
+ The global builds register <hyperframes-player> and <hyperframes-slideshow>
39
+ as custom elements — no import map needed.
40
+ -->
41
+ <script src="../../../packages/player/dist/hyperframes-player.global.js"></script>
42
+ <script src="../../../packages/player/dist/slideshow/hyperframes-slideshow.global.js"></script>
43
+
44
+ <style>
45
+ *,
46
+ *::before,
47
+ *::after {
48
+ box-sizing: border-box;
49
+ margin: 0;
50
+ padding: 0;
51
+ }
52
+ html,
53
+ body {
54
+ width: 100%;
55
+ height: 100%;
56
+ overflow: hidden;
57
+ background: #111;
58
+ }
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <!--
63
+ tabindex="0" is critical — <hyperframes-slideshow> binds keydown
64
+ (ArrowLeft/Right, Space, Backspace) to itself. Without tabindex the
65
+ element cannot receive focus and arrow keys are dead.
66
+ -->
67
+ <hyperframes-slideshow
68
+ tabindex="0"
69
+ style="display: block; position: relative; width: 100vw; height: 100vh"
70
+ >
71
+ <hyperframes-player
72
+ interactive
73
+ style="position: absolute; inset: 0"
74
+ src="composition/index.html"
75
+ ></hyperframes-player>
76
+
77
+ <!--
78
+ DUPLICATED ISLAND — keep in sync with the island inside index.html.
79
+ <hyperframes-slideshow> reads from its own innerHTML, not from the
80
+ composition the player loads. Every time slides/fragments/hotspots/
81
+ sequences change in index.html, update this copy too.
82
+ -->
83
+ <script type="application/hyperframes-slideshow+json">
84
+ {
85
+ "slides": [
86
+ { "sceneId": "scene-one", "notes": "..." },
87
+ {
88
+ "sceneId": "scene-two",
89
+ "notes": "...",
90
+ "fragments": [8.3, 8.6]
91
+ }
92
+ ],
93
+ "slideSequences": [
94
+ {
95
+ "id": "branch-one",
96
+ "label": "Branch label",
97
+ "slides": [{ "sceneId": "branch-scene", "notes": "..." }]
98
+ }
99
+ ]
100
+ }
101
+ </script>
102
+ </hyperframes-slideshow>
103
+ <!-- The built-in slideshow nav capsule renders Present; do not add a wrapper-level button. -->
104
+
105
+ <!-- Audio player lives here — see Section 6 -->
106
+ </body>
107
+ </html>
108
+ ```
109
+
110
+ `interactive` is required for decks with clickable page content or media controls. Without it, iframe pointer events are disabled by the player shell and a click on the composition can be interpreted as a player play/pause toggle instead of a slide interaction.
111
+
112
+ ### Per-slide `autoplay`
113
+
114
+ Add `"autoplay": true` to a slide in the island to play that slide's first `<video>` from the start when the presenter lands on it. The slideshow still holds — it never auto-advances — so the presenter clicks Next when ready; autoplay only saves a manual play click into the composition.
115
+
116
+ ```json
117
+ {
118
+ "sceneId": "cold-open",
119
+ "autoplay": true,
120
+ "notes": "Promo plays on enter; click Next when it ends."
121
+ }
122
+ ```
123
+
124
+ Use `autoplay` when the video **is** the slide's primary content and its natural end is the cue to advance — a cold-open promo, a demo clip you let run and then move on from. Do **not** use it for background/ambient loops or for footage the presenter talks over; those should start on the presenter's own cue (a click on the clip's controls via `interactive`), not automatically. One autoplay clip per slide (the first `<video>` in the scene).
125
+
126
+ ### Presenter media bridge for interactive media
127
+
128
+ Presenter/audience mode syncs slide position through a deck-scoped `BroadcastChannel`. If the presenter is expected to play, pause, seek, mute, or change rate on media inside the composition, mirror those native media events over the same channel. Keep the media element as the source of truth; do not mirror a custom button's private state.
129
+
130
+ Audible playback has one extra browser constraint: a `BroadcastChannel` message does not carry the presenter's user activation into the audience window. The audience window should try presenter-driven playback muted first, because browsers usually allow muted autoplay; it may still reject `media.play()` even while it accepts remote `currentTime` updates. If that happens, do not keep chasing presenter `timeupdate` messages; show an audience-side unlock control, store the latest play intent, and retry playback from that intent after the audience window receives a click/key gesture.
131
+
132
+ ```js
133
+ (function () {
134
+ if (typeof BroadcastChannel === "undefined") return;
135
+
136
+ var sender =
137
+ new URLSearchParams(location.search).get("mode") === "audience" ? "audience" : "presenter";
138
+ var channel = new BroadcastChannel("hf-slideshow:" + location.pathname);
139
+ var applyingRemote = false;
140
+ var lastTimeBroadcast = 0;
141
+ var pendingPlayByKey = {};
142
+ var blockedPlayByKey = {};
143
+ var mutedPlaybackByKey = {};
144
+ var unlockButton = null;
145
+
146
+ function frameDocument() {
147
+ var player = document.querySelector("hyperframes-player");
148
+ var frame = player && player.iframeElement;
149
+ try {
150
+ return frame && frame.contentDocument ? frame.contentDocument : null;
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+ function mediaNodes() {
156
+ var doc = frameDocument();
157
+ return doc ? Array.from(doc.querySelectorAll("video,audio")) : [];
158
+ }
159
+ function mediaKey(el, index) {
160
+ return el.id ? "id:" + el.id : el.tagName.toLowerCase() + ":" + index;
161
+ }
162
+ function findMedia(key) {
163
+ return mediaNodes().find(function (el, index) {
164
+ return mediaKey(el, index) === key;
165
+ });
166
+ }
167
+ function syncMediaState(el, msg, allowTimeSync) {
168
+ if (typeof msg.playbackRate === "number") el.playbackRate = msg.playbackRate;
169
+ if (typeof msg.volume === "number") el.volume = Math.max(0, Math.min(1, msg.volume));
170
+ if (sender === "audience" && mutedPlaybackByKey[msg.key]) {
171
+ el.muted = true;
172
+ } else if (typeof msg.muted === "boolean") {
173
+ el.muted = msg.muted;
174
+ }
175
+ if (
176
+ allowTimeSync &&
177
+ typeof msg.currentTime === "number" &&
178
+ Math.abs((el.currentTime || 0) - msg.currentTime) > 0.35
179
+ ) {
180
+ el.currentTime = Math.max(0, msg.currentTime);
181
+ }
182
+ }
183
+ function hasBlockedPlay() {
184
+ return Object.keys(blockedPlayByKey).length > 0;
185
+ }
186
+ function hideAudienceUnlockIfClear() {
187
+ if (hasBlockedPlay() || !unlockButton) return;
188
+ unlockButton.remove();
189
+ unlockButton = null;
190
+ }
191
+ function showAudienceUnlock() {
192
+ if (sender !== "audience" || unlockButton) return;
193
+ unlockButton = document.createElement("button");
194
+ unlockButton.type = "button";
195
+ unlockButton.textContent = "Enable audience media";
196
+ unlockButton.style.cssText =
197
+ "position:fixed;left:50%;bottom:96px;transform:translateX(-50%);z-index:100000;border:0;border-radius:999px;padding:12px 18px;background:#fff;color:#111827;box-shadow:0 10px 32px rgba(0,0,0,.28);font:700 14px/1 system-ui,sans-serif;cursor:pointer;";
198
+ unlockButton.addEventListener("click", retryBlockedPlays);
199
+ document.body.appendChild(unlockButton);
200
+ }
201
+ function rememberBlockedPlay(msg) {
202
+ pendingPlayByKey[msg.key] = msg;
203
+ blockedPlayByKey[msg.key] = true;
204
+ showAudienceUnlock();
205
+ }
206
+ function clearBlockedPlay(key) {
207
+ delete blockedPlayByKey[key];
208
+ hideAudienceUnlockIfClear();
209
+ }
210
+ function tryPlay(el, msg) {
211
+ if (sender === "audience") mutedPlaybackByKey[msg.key] = true;
212
+ syncMediaState(el, msg, true);
213
+ if (sender === "audience") el.muted = true;
214
+ try {
215
+ var playResult = el.play();
216
+ if (playResult && typeof playResult.then === "function") {
217
+ playResult
218
+ .then(function () {
219
+ clearBlockedPlay(msg.key);
220
+ })
221
+ .catch(function () {
222
+ rememberBlockedPlay(msg);
223
+ });
224
+ } else {
225
+ clearBlockedPlay(msg.key);
226
+ }
227
+ } catch (e) {
228
+ rememberBlockedPlay(msg);
229
+ }
230
+ }
231
+ function retryBlockedPlays() {
232
+ wireMedia();
233
+ applyingRemote = true;
234
+ try {
235
+ Object.keys(pendingPlayByKey).forEach(function (key) {
236
+ var msg = pendingPlayByKey[key];
237
+ var el = findMedia(key);
238
+ if (el && msg) tryPlay(el, msg);
239
+ });
240
+ } finally {
241
+ setTimeout(function () {
242
+ applyingRemote = false;
243
+ }, 300);
244
+ }
245
+ }
246
+ function publish(el, index, action) {
247
+ if (sender !== "presenter") return;
248
+ if (applyingRemote) return;
249
+ if (action === "timeupdate") {
250
+ var now = performance.now();
251
+ if (now - lastTimeBroadcast < 450 && !el.paused) return;
252
+ lastTimeBroadcast = now;
253
+ }
254
+ channel.postMessage({
255
+ type: "media",
256
+ sender,
257
+ key: mediaKey(el, index),
258
+ action,
259
+ currentTime: el.currentTime || 0,
260
+ paused: el.paused,
261
+ ended: el.ended,
262
+ muted: el.muted,
263
+ volume: el.volume,
264
+ playbackRate: el.playbackRate,
265
+ });
266
+ }
267
+ function wireMedia() {
268
+ mediaNodes().forEach(function (el, index) {
269
+ if (el.dataset.hfPresenterMediaSync === "1") return;
270
+ el.dataset.hfPresenterMediaSync = "1";
271
+ [
272
+ "play",
273
+ "pause",
274
+ "seeking",
275
+ "seeked",
276
+ "ratechange",
277
+ "volumechange",
278
+ "ended",
279
+ "timeupdate",
280
+ ].forEach(function (name) {
281
+ el.addEventListener(name, function () {
282
+ publish(el, index, name);
283
+ });
284
+ });
285
+ });
286
+ }
287
+ channel.addEventListener("message", function (event) {
288
+ var msg = event.data;
289
+ if (!msg || msg.type !== "media" || msg.sender === sender) return;
290
+ if (sender === "audience" && blockedPlayByKey[msg.key] && msg.action === "timeupdate") {
291
+ pendingPlayByKey[msg.key] = msg;
292
+ showAudienceUnlock();
293
+ return;
294
+ }
295
+ var el = findMedia(msg.key);
296
+ if (!el) return;
297
+ applyingRemote = true;
298
+ try {
299
+ if (
300
+ msg.action === "play" ||
301
+ (sender === "audience" && msg.action === "timeupdate" && msg.paused === false && el.paused)
302
+ ) {
303
+ pendingPlayByKey[msg.key] = msg;
304
+ tryPlay(el, msg);
305
+ } else {
306
+ syncMediaState(el, msg, true);
307
+ }
308
+ if (msg.action === "pause" || msg.action === "ended") {
309
+ delete pendingPlayByKey[msg.key];
310
+ delete mutedPlaybackByKey[msg.key];
311
+ clearBlockedPlay(msg.key);
312
+ el.pause();
313
+ }
314
+ } catch {
315
+ } finally {
316
+ setTimeout(function () {
317
+ applyingRemote = false;
318
+ }, 300);
319
+ }
320
+ });
321
+ wireMedia();
322
+ window.addEventListener("load", wireMedia);
323
+ window.addEventListener("keydown", retryBlockedPlays, true);
324
+ window.addEventListener("pointerdown", retryBlockedPlays, true);
325
+ setInterval(wireMedia, 1000);
326
+ })();
327
+ ```
328
+
329
+ ### Custom media visualizers
330
+
331
+ For waveform, beat-grid, canvas, or timeline players, wire visual state to the native media element. This keeps native controls, custom controls, presenter sync, slide-exit cleanup, and global mute in one event path.
332
+
333
+ ```js
334
+ function wireMediaDrivenVisualizer(media, renderFrame, fireCrossedEvents) {
335
+ var mediaFrame = 0;
336
+ var lastTime = media.currentTime || 0;
337
+
338
+ function update() {
339
+ var time = media.currentTime || 0;
340
+ if (Math.abs(time - lastTime) < 1.5 && time >= lastTime) {
341
+ fireCrossedEvents(lastTime, time);
342
+ }
343
+ lastTime = time;
344
+ renderFrame(time, media);
345
+ }
346
+
347
+ function start() {
348
+ if (!media.requestVideoFrameCallback || mediaFrame) return;
349
+ mediaFrame = media.requestVideoFrameCallback(function () {
350
+ mediaFrame = 0;
351
+ update();
352
+ if (!media.paused && !media.ended) start();
353
+ });
354
+ }
355
+
356
+ media.addEventListener("play", start);
357
+ media.addEventListener("playing", start);
358
+ media.addEventListener("pause", update);
359
+ media.addEventListener("ended", update);
360
+ media.addEventListener("timeupdate", update);
361
+ media.addEventListener("seeking", function () {
362
+ lastTime = media.currentTime || 0;
363
+ renderFrame(lastTime, media);
364
+ });
365
+ media.addEventListener("seeked", update);
366
+ media.addEventListener("ratechange", update);
367
+ media.addEventListener("volumechange", update);
368
+ renderFrame(media.currentTime || 0, media);
369
+ }
370
+ ```
371
+
372
+ Do not use `requestAnimationFrame` inside compositions for media sync; composition lint rejects wall-clock loops. Prefer `HTMLVideoElement.requestVideoFrameCallback()` for smooth video-tied updates and rely on native `timeupdate`/seek events as the fallback.
373
+
374
+ Use a dedicated wiring marker such as `data-media-sync-wired`. Do not reuse a marker like `data-wired` for both "timeline DOM already rendered" and "media event listeners attached"; pre-rendered timeline HTML will otherwise skip listener setup.
375
+
376
+ ### Editable presenter notes
377
+
378
+ The shared `<hyperframes-slideshow>` presenter already renders speaker notes as an editable textarea and stores edits in `localStorage`. Do not add deck-specific note editors when the shared player is available.
379
+
380
+ For interim custom wrappers that cannot use the shared presenter chrome, use this deterministic storage contract exactly so notes migrate cleanly:
381
+
382
+ ```js
383
+ const NOTES_STORAGE_PREFIX = "hf-slideshow:presenter-notes:v1:";
384
+
385
+ function notesDeckKey(slideshowEl) {
386
+ const explicit = slideshowEl.getAttribute("notes-storage-key");
387
+ if (explicit && explicit.trim()) return explicit.trim();
388
+
389
+ const playerSrc = slideshowEl.querySelector("hyperframes-player")?.getAttribute("src") || "";
390
+ let resolvedPlayerSrc = playerSrc;
391
+ try {
392
+ resolvedPlayerSrc = new URL(playerSrc, location.href).href;
393
+ } catch {}
394
+
395
+ return `${location.origin}${location.pathname}|${document.title}|${resolvedPlayerSrc}`;
396
+ }
397
+
398
+ function notesStorageKey(slideshowEl, position, slide) {
399
+ return `${NOTES_STORAGE_PREFIX}${JSON.stringify([
400
+ notesDeckKey(slideshowEl),
401
+ position.sequenceId,
402
+ position.slideIndex,
403
+ slide.sceneId || "",
404
+ ])}`;
405
+ }
406
+
407
+ function readPresenterNotes(slideshowEl, position, slide) {
408
+ const key = notesStorageKey(slideshowEl, position, slide);
409
+ try {
410
+ const stored = localStorage.getItem(key);
411
+ return stored == null ? slide.notes || "" : stored;
412
+ } catch {
413
+ return slide.notes || "";
414
+ }
415
+ }
416
+
417
+ function wirePresenterNotes(textarea, slideshowEl, position, slide) {
418
+ const key = notesStorageKey(slideshowEl, position, slide);
419
+ textarea.value = readPresenterNotes(slideshowEl, position, slide);
420
+ textarea.addEventListener("input", function () {
421
+ try {
422
+ localStorage.setItem(key, textarea.value);
423
+ } catch {}
424
+ });
425
+ }
426
+ ```
427
+
428
+ Clearing the textarea must save an empty string, not remove the local value, because a presenter may intentionally blank a manifest note for their run. Use `notes-storage-key="stable-deck-id"` on `<hyperframes-slideshow>` when a standalone demo has a stable project id; otherwise the fallback key isolates by page, title, and player `src`.
429
+
430
+ ---
431
+
432
+ ## 3. Playhead-driven scene visibility
433
+
434
+ Without the engine, scenes are driven by a `root` GSAP timeline that the composition manages on its own clock. The visibility controller reads `window.__timelines.root.time()` via that timeline's `onUpdate` callback and sets `opacity` accordingly. Only the active scene is visible.
435
+
436
+ The key insight: scene backgrounds must be `transparent` (not opaque) if you want a Three.js canvas behind them; the body/html background and scene inline `background` set the visual fill.
437
+
438
+ For converted source pages, preserve the original page's visual design, motion language, interactive behavior, media behavior, and presentation affordances as closely as practical. Port source-specific widgets exactly where practical: custom canvas players, waveform/timeline decorations, expanding rings, playheads, hover states, and event wiring are source material, not optional polish. Also audit for atypical page movement: scroll-scrubbed cameras, parallax, pinned sections, horizontal scrollers, section snapping, translated/scaled world layers, or zoom-to-element navigation. Scroll is often the source's transition trigger, so extract the scroll-progress stops, easing, and camera/focus states, then re-host that motion on slideshow navigation through timeline positions, fragments, or reusable harness hooks. Do not recreate the browser's literal page-scroll-down motion inside a slide; translate it into camera travel/zoom from one focus area to the next. If the same mechanical behavior appears across decks, move it into the player or this harness instead of copying a fragile one-off script.
439
+
440
+ ### Navigation camera transitions for converted pages
441
+
442
+ When a source page uses scroll to move a translated/scaled world, slideshow navigation usually seeks directly to each slide's hold frame. That seek bypasses any in-timeline interpolation near the scene boundary, so a deck can compute the right camera positions and still appear to jump. Add an explicit standalone navigation transition for manual slide changes, while keeping normal HyperFrames timeline seeks static and deterministic.
443
+
444
+ Use this pattern only for direct-open/presenter slideshow UI. Do not depend on CSS transitions for rendered video output; rendered compositions must still be correct when seeking a single frame.
445
+
446
+ ```css
447
+ #world {
448
+ transform-origin: 0 0;
449
+ will-change: transform;
450
+ transition:
451
+ transform 760ms cubic-bezier(0.22, 1, 0.36, 1),
452
+ opacity 0.3s ease;
453
+ }
454
+
455
+ #world.hf-camera-static {
456
+ transition: opacity 0.3s ease;
457
+ }
458
+ ```
459
+
460
+ ```js
461
+ var currentCamera = null;
462
+ var currentSlideIndex = null;
463
+
464
+ function cameraTransform(cam) {
465
+ return "translate(" + cam.tx + "px," + cam.ty + "px) scale(" + cam.s + ")";
466
+ }
467
+
468
+ function setWorldCamera(world, cam, animate) {
469
+ if (!cam) return;
470
+ if (!animate) world.classList.add("hf-camera-static");
471
+ world.style.transform = cameraTransform(cam);
472
+ world.style.opacity = "1";
473
+ if (!animate) {
474
+ world.getBoundingClientRect();
475
+ world.classList.remove("hf-camera-static");
476
+ }
477
+ currentCamera = cam;
478
+ }
479
+
480
+ function slideIndexAtTime(t, slideDuration, slideCount) {
481
+ return Math.max(0, Math.min(slideCount - 1, Math.floor(t / slideDuration)));
482
+ }
483
+
484
+ function updateCameraForTime(t, opts) {
485
+ var nextSlideIndex = slideIndexAtTime(t, SLIDE_DURATION, SLIDES.length);
486
+ var jumpedBetweenSlides =
487
+ currentSlideIndex !== null &&
488
+ nextSlideIndex !== currentSlideIndex &&
489
+ Math.abs(t - lastTime) > 1.2;
490
+ var animateCamera = Boolean(
491
+ window.__hfCameraTransitionsEnabled && jumpedBetweenSlides && !(opts && opts.staticCamera),
492
+ );
493
+ var cam = cameraAtTime(t);
494
+ setWorldCamera(world, cam, animateCamera);
495
+ currentSlideIndex = nextSlideIndex;
496
+ }
497
+ ```
498
+
499
+ During measurement, temporarily remove the transform with `hf-camera-static`, compute all element union rects, then restore `currentCamera` without animation. On initial load, resize, and validation-style seeks, call `updateCameraForTime(t, { staticCamera: true })`. In the standalone wrapper, set `iframe.contentWindow.__hfCameraTransitionsEnabled = true` after the player iframe is available. That keeps the exported composition seekable while letting presenter navigation glide between focal points.
500
+
501
+ Before validation, resolve source font variables. HyperFrames lint accepts concrete generic stacks such as `system-ui, sans-serif` and `ui-monospace, monospace`, or real `@font-face` declarations pointing at local font files. It does not accept `font-family: var(--f-body)` / `var(--f-mono)` as a render-safe family.
502
+
503
+ ```html
504
+ <!-- In index.html (composition) -->
505
+
506
+ <!-- Shared scene CSS — all scenes start hidden -->
507
+ <style>
508
+ .scene-frame {
509
+ position: absolute;
510
+ top: 0;
511
+ left: 0;
512
+ width: 1920px;
513
+ height: 1080px;
514
+ overflow: hidden;
515
+ opacity: 0; /* hidden at rest — visibility controller shows the active one */
516
+ visibility: hidden; /* opacity:0 alone still lets invisible frames block clicks */
517
+ pointer-events: none; /* inactive scenes must not swallow events */
518
+ }
519
+ </style>
520
+
521
+ <!--
522
+ content-visible-at-rest: mark the first scene's elements with their
523
+ final non-hidden state so the deck is not blank before the controller
524
+ fires. The controller calls updateVisibility(0) synchronously on load.
525
+
526
+ If using Three.js canvas behind scenes, set background: transparent
527
+ here and let the 3D canvas + body color supply the fill.
528
+ -->
529
+ <div
530
+ id="scene-cover"
531
+ class="scene-frame clip"
532
+ data-composition-id="cover"
533
+ data-start="0"
534
+ data-duration="9"
535
+ style="background: transparent"
536
+ >
537
+ <!-- content here -->
538
+ </div>
539
+
540
+ <div
541
+ id="scene-problem"
542
+ class="scene-frame clip"
543
+ data-composition-id="problem"
544
+ data-start="9"
545
+ data-duration="9"
546
+ style="background: transparent"
547
+ >
548
+ <!-- content here -->
549
+ </div>
550
+
551
+ <!-- Root timeline — spans the full composition duration -->
552
+ <script>
553
+ (function () {
554
+ window.__timelines = window.__timelines || {};
555
+ var tl = gsap.timeline({ paused: true });
556
+ // A single to() for the full duration establishes the seekable range
557
+ tl.to({}, { duration: 108 }); // replace 108 with your total seconds
558
+ window.__timelines["root"] = tl;
559
+ })();
560
+ </script>
561
+
562
+ <!-- Visibility controller -->
563
+ <script>
564
+ (function () {
565
+ var scenes = [
566
+ { id: "scene-cover", start: 0, end: 9 },
567
+ { id: "scene-problem", start: 9, end: 18 },
568
+ // ... all scenes including branch scenes
569
+ ];
570
+
571
+ var lastActiveId = null;
572
+
573
+ function updateVisibility(t) {
574
+ for (var i = 0; i < scenes.length; i++) {
575
+ var s = scenes[i];
576
+ var el = document.getElementById(s.id);
577
+ if (!el) continue;
578
+ var active = t >= s.start && t < s.end;
579
+ el.style.opacity = active ? "1" : "0";
580
+ el.style.visibility = active ? "visible" : "hidden";
581
+ el.style.pointerEvents = active ? "auto" : "none";
582
+
583
+ if (active && lastActiveId !== s.id) {
584
+ lastActiveId = s.id;
585
+ fireEntrance(el); // see Section 4
586
+ }
587
+ }
588
+ // fragment reveals here — see Section 4
589
+ }
590
+
591
+ window.__hfSetTime = updateVisibility;
592
+
593
+ // Show first slide immediately — avoids blank on load
594
+ updateVisibility(0);
595
+
596
+ // Hook the root timeline so every seek drives visibility
597
+ var root = window.__timelines && window.__timelines["root"];
598
+ if (root) {
599
+ root.eventCallback("onUpdate", function () {
600
+ updateVisibility(root.time());
601
+ });
602
+ }
603
+ })();
604
+ </script>
605
+ ```
606
+
607
+ ---
608
+
609
+ ## 4. Imperative entrances on slide-activate
610
+
611
+ The engine-hosted path drives GSAP seek-timelines frame by frame. Without it, seek-timeline tweens never fire. Instead, fire imperative `gsap.from()` calls each time a scene becomes active — these run on GSAP's own ticker and are independent of any playhead.
612
+
613
+ Fragment reveals use playhead-crossing: the visibility controller checks whether the playhead has passed each fragment's hold-time and fires an animation on the first crossing. Bunch fragment hold-times near the scene start (within the first 300–500 ms of the scene) so successive ArrowRight presses feel like snappy sequential reveals rather than long waits.
614
+
615
+ ```js
616
+ // --- Entrance animations ---
617
+
618
+ function fireEntrance(sceneEl) {
619
+ // [data-anim] marks elements that should entrance on slide-activate.
620
+ // Add data-anim to eyebrows, headlines, subheads, and card grids.
621
+ var animEls = sceneEl.querySelectorAll("[data-anim]");
622
+ if (!animEls.length) return;
623
+ gsap.from(animEls, {
624
+ opacity: 0,
625
+ y: 28,
626
+ duration: 0.4,
627
+ stagger: 0.07,
628
+ ease: "power2.out",
629
+ overwrite: true, // cancel any in-flight animation on rapid slide changes
630
+ });
631
+ }
632
+
633
+ // --- Fragment reveals ---
634
+
635
+ // Fragment config: times in absolute composition timeline seconds,
636
+ // bunched near the scene start for snappy successive reveals.
637
+ var fragments = [
638
+ { time: 9.3, id: "prob-item1", revealed: false },
639
+ { time: 9.6, id: "prob-item2", revealed: false },
640
+ ];
641
+
642
+ function revealFragment(id) {
643
+ var el = document.getElementById(id);
644
+ if (!el) return;
645
+ gsap.fromTo(
646
+ el,
647
+ { opacity: 0, x: -24 },
648
+ { opacity: 1, x: 0, duration: 0.35, ease: "power2.out", overwrite: true },
649
+ );
650
+ }
651
+
652
+ // Inside updateVisibility(t):
653
+ for (var f = 0; f < fragments.length; f++) {
654
+ if (!fragments[f].revealed && t >= fragments[f].time) {
655
+ fragments[f].revealed = true;
656
+ revealFragment(fragments[f].id);
657
+ }
658
+ }
659
+
660
+ // On problem scene re-entry, reset all fragment states:
661
+ if (active && lastActiveId !== s.id && s.id === "scene-problem") {
662
+ for (var f = 0; f < fragments.length; f++) {
663
+ fragments[f].revealed = false;
664
+ var pEl = document.getElementById(fragments[f].id);
665
+ if (pEl) gsap.set(pEl, { opacity: 0, clearProps: "transform" });
666
+ }
667
+ }
668
+ ```
669
+
670
+ Fragment items start with `opacity: 0` in CSS. The visibility controller reveals them; the entrance driver does not touch them until crossing.
671
+
672
+ ---
673
+
674
+ ## 5. The scenes bootstrap postMessage
675
+
676
+ `<hyperframes-slideshow>` must know each scene's time range to map a `sceneId` to a playhead position. Without the engine injecting this at runtime, the composition must post it manually after load.
677
+
678
+ Post the manifest from the composition (index.html), not the parent wrapper:
679
+
680
+ ```js
681
+ // In index.html — post after a brief delay so the parent frame has settled
682
+ (function () {
683
+ var FPS = 30;
684
+ var totalSeconds = 108; // match your composition's data-duration
685
+ var totalFrames = totalSeconds * FPS;
686
+
687
+ var scenes = [
688
+ // EVERY scene — including branch scenes — must appear here.
689
+ // id must match data-composition-id; start/duration in seconds.
690
+ { id: "cover", start: 0, duration: 9 },
691
+ { id: "problem", start: 9, duration: 9 },
692
+ { id: "solution", start: 18, duration: 9 },
693
+ // ... all main-line scenes ...
694
+ // branch scene — listed last, NOT in main slides array in the island
695
+ { id: "market-sizing", start: 99, duration: 9 },
696
+ ];
697
+
698
+ function postTimeline() {
699
+ parent.postMessage(
700
+ {
701
+ source: "hf-preview",
702
+ type: "timeline",
703
+ durationInFrames: totalFrames,
704
+ scenes: scenes,
705
+ },
706
+ "*",
707
+ );
708
+ }
709
+
710
+ // ~300ms delay after load to let the parent settle
711
+ if (document.readyState === "complete") {
712
+ setTimeout(postTimeline, 300);
713
+ } else {
714
+ window.addEventListener("load", function () {
715
+ setTimeout(postTimeline, 300);
716
+ });
717
+ }
718
+ })();
719
+ ```
720
+
721
+ Omitting any scene (including branch scenes) from this manifest means the slideshow component cannot seek to it. Include every scene declared in the HTML, even scenes only reachable via a hotspot.
722
+
723
+ ---
724
+
725
+ ## 6. Audio/SFX — built-in mute control via `<hyperframes-slideshow sound>`
726
+
727
+ Wrapper-owned SFX should live in the parent page. Browsers enforce user-activation for AudioContext and HTMLAudioElement.play() — an iframe without its own activation (i.e., the user never clicked inside it) is often autoplay-blocked. The user's keypress lands on the parent, so the parent is the reliable frame for click/transition sound effects.
728
+
729
+ Normal slide media should stay in the composition. The slideshow player now stops slide media automatically on slide/sequence changes by calling `hyperframes-player.stopMedia()`, which pauses iframe `<video>` / `<audio>`, runtime WebAudio, and parent proxies adopted from iframe media. Same-slide fragment reveals do not stop media, and global/deck-level parent audio such as `audio-src` is left alone. Do not hand-roll per-slide cleanup scripts for regular video/audio players.
730
+
731
+ Every copied `<video>` / `<audio>` with a `src` must be timed for HyperFrames ownership:
732
+
733
+ ```html
734
+ <video
735
+ src="assets/demo.mp4"
736
+ controls
737
+ playsinline
738
+ preload="metadata"
739
+ data-start="0"
740
+ data-duration="96"
741
+ data-has-audio="true"
742
+ ></video>
743
+ ```
744
+
745
+ Use `data-has-audio="true"` only for audible media. Muted autoplay loops can omit it. Do not leave `preload="none"` in converted compositions.
746
+
747
+ Implementation detail: iframe media elements belong to the iframe's DOM realm. Fallback code in the parent page/player must not use the parent page's `el instanceof HTMLMediaElement` check for iframe nodes; in real browsers that fails and leaves videos audible. Use `el.ownerDocument.defaultView.HTMLMediaElement` or a tag/duck-type guard before setting `muted` or calling `pause()`.
748
+
749
+ ### Mute toggle — built-in chrome control
750
+
751
+ Add the `sound` boolean attribute to `<hyperframes-slideshow>` in demo.html. The component renders a speaker/speaker-muted SVG button as the **leftmost item in the nav capsule**, styled identically to the prev/next ghost buttons. No separate mute button in the composition.
752
+
753
+ ```html
754
+ <hyperframes-slideshow tabindex="0" sound style="..."> ... </hyperframes-slideshow>
755
+ ```
756
+
757
+ The component:
758
+
759
+ - Tracks `muted` state (default `false`); exposes a `muted` getter
760
+ - Reflects to a `data-hf-muted` attribute on the host when muted
761
+ - Applies mute globally to child `<hyperframes-player>` media and top-level page `<audio>` / `<video>` elements
762
+ - Dispatches `CustomEvent("hf-sound", { detail: { muted }, bubbles: true, composed: true })` on every toggle
763
+ - Browser-checks the actual iframe media state after changes; every composition `<video>` / `<audio>` should report `muted: true` after clicking the nav mute button
764
+
765
+ Wrapper-owned `new Audio(...)` objects are not attached to the DOM, so the parent audio player must mirror the `hf-sound` event onto each clip:
766
+
767
+ ```js
768
+ var muted = false;
769
+ var slideshow = document.querySelector("hyperframes-slideshow");
770
+ if (slideshow) {
771
+ slideshow.addEventListener("hf-sound", function (e) {
772
+ muted = e.detail && e.detail.muted === true;
773
+ Object.keys(clips).forEach(function (name) {
774
+ clips[name].muted = muted;
775
+ });
776
+ });
777
+ }
778
+ // In message handler:
779
+ if (muted) return; // skip play
780
+ ```
781
+
782
+ If `sound` is **not** present on `<hyperframes-slideshow>` (decks without audio), the mute control is hidden — the capsule shows only nav.
783
+
784
+ ### Composition: post cues unconditionally
785
+
786
+ The composition posts sfx cues **unconditionally** — it does not track mute state. The parent gates on `muted`:
787
+
788
+ **In the composition (index.html):**
789
+
790
+ ```js
791
+ // Post an sfx cue at transition points — unconditionally.
792
+ // The parent audio player gates on the slideshow component's mute state.
793
+ function playSfx(name) {
794
+ try {
795
+ parent.postMessage({ type: "hf-sfx", name: name }, "*");
796
+ } catch (e) {}
797
+ }
798
+
799
+ // Fire at scene transitions:
800
+ // playSfx("advance") — moving to the next main-line slide
801
+ // playSfx("back") — returning from a branch
802
+ // playSfx("branch-enter") — entering a branch
803
+ // playSfx("fragment") — a fragment item is revealed
804
+ ```
805
+
806
+ Do NOT add a mute button inside the composition. The `#sfx-mute` coral button pattern is removed; the nav capsule in the parent chrome owns mute.
807
+
808
+ **In the parent (demo.html):**
809
+
810
+ ```html
811
+ <script>
812
+ (function () {
813
+ // Audio elements are preloaded here, in the frame that receives user gestures.
814
+ var clips = {
815
+ advance: new Audio("sfx/advance.mp3"),
816
+ fragment: new Audio("sfx/fragment.mp3"),
817
+ "branch-enter": new Audio("sfx/branch-enter.mp3"),
818
+ back: new Audio("sfx/back.mp3"),
819
+ };
820
+ clips.advance.volume = 0.45;
821
+ clips.fragment.volume = 0.4;
822
+ clips["branch-enter"].volume = 0.4;
823
+ clips.back.volume = 0.4;
824
+ Object.keys(clips).forEach(function (k) {
825
+ clips[k].preload = "auto";
826
+ });
827
+
828
+ // Track mute state from the slideshow component's hf-sound event.
829
+ var muted = false;
830
+ var slideshow = document.querySelector("hyperframes-slideshow");
831
+ if (slideshow) {
832
+ slideshow.addEventListener("hf-sound", function (e) {
833
+ muted = e.detail && e.detail.muted === true;
834
+ });
835
+ }
836
+
837
+ var unlocked = false;
838
+
839
+ function unlock() {
840
+ if (unlocked) return;
841
+ unlocked = true;
842
+ // Prime wrapper-owned SFX clips: play muted then immediately pause/reset.
843
+ // This moves the clip into the "allowed" state so later plays are instant.
844
+ Object.keys(clips).forEach(function (name) {
845
+ var el = clips[name];
846
+ var v = el.volume;
847
+ el.volume = 0;
848
+ el.play()
849
+ .then(function () {
850
+ el.pause();
851
+ el.currentTime = 0;
852
+ el.volume = v;
853
+ })
854
+ .catch(function () {
855
+ el.volume = v;
856
+ });
857
+ });
858
+ }
859
+
860
+ // Unlock on the first user gesture in the parent frame.
861
+ window.addEventListener("keydown", unlock, true);
862
+ window.addEventListener("pointerdown", unlock, true);
863
+ window.addEventListener("click", unlock, true);
864
+
865
+ window.addEventListener("message", function (e) {
866
+ var d = e.data;
867
+ if (!d || d.type !== "hf-sfx") return;
868
+ // Gate on mute state — the component owns this.
869
+ if (muted) return;
870
+ var el = clips[d.name];
871
+ if (!el || !unlocked) return;
872
+ try {
873
+ el.currentTime = 0;
874
+ el.play().catch(function () {});
875
+ } catch (err) {}
876
+ });
877
+ })();
878
+ </script>
879
+ ```
880
+
881
+ **Sourcing SFX files:** use the HeyGen MCP `search_audio_sounds` tool with `type=sound_effects` and keywords like "whoosh", "click", "transition". Download the results to a local `sfx/` directory next to `demo.html` and reference them by relative path. Do not fetch SFX at render time — the HyperFrames determinism rule forbids runtime network requests; pre-download and commit them.
882
+
883
+ ---
884
+
885
+ ## 7. Three.js (optional)
886
+
887
+ Add a Three.js scene behind the slides for ambient motion. The key rules:
888
+
889
+ - **Own rAF loop** — do not integrate with the HF seek timeline. Three.js runs its own `requestAnimationFrame` loop independent of playhead position.
890
+ - **One persistent canvas** — create the canvas once; update geometry/materials in-place per scene.
891
+ - **Guard renderer creation** — WebGL may be unavailable (software-GL environments, some CI contexts). Create the renderer inside try/catch once; if it fails, hide the canvas and expose no-op stubs. Do not spam `console.error` — silence it during creation and restore it in `finally`.
892
+ - **Full-bleed, behind content** — fix the canvas at `z-index: 0`, `pointer-events: none`, behind scene frames at `z-index: 1`.
893
+ - **Transparent scene frames** — set scene backgrounds to `transparent` so the 3D canvas shows through. Use a radial-gradient scrim on the text container (not the scene frame itself) to keep type legible while letting 3D show in the margins.
894
+ - **Expose a mood hook** — export `window.__threeApplyMood(sceneKey)` so the visibility controller can switch particle colors, toggle sub-objects, or change the clear color when the active scene changes.
895
+
896
+ ```js
897
+ // In index.html — Three.js setup (module script)
898
+ import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js";
899
+
900
+ var canvas = document.getElementById("three-canvas");
901
+ var renderer = null;
902
+ var _err = console.error;
903
+ console.error = function () {}; // silence THREE's multi-line GPU error during init
904
+ try {
905
+ renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
906
+ } catch (e) {
907
+ // renderer stays null
908
+ } finally {
909
+ console.error = _err;
910
+ }
911
+
912
+ if (!renderer) {
913
+ // Graceful degradation — branded layout is the fallback.
914
+ canvas.style.display = "none";
915
+ window.__threeApplyMood = function () {};
916
+ // Do NOT start the rAF loop.
917
+ } else {
918
+ renderer.setSize(1920, 1080);
919
+ renderer.setPixelRatio(1);
920
+ canvas.style.cssText =
921
+ "position:fixed;top:0;left:0;width:1920px;height:1080px;z-index:0;pointer-events:none";
922
+
923
+ var scene = new THREE.Scene();
924
+ var camera = new THREE.PerspectiveCamera(60, 1920 / 1080, 0.1, 1000);
925
+ camera.position.set(0, 0, 5);
926
+
927
+ // --- build your particle system, meshes, etc. here ---
928
+
929
+ // Mood config: map sceneId → visual state (colors, sub-object visibility, bg color)
930
+ var MOODS = {
931
+ cover: {
932
+ /* particle color, opacity, bg ... */
933
+ },
934
+ problem: {
935
+ /* ... */
936
+ },
937
+ // one entry per scene key
938
+ };
939
+
940
+ window.__threeApplyMood = function (sceneKey) {
941
+ var m = MOODS[sceneKey] || MOODS["cover"];
942
+ // update geometry attributes, material opacity, sub-group visibility, etc.
943
+ };
944
+ window.__threeApplyMood("cover"); // apply initial state
945
+
946
+ // --- own rAF loop ---
947
+ var lastTime = null;
948
+ function animate(ts) {
949
+ requestAnimationFrame(animate);
950
+ if (lastTime === null) lastTime = ts;
951
+ var dt = Math.min((ts - lastTime) / 1000, 0.05);
952
+ lastTime = ts;
953
+ // update particles, rotate objects, etc.
954
+ renderer.render(scene, camera);
955
+ }
956
+ requestAnimationFrame(animate);
957
+ }
958
+ ```
959
+
960
+ **CSS for transparent scene frames + scrim:**
961
+
962
+ ```css
963
+ /* Three.js canvas — always behind everything */
964
+ #three-canvas {
965
+ position: fixed;
966
+ top: 0;
967
+ left: 0;
968
+ width: 1920px;
969
+ height: 1080px;
970
+ z-index: 0;
971
+ pointer-events: none;
972
+ }
973
+
974
+ /* Scene frames are transparent so the 3D canvas shows through */
975
+ .scene-frame {
976
+ position: absolute;
977
+ top: 0;
978
+ left: 0;
979
+ width: 1920px;
980
+ height: 1080px;
981
+ background: transparent; /* NOT opaque — 3D would be occluded */
982
+ z-index: 1;
983
+ }
984
+
985
+ /* Scrim on the TEXT container — not the scene frame.
986
+ Radial gradient: opaque in the center where text is, transparent at edges
987
+ so 3D shows in the whitespace margins. */
988
+ .slide-inner.scrim-light {
989
+ background: radial-gradient(
990
+ ellipse 75% 80% at 50% 50%,
991
+ rgba(255, 255, 255, 0.88) 30%,
992
+ rgba(255, 255, 255, 0.6) 65%,
993
+ rgba(255, 255, 255, 0) 100%
994
+ );
995
+ }
996
+ ```
997
+
998
+ ---
999
+
1000
+ ## 8. Foot-gun checklist
1001
+
1002
+ | Failure | Symptom | One-line fix |
1003
+ | ----------------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
1004
+ | Island not duplicated in wrapper | Slideshow chrome never renders; no slide counter, no prev/next | Copy the `<script type="application/hyperframes-slideshow+json">` block verbatim into the `<hyperframes-slideshow>` element in demo.html |
1005
+ | Wrapper SFX in the iframe | Click/transition sounds silent | Move SFX Audio elements and unlock logic to demo.html; post `{type:'hf-sfx',name}` from index.html |
1006
+ | No self-clock in composition | All scene frames stacked / wrong slide visible at load | Add the root GSAP timeline (`window.__timelines["root"]`) and the `onUpdate` visibility controller as shown in Section 3 |
1007
+ | Content opacity:0 with no engine | Blank slides — `[data-anim]` elements invisible at rest | Call `updateVisibility(0)` synchronously after defining the controller so the first slide is shown immediately |
1008
+ | Keydown bound to the element without focus | ArrowLeft/Right dead | Add `tabindex="0"` to `<hyperframes-slideshow>` so it can receive keyboard focus |
1009
+ | Opaque scene background occluding Three.js canvas | 3D never visible | Set `background: transparent` on `.scene-frame`; put the visual fill on the text scrim container instead |
1010
+ | WebGL renderer creation spams errors in headless envs | Console noise, rAF loop starts anyway | Silence `console.error` during `new THREE.WebGLRenderer(...)`, restore in `finally`, guard the rAF start on `renderer !== null` |
1011
+ | Branch scene missing from postMessage manifest | Hotspot navigates but slide is blank | Include every scene — main line and branch — in the `scenes` array of the `postTimeline()` message |
1012
+ | Prominent 3D/content in nav-capsule zone | Bright element bleeds behind/beside the nav pill | Keep the bottom-right ~360×140px region clear; add a background-matching gradient overlay on any slide whose 3D mood is bright in that corner |
1013
+ | Custom media visualizer uses its own timer | Canvas/playhead drifts from the actual video or native controls | Drive visual state from media events and `media.currentTime`; do not use an independent `setTimeout` clock |
1014
+ | One `data-wired` flag means two different things | Pre-rendered timeline HTML skips media listener setup | Use separate markers such as `data-timeline-rendered` and `data-media-sync-wired` |
1015
+ | Presenter media events are not bridged | Audience follows slides but not play/pause/seek/mute | Mirror native media events over `BroadcastChannel("hf-slideshow:" + location.pathname)` in standalone wrappers with interactive media |
1016
+ | Remote play is blocked in the audience window | Audience media time jumps but video never plays | Try muted playback first; if `media.play()` rejects, show an audience unlock button and ignore live `timeupdate` chasing until playback succeeds |
1017
+ | Audience muted autoplay publishes back to presenter | Presenter audio starts, then mutes or cuts out | Publish media events only from presenter mode; audience mute is a local browser-autoplay workaround, not shared media state |
1018
+ | Copied media lacks HyperFrames timing | Lint errors on untimed media; preview/render diverge | Add `data-start`, `data-duration`, and `data-has-audio="true"` when audible; avoid `preload="none"` |
1019
+ | Source font CSS variables kept as font-family values | StaticGuard font-family contract errors | Replace with concrete render-safe stacks or add local `@font-face` declarations |
1020
+ | Converted scroll/camera source jumps between slides | Per-slide focal points are correct but manual navigation snaps | Add a standalone navigation-camera transition hook; disable it for measurement, initial load, resize, and render/validation seeks |