dsh-hyperframes 0.3.1 → 0.4.0

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 (375) hide show
  1. package/README.en.md +6 -5
  2. package/README.md +7 -6
  3. package/lib/index.d.ts +1 -1
  4. package/lib/index.js +1 -0
  5. package/package.json +63 -63
  6. package/skills/embedded-captions/SKILL.md +28 -3
  7. package/skills/embedded-captions/references/aesthetic-principles.md +2 -3
  8. package/skills/embedded-captions/references/rail.md +1 -1
  9. package/skills/embedded-captions/scripts/check-occlusion.cjs +4 -1
  10. package/skills/embedded-captions/scripts/check-overflow.cjs +1 -0
  11. package/skills/embedded-captions/scripts/check-rail-climax.cjs +5 -1
  12. package/skills/embedded-captions/scripts/fixtures/heroless/theme.json +11 -0
  13. package/skills/embedded-captions/scripts/hf-cli.cjs +33 -0
  14. package/skills/embedded-captions/scripts/make-theme.cjs +95 -57
  15. package/skills/embedded-captions/scripts/make-theme.test.mjs +131 -0
  16. package/skills/embedded-captions/scripts/matte.cjs +1 -13
  17. package/skills/embedded-captions/scripts/measure-layout.cjs +2 -1
  18. package/skills/embedded-captions/scripts/preview-frames.cjs +39 -11
  19. package/skills/embedded-captions/scripts/preview-frames.test.mjs +24 -0
  20. package/skills/embedded-captions/scripts/render-and-composite.sh +8 -52
  21. package/skills/embedded-captions/scripts/safe-zones.cjs +4 -1
  22. package/skills/embedded-captions/scripts/transcribe.cjs +5 -14
  23. package/skills/embedded-captions/scripts/transcribe.test.mjs +41 -0
  24. package/skills/faceless-explainer/SKILL.md +20 -16
  25. package/skills/faceless-explainer/references/story-design.md +1 -1
  26. package/skills/faceless-explainer/scripts/assemble-index.mjs +6 -4
  27. package/skills/faceless-explainer/scripts/assemble-index.test.mjs +57 -2
  28. package/skills/faceless-explainer/scripts/audio.mjs +2 -0
  29. package/skills/faceless-explainer/scripts/build-frame.mjs +6 -3
  30. package/skills/faceless-explainer/scripts/captions.mjs +7 -4
  31. package/skills/faceless-explainer/scripts/frame-packets.mjs +4 -4
  32. package/skills/faceless-explainer/scripts/lib/bgm-volume.mjs +4 -0
  33. package/skills/faceless-explainer/scripts/lib/captured-fonts.mjs +58 -0
  34. package/skills/{hyperframes-core → faceless-explainer}/scripts/lib/frame-packets-core.mjs +5 -23
  35. package/skills/faceless-explainer/scripts/lib/storyboard.mjs +1 -1
  36. package/skills/faceless-explainer/scripts/lib/transitions.json +3 -3
  37. package/skills/faceless-explainer/scripts/transitions.mjs +1 -0
  38. package/skills/faceless-explainer/scripts/transitions.test.mjs +2 -0
  39. package/skills/faceless-explainer/sub-agents/frame-worker.md +1 -1
  40. package/skills/figma/SKILL.md +2 -0
  41. package/skills/general-video/SKILL.md +13 -11
  42. package/skills/general-video/scripts/frame-packets.mjs +4 -4
  43. package/skills/general-video/scripts/lib/frame-packets-core.mjs +194 -0
  44. package/skills/general-video/sub-agents/frame-worker.md +1 -1
  45. package/skills/hyperframes/SKILL.md +40 -30
  46. package/skills/{hyperframes-core → hyperframes}/references/brief-contract.md +15 -15
  47. package/skills/{hyperframes-core → hyperframes}/references/brief-format.md +8 -8
  48. package/skills/hyperframes/references/capability-menu.md +2 -2
  49. package/skills/{hyperframes-core → hyperframes}/references/frame-worker-core.md +1 -1
  50. package/skills/hyperframes/references/intent-interview.md +16 -16
  51. package/skills/hyperframes/references/plugin-installation.md +43 -0
  52. package/skills/{hyperframes-core → hyperframes}/references/production-loop.md +11 -11
  53. package/skills/hyperframes/references/review-loop.md +37 -0
  54. package/skills/hyperframes/references/route-briefs.md +1 -1
  55. package/skills/hyperframes/references/routes/motion-graphics.md +1 -1
  56. package/skills/{hyperframes-core → hyperframes}/references/script-format.md +3 -3
  57. package/skills/hyperframes/references/skill-lifecycle.md +2 -0
  58. package/skills/{hyperframes-core → hyperframes}/references/storyboard-format.md +3 -34
  59. package/skills/hyperframes/scripts/lib/frame-packets-core.mjs +194 -0
  60. package/skills/hyperframes/scripts/plugin-cli.mjs +88 -0
  61. package/skills/hyperframes/scripts/plugin-cli.test.mjs +117 -0
  62. package/skills/hyperframes-animation/SKILL.md +5 -1
  63. package/skills/hyperframes-animation/adapters/gsap.md +1 -1
  64. package/skills/hyperframes-animation/adapters/lottie.md +15 -2
  65. package/skills/hyperframes-animation/adapters/three.md +14 -0
  66. package/skills/hyperframes-animation/blueprints/titlecard-reveal.md +1 -1
  67. package/skills/hyperframes-animation/references/motion-blur.md +133 -0
  68. package/skills/hyperframes-animation/rules/cursor-click-ripple.md +1 -1
  69. package/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +1 -1
  70. package/skills/hyperframes-animation/rules/hacker-flip-3d.md +1 -1
  71. package/skills/hyperframes-animation/rules/kinetic-beat-slam.md +2 -2
  72. package/skills/hyperframes-animation/rules/motion-blur-streak.md +1 -1
  73. package/skills/hyperframes-animation/rules/vertical-spring-ticker.md +1 -1
  74. package/skills/hyperframes-animation/scripts/package-loader.mjs +47 -16
  75. package/skills/hyperframes-animation/scripts/package-loader.test.mjs +52 -0
  76. package/skills/hyperframes-animation/techniques.md +1 -1
  77. package/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +3 -3
  78. package/skills/hyperframes-animation/transitions/css-push.md +4 -4
  79. package/skills/hyperframes-audio/SKILL.md +30 -22
  80. package/skills/hyperframes-audio/scripts/carve.mjs +6 -3
  81. package/skills/hyperframes-audio/scripts/carve.test.mjs +16 -1
  82. package/skills/hyperframes-cli/SKILL.md +44 -35
  83. package/skills/hyperframes-cli/references/init-and-scaffold.md +8 -6
  84. package/skills/hyperframes-cli/references/lint-validate-inspect.md +3 -3
  85. package/skills/hyperframes-cli/references/preview-render.md +35 -32
  86. package/skills/hyperframes-cli/references/upgrade-info-misc.md +50 -1
  87. package/skills/hyperframes-core/SKILL.md +30 -24
  88. package/skills/hyperframes-core/references/composition-patterns.md +3 -42
  89. package/skills/hyperframes-core/references/creator-editing-recipes.md +131 -7
  90. package/skills/hyperframes-core/references/data-attributes.md +11 -11
  91. package/skills/hyperframes-core/references/determinism-rules.md +2 -19
  92. package/skills/hyperframes-core/references/full-screen-motion.md +0 -9
  93. package/skills/hyperframes-core/references/minimal-composition.md +2 -2
  94. package/skills/hyperframes-core/references/sub-compositions.md +3 -66
  95. package/skills/hyperframes-core/references/tailwind.md +1 -20
  96. package/skills/hyperframes-core/references/tracks-and-clips.md +2 -6
  97. package/skills/hyperframes-core/references/variables-and-media.md +3 -3
  98. package/skills/hyperframes-creative/SKILL.md +2 -0
  99. package/skills/hyperframes-creative/references/house-style.md +15 -15
  100. package/skills/hyperframes-creative/references/story-spine.md +3 -1
  101. package/skills/hyperframes-creative/references/storyboard-recipe.md +57 -0
  102. package/skills/hyperframes-creative/references/typography.md +1 -1
  103. package/skills/hyperframes-creative/scripts/package-loader.mjs +47 -16
  104. package/skills/hyperframes-creative/scripts/package-loader.test.mjs +52 -0
  105. package/skills/hyperframes-keyframes/SKILL.md +4 -2
  106. package/skills/hyperframes-registry/SKILL.md +3 -1
  107. package/skills/hyperframes-registry/references/contributing.md +8 -2
  108. package/skills/hyperframes-registry/references/discovery.md +20 -34
  109. package/skills/hyperframes-studio/SKILL.md +77 -0
  110. package/skills/media-use/SKILL.md +14 -12
  111. package/skills/media-use/audio/references/captions/authoring.md +4 -0
  112. package/skills/media-use/audio/references/remove-background.md +1 -1
  113. package/skills/media-use/audio/references/tts-to-captions.md +11 -2
  114. package/skills/media-use/audio/references/tts.md +84 -6
  115. package/skills/media-use/audio/scripts/audio.mjs +15 -6
  116. package/skills/media-use/audio/scripts/gemini-pipeline.test.mjs +113 -0
  117. package/skills/media-use/audio/scripts/lib/audio-meta.mjs +39 -0
  118. package/skills/media-use/audio/scripts/lib/audio-meta.test.mjs +136 -0
  119. package/skills/media-use/audio/scripts/lib/bgm-volume.mjs +4 -0
  120. package/skills/media-use/audio/scripts/lib/bgm.mjs +2 -6
  121. package/skills/media-use/audio/scripts/lib/gemini-auth.mjs +43 -0
  122. package/skills/media-use/audio/scripts/lib/gemini-auth.py +52 -0
  123. package/skills/media-use/audio/scripts/lib/gemini-auth.test.mjs +86 -0
  124. package/skills/media-use/audio/scripts/lib/gemini-auth_test.py +90 -0
  125. package/skills/media-use/audio/scripts/lib/gemini-tts.mjs +122 -0
  126. package/skills/media-use/audio/scripts/lib/gemini-tts.test.mjs +243 -0
  127. package/skills/media-use/audio/scripts/lib/heygen.mjs +2 -1
  128. package/skills/media-use/audio/scripts/lib/tts.mjs +15 -3
  129. package/skills/media-use/audio/scripts/wait-bgm.mjs +8 -4
  130. package/skills/media-use/audio/scripts/wait-bgm.test.mjs +65 -0
  131. package/skills/media-use/references/audio.md +3 -2
  132. package/skills/media-use/references/grading.md +7 -7
  133. package/skills/media-use/references/media-treatment-recipes.md +1 -1
  134. package/skills/media-use/references/memory.md +2 -2
  135. package/skills/media-use/references/meta.md +2 -2
  136. package/skills/media-use/references/operations.md +15 -13
  137. package/skills/media-use/references/resolve.md +22 -11
  138. package/skills/media-use/references/setup-providers.md +11 -11
  139. package/skills/media-use/scripts/audio-duck.mjs +19 -27
  140. package/skills/media-use/scripts/compatibility.test.mjs +96 -0
  141. package/skills/media-use/scripts/lib/config-lock.mjs +45 -0
  142. package/skills/media-use/scripts/lib/duck.mjs +16 -10
  143. package/skills/media-use/scripts/lib/media-fetch.mjs +85 -0
  144. package/skills/media-use/scripts/lib/media-home.mjs +20 -0
  145. package/skills/media-use/scripts/lib/npx-sync.mjs +28 -2
  146. package/skills/media-use/scripts/lib/prefs-store.mjs +2 -2
  147. package/skills/media-use/scripts/lib/recipe-store.mjs +2 -2
  148. package/skills/media-use/scripts/lib/telemetry.mjs +29 -41
  149. package/skills/media-use/scripts/lib/transcriptCutFade.mjs +21 -0
  150. package/skills/media-use/scripts/prefs.mjs +1 -1
  151. package/skills/media-use/scripts/recipe.mjs +1 -1
  152. package/skills/media-use/scripts/resolve-plugin.test.mjs +62 -0
  153. package/skills/media-use/scripts/resolve.mjs +27 -1267
  154. package/skills/media-use/scripts/transcribe.mjs +39 -25
  155. package/skills/media-use/scripts/transcribe.test.mjs +112 -0
  156. package/skills/media-use/scripts/transcript-cut.mjs +39 -8
  157. package/skills/media-use/scripts/transcript-cut.test.mjs +117 -0
  158. package/skills/motion-graphics/SKILL.md +3 -1
  159. package/skills/motion-graphics/agents/builder.md +1 -0
  160. package/skills/motion-graphics/agents/director.md +2 -1
  161. package/skills/motion-graphics/catalog-map.md +25 -13
  162. package/skills/motion-graphics/categories/charts/module.md +2 -1
  163. package/skills/motion-graphics/categories/lower-thirds/module.md +4 -0
  164. package/skills/motion-graphics/categories/news/module.md +1 -1
  165. package/skills/motion-graphics/references/motion-vocabulary.md +4 -0
  166. package/skills/music-to-video/SKILL.md +8 -6
  167. package/skills/music-to-video/references/montage.md +3 -6
  168. package/skills/music-to-video/references/motion-primitives/3d-card-flip/index.html +10 -111
  169. package/skills/music-to-video/references/motion-primitives/3d-card-flip/scene.html +119 -0
  170. package/skills/music-to-video/references/motion-primitives/bg-flow-field/index.html +11 -284
  171. package/skills/music-to-video/references/motion-primitives/bg-flow-field/scene.html +294 -0
  172. package/skills/music-to-video/references/motion-primitives/binary-decrypt/index.html +10 -76
  173. package/skills/music-to-video/references/motion-primitives/binary-decrypt/scene.html +85 -0
  174. package/skills/music-to-video/references/motion-primitives/blur-resolve/index.html +10 -52
  175. package/skills/music-to-video/references/motion-primitives/blur-resolve/scene.html +60 -0
  176. package/skills/music-to-video/references/motion-primitives/braam-punch/index.html +10 -92
  177. package/skills/music-to-video/references/motion-primitives/braam-punch/scene.html +100 -0
  178. package/skills/music-to-video/references/motion-primitives/chromatic-split/index.html +10 -103
  179. package/skills/music-to-video/references/motion-primitives/chromatic-split/scene.html +111 -0
  180. package/skills/music-to-video/references/motion-primitives/chrome-sweep/index.html +10 -60
  181. package/skills/music-to-video/references/motion-primitives/chrome-sweep/scene.html +68 -0
  182. package/skills/music-to-video/references/motion-primitives/counting-punch/index.html +10 -110
  183. package/skills/music-to-video/references/motion-primitives/counting-punch/scene.html +119 -0
  184. package/skills/music-to-video/references/motion-primitives/crash-zoom-in/index.html +10 -112
  185. package/skills/music-to-video/references/motion-primitives/crash-zoom-in/scene.html +120 -0
  186. package/skills/music-to-video/references/motion-primitives/datamosh-smear/index.html +10 -121
  187. package/skills/music-to-video/references/motion-primitives/datamosh-smear/scene.html +131 -0
  188. package/skills/music-to-video/references/motion-primitives/directional-fill/index.html +10 -90
  189. package/skills/music-to-video/references/motion-primitives/directional-fill/scene.html +103 -0
  190. package/skills/music-to-video/references/motion-primitives/dolly-zoom/index.html +10 -94
  191. package/skills/music-to-video/references/motion-primitives/dolly-zoom/scene.html +102 -0
  192. package/skills/music-to-video/references/motion-primitives/electric-arc/index.html +10 -100
  193. package/skills/music-to-video/references/motion-primitives/electric-arc/scene.html +105 -0
  194. package/skills/music-to-video/references/motion-primitives/flash-cut/index.html +10 -89
  195. package/skills/music-to-video/references/motion-primitives/flash-cut/scene.html +97 -0
  196. package/skills/music-to-video/references/motion-primitives/gooey-metaball/index.html +10 -197
  197. package/skills/music-to-video/references/motion-primitives/gooey-metaball/scene.html +206 -0
  198. package/skills/music-to-video/references/motion-primitives/hard-cut/index.html +10 -74
  199. package/skills/music-to-video/references/motion-primitives/hard-cut/scene.html +82 -0
  200. package/skills/music-to-video/references/motion-primitives/hypercut-whip/index.html +10 -36
  201. package/skills/music-to-video/references/motion-primitives/hypercut-whip/scene.html +44 -0
  202. package/skills/music-to-video/references/motion-primitives/iris-open/index.html +10 -61
  203. package/skills/music-to-video/references/motion-primitives/iris-open/scene.html +69 -0
  204. package/skills/music-to-video/references/motion-primitives/kinetic-letter-in/index.html +10 -55
  205. package/skills/music-to-video/references/motion-primitives/kinetic-letter-in/scene.html +64 -0
  206. package/skills/music-to-video/references/motion-primitives/liquid-morph/index.html +10 -88
  207. package/skills/music-to-video/references/motion-primitives/liquid-morph/scene.html +96 -0
  208. package/skills/music-to-video/references/motion-primitives/mask-reveal/index.html +10 -66
  209. package/skills/music-to-video/references/motion-primitives/mask-reveal/scene.html +74 -0
  210. package/skills/music-to-video/references/motion-primitives/mosaic-pack/index.html +10 -81
  211. package/skills/music-to-video/references/motion-primitives/mosaic-pack/scene.html +89 -0
  212. package/skills/music-to-video/references/motion-primitives/neon-flicker/index.html +10 -65
  213. package/skills/music-to-video/references/motion-primitives/neon-flicker/scene.html +73 -0
  214. package/skills/music-to-video/references/motion-primitives/outline-to-fill/index.html +10 -74
  215. package/skills/music-to-video/references/motion-primitives/outline-to-fill/scene.html +82 -0
  216. package/skills/music-to-video/references/motion-primitives/palette-flip/index.html +10 -89
  217. package/skills/music-to-video/references/motion-primitives/palette-flip/scene.html +97 -0
  218. package/skills/music-to-video/references/motion-primitives/particle-burst/index.html +10 -98
  219. package/skills/music-to-video/references/motion-primitives/particle-burst/scene.html +106 -0
  220. package/skills/music-to-video/references/motion-primitives/pixel-dissolve/index.html +10 -78
  221. package/skills/music-to-video/references/motion-primitives/pixel-dissolve/scene.html +86 -0
  222. package/skills/music-to-video/references/motion-primitives/radial-burst-lines/index.html +10 -117
  223. package/skills/music-to-video/references/motion-primitives/radial-burst-lines/scene.html +130 -0
  224. package/skills/music-to-video/references/motion-primitives/screen-shake/index.html +10 -70
  225. package/skills/music-to-video/references/motion-primitives/screen-shake/scene.html +78 -0
  226. package/skills/music-to-video/references/motion-primitives/slot-machine-reveal/index.html +10 -87
  227. package/skills/music-to-video/references/motion-primitives/slot-machine-reveal/scene.html +100 -0
  228. package/skills/music-to-video/references/motion-primitives/spotlight-sweep/index.html +10 -94
  229. package/skills/music-to-video/references/motion-primitives/spotlight-sweep/scene.html +102 -0
  230. package/skills/music-to-video/references/motion-primitives/staggered-exit/index.html +10 -74
  231. package/skills/music-to-video/references/motion-primitives/staggered-exit/scene.html +82 -0
  232. package/skills/music-to-video/references/motion-primitives/text-spectral-rays/index.html +11 -271
  233. package/skills/music-to-video/references/motion-primitives/text-spectral-rays/scene.html +280 -0
  234. package/skills/music-to-video/references/motion-primitives/text-wave-distort/index.html +10 -92
  235. package/skills/music-to-video/references/motion-primitives/text-wave-distort/scene.html +106 -0
  236. package/skills/music-to-video/references/motion-primitives/tile-mosaic/index.html +10 -101
  237. package/skills/music-to-video/references/motion-primitives/tile-mosaic/scene.html +109 -0
  238. package/skills/music-to-video/references/motion-primitives/typewriter-reveal/index.html +10 -79
  239. package/skills/music-to-video/references/motion-primitives/typewriter-reveal/scene.html +91 -0
  240. package/skills/music-to-video/references/motion-primitives/word-grid-burst/index.html +10 -83
  241. package/skills/music-to-video/references/motion-primitives/word-grid-burst/scene.html +96 -0
  242. package/skills/music-to-video/references/planning.md +2 -2
  243. package/skills/music-to-video/scripts/lib/storyboard.mjs +1 -1
  244. package/skills/music-to-video/sub-agents/frame-worker.md +2 -2
  245. package/skills/pr-to-video/SKILL.md +20 -16
  246. package/skills/pr-to-video/references/code-vocabulary.md +14 -12
  247. package/skills/pr-to-video/references/story-design.md +1 -1
  248. package/skills/pr-to-video/scripts/assemble-index.mjs +6 -4
  249. package/skills/pr-to-video/scripts/assemble-index.test.mjs +57 -2
  250. package/skills/pr-to-video/scripts/audio.mjs +2 -0
  251. package/skills/pr-to-video/scripts/build-frame.mjs +6 -3
  252. package/skills/pr-to-video/scripts/captions.mjs +7 -4
  253. package/skills/pr-to-video/scripts/captions.test.mjs +41 -0
  254. package/skills/pr-to-video/scripts/frame-packets.mjs +4 -4
  255. package/skills/pr-to-video/scripts/lib/bgm-volume.mjs +4 -0
  256. package/skills/pr-to-video/scripts/lib/captured-fonts.mjs +58 -0
  257. package/skills/pr-to-video/scripts/lib/frame-packets-core.mjs +194 -0
  258. package/skills/pr-to-video/scripts/lib/storyboard.mjs +1 -1
  259. package/skills/pr-to-video/scripts/lib/transitions.json +3 -3
  260. package/skills/pr-to-video/scripts/transitions.mjs +7 -5
  261. package/skills/pr-to-video/sub-agents/frame-worker.md +1 -1
  262. package/skills/product-launch-video/SKILL.md +20 -16
  263. package/skills/product-launch-video/references/story-design.md +1 -1
  264. package/skills/product-launch-video/scripts/assemble-index.mjs +6 -4
  265. package/skills/product-launch-video/scripts/assemble-index.test.mjs +57 -2
  266. package/skills/product-launch-video/scripts/audio.mjs +2 -0
  267. package/skills/product-launch-video/scripts/audio.test.mjs +16 -0
  268. package/skills/product-launch-video/scripts/build-frame-subsets.test.mjs +152 -0
  269. package/skills/product-launch-video/scripts/build-frame.mjs +6 -3
  270. package/skills/product-launch-video/scripts/captions.mjs +7 -4
  271. package/skills/product-launch-video/scripts/frame-packets.mjs +4 -4
  272. package/skills/product-launch-video/scripts/lib/bgm-volume.mjs +4 -0
  273. package/skills/product-launch-video/scripts/lib/captured-fonts.mjs +58 -0
  274. package/skills/product-launch-video/scripts/lib/frame-packets-core.mjs +194 -0
  275. package/skills/product-launch-video/scripts/lib/storyboard.mjs +1 -1
  276. package/skills/product-launch-video/scripts/lib/transitions.json +3 -3
  277. package/skills/product-launch-video/scripts/transitions.mjs +7 -5
  278. package/skills/product-launch-video/scripts/transitions.test.mjs +68 -0
  279. package/skills/product-launch-video/sub-agents/frame-worker.md +1 -1
  280. package/skills/remotion-to-hyperframes/SKILL.md +7 -4
  281. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/expected.json +5 -5
  282. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/compositions/title.html +31 -0
  283. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/index.html +10 -25
  284. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/README.md +8 -8
  285. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/expected.json +9 -9
  286. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-1.html +35 -0
  287. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-2.html +35 -0
  288. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-3.html +36 -0
  289. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/index.html +28 -71
  290. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/expected.json +3 -3
  291. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-1.html +52 -0
  292. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-2.html +99 -0
  293. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-3.html +55 -0
  294. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/index.html +13 -212
  295. package/skills/remotion-to-hyperframes/references/api-map.md +16 -16
  296. package/skills/remotion-to-hyperframes/references/limitations.md +7 -7
  297. package/skills/remotion-to-hyperframes/references/lottie.md +5 -7
  298. package/skills/remotion-to-hyperframes/references/media.md +3 -3
  299. package/skills/remotion-to-hyperframes/references/parameters.md +5 -0
  300. package/skills/remotion-to-hyperframes/references/sequencing.md +71 -18
  301. package/skills/slideshow/SKILL.md +4 -1
  302. package/skills/slideshow/references/standalone-harness.md +0 -3
  303. package/skills/talking-head-recut/SKILL.md +10 -1
  304. package/skills/hyperframes-core/references/review-loop.md +0 -41
  305. package/skills/media-use/scripts/lib/adopt.mjs +0 -128
  306. package/skills/media-use/scripts/lib/adopt.test.mjs +0 -95
  307. package/skills/media-use/scripts/lib/bgm-provider.mjs +0 -20
  308. package/skills/media-use/scripts/lib/brand-provider.mjs +0 -59
  309. package/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +0 -108
  310. package/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +0 -85
  311. package/skills/media-use/scripts/lib/cache.mjs +0 -150
  312. package/skills/media-use/scripts/lib/candidates.mjs +0 -91
  313. package/skills/media-use/scripts/lib/candidates.test.mjs +0 -155
  314. package/skills/media-use/scripts/lib/codex-provider.mjs +0 -161
  315. package/skills/media-use/scripts/lib/codex-provider.test.mjs +0 -34
  316. package/skills/media-use/scripts/lib/coverage.test.mjs +0 -117
  317. package/skills/media-use/scripts/lib/cube-build.mjs +0 -181
  318. package/skills/media-use/scripts/lib/cube-build.test.mjs +0 -80
  319. package/skills/media-use/scripts/lib/cube-validate.mjs +0 -198
  320. package/skills/media-use/scripts/lib/cube-validate.test.mjs +0 -125
  321. package/skills/media-use/scripts/lib/cutlist.test.mjs +0 -148
  322. package/skills/media-use/scripts/lib/duck.test.mjs +0 -118
  323. package/skills/media-use/scripts/lib/error-diffusion.test.mjs +0 -102
  324. package/skills/media-use/scripts/lib/freeze.mjs +0 -70
  325. package/skills/media-use/scripts/lib/freeze.test.mjs +0 -46
  326. package/skills/media-use/scripts/lib/grade-analyzer.mjs +0 -264
  327. package/skills/media-use/scripts/lib/grade-analyzer.test.mjs +0 -285
  328. package/skills/media-use/scripts/lib/heygen-cli.mjs +0 -191
  329. package/skills/media-use/scripts/lib/heygen-cli.test.mjs +0 -301
  330. package/skills/media-use/scripts/lib/heygen-search.mjs +0 -45
  331. package/skills/media-use/scripts/lib/heygen-search.test.mjs +0 -49
  332. package/skills/media-use/scripts/lib/heygen-video-provider.mjs +0 -127
  333. package/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +0 -344
  334. package/skills/media-use/scripts/lib/image-provider.mjs +0 -44
  335. package/skills/media-use/scripts/lib/local-models.mjs +0 -318
  336. package/skills/media-use/scripts/lib/local-models.test.mjs +0 -220
  337. package/skills/media-use/scripts/lib/local-run.mjs +0 -78
  338. package/skills/media-use/scripts/lib/local-run.test.mjs +0 -121
  339. package/skills/media-use/scripts/lib/logo-provider.mjs +0 -222
  340. package/skills/media-use/scripts/lib/logo-provider.test.mjs +0 -137
  341. package/skills/media-use/scripts/lib/ltx-video-provider.mjs +0 -94
  342. package/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +0 -316
  343. package/skills/media-use/scripts/lib/lut-preset-provider.mjs +0 -266
  344. package/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +0 -176
  345. package/skills/media-use/scripts/lib/manifest.test.mjs +0 -388
  346. package/skills/media-use/scripts/lib/match.mjs +0 -47
  347. package/skills/media-use/scripts/lib/mflux-provider.mjs +0 -128
  348. package/skills/media-use/scripts/lib/mflux-provider.test.mjs +0 -174
  349. package/skills/media-use/scripts/lib/misses.mjs +0 -49
  350. package/skills/media-use/scripts/lib/misses.test.mjs +0 -78
  351. package/skills/media-use/scripts/lib/npx-sync.test.mjs +0 -50
  352. package/skills/media-use/scripts/lib/parakeet-words.test.mjs +0 -44
  353. package/skills/media-use/scripts/lib/prefs-store.test.mjs +0 -189
  354. package/skills/media-use/scripts/lib/probe.mjs +0 -39
  355. package/skills/media-use/scripts/lib/probe.test.mjs +0 -31
  356. package/skills/media-use/scripts/lib/providers.mjs +0 -5
  357. package/skills/media-use/scripts/lib/recipe-store.test.mjs +0 -367
  358. package/skills/media-use/scripts/lib/registry.mjs +0 -218
  359. package/skills/media-use/scripts/lib/registry.test.mjs +0 -261
  360. package/skills/media-use/scripts/lib/search.mjs +0 -24
  361. package/skills/media-use/scripts/lib/search.test.mjs +0 -32
  362. package/skills/media-use/scripts/lib/sfx-provider.mjs +0 -23
  363. package/skills/media-use/scripts/lib/specs.mjs +0 -132
  364. package/skills/media-use/scripts/lib/specs.test.mjs +0 -157
  365. package/skills/media-use/scripts/lib/stats.mjs +0 -117
  366. package/skills/media-use/scripts/lib/stats.test.mjs +0 -165
  367. package/skills/media-use/scripts/lib/telemetry.test.mjs +0 -373
  368. package/skills/media-use/scripts/lib/tts-local-provider.mjs +0 -92
  369. package/skills/media-use/scripts/lib/tts-local-provider.test.mjs +0 -72
  370. package/skills/media-use/scripts/lib/usage.mjs +0 -57
  371. package/skills/media-use/scripts/lib/usage.test.mjs +0 -54
  372. package/skills/media-use/scripts/lib/voice-provider.mjs +0 -57
  373. package/skills/media-use/scripts/lib/voice-provider.test.mjs +0 -46
  374. package/skills/media-use/scripts/resolve.test.mjs +0 -1120
  375. /package/skills/{hyperframes-core → hyperframes}/references/subagent-dispatch.md +0 -0
@@ -0,0 +1,152 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
3
+ import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import test from "node:test";
8
+
9
+ const workflows = ["product-launch-video", "faceless-explainer", "pr-to-video"];
10
+ const css = `<style>
11
+ /* cyrillic */
12
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 200 800;
13
+ src: url(assets/fonts/aaa.woff2) format('woff2'); unicode-range: U+0400-045F; }
14
+ /* latin */
15
+ @font-face { font-family: 'Manrope'; font-style: normal; font-weight: 200 800;
16
+ src: url("assets/fonts/zzz.woff2") format('woff2'); unicode-range: U+0000-00FF; }
17
+ </style>`;
18
+
19
+ for (const skill of workflows) {
20
+ const scripts = new URL("../../" + skill + "/scripts/", import.meta.url);
21
+ const { brandFontFaces } = await import(new URL("captions.mjs", scripts));
22
+ test(skill + " stages every captured subset and captions preserve the same coverage", () => {
23
+ const project = mkdtempSync(join(tmpdir(), "frame-subsets-"));
24
+ try {
25
+ mkdirSync(join(project, "capture/extracted"), { recursive: true });
26
+ mkdirSync(join(project, "capture/assets/fonts"), { recursive: true });
27
+ writeFileSync(
28
+ join(project, "capture/extracted/tokens.json"),
29
+ JSON.stringify({ fonts: [{ family: "Manrope" }] }),
30
+ );
31
+ writeFileSync(join(project, "capture/extracted/page.html"), css);
32
+ writeFileSync(join(project, "capture/assets/fonts/aaa.woff2"), "cyrillic bytes");
33
+ writeFileSync(join(project, "capture/assets/fonts/zzz.woff2"), "latin bytes");
34
+ for (let pass = 0; pass < 2; pass++) {
35
+ execFileSync(
36
+ process.execPath,
37
+ [
38
+ fileURLToPath(new URL("build-frame.mjs", scripts)),
39
+ "--preset",
40
+ "capsule",
41
+ "--hyperframes",
42
+ project,
43
+ ],
44
+ { stdio: "pipe" },
45
+ );
46
+ const frame = readFileSync(join(project, "frame.md"), "utf8");
47
+ const captions = brandFontFaces(join(project, "frame.md"), project);
48
+ for (const output of [frame, captions]) {
49
+ assert.match(output, /unicode-range:\s*U\+0400-045F/);
50
+ assert.match(output, /unicode-range:\s*U\+0000-00FF/);
51
+ assert.equal((output.match(/font-weight:\s*200 800/g) ?? []).length, 2);
52
+ assert.equal((output.match(/@font-face/g) ?? []).length, 2);
53
+ for (const match of output.matchAll(/url\(["']?(assets\/fonts\/[^"')]+)["']?\)/g)) {
54
+ assert.ok(
55
+ ["cyrillic bytes", "latin bytes"].includes(
56
+ readFileSync(join(project, match[1]), "utf8"),
57
+ ),
58
+ );
59
+ }
60
+ }
61
+ assert.equal(readdirSync(join(project, "assets/fonts")).length, 2);
62
+ }
63
+ } finally {
64
+ rmSync(project, { recursive: true, force: true });
65
+ }
66
+ });
67
+ }
68
+
69
+ test("captured font helper copies stay equal to the owner", () => {
70
+ const owner = readFileSync(new URL("./lib/captured-fonts.mjs", import.meta.url), "utf8");
71
+ for (const skill of workflows.slice(1)) {
72
+ assert.equal(
73
+ readFileSync(
74
+ new URL("../../" + skill + "/scripts/lib/captured-fonts.mjs", import.meta.url),
75
+ "utf8",
76
+ ),
77
+ owner,
78
+ );
79
+ }
80
+ });
81
+
82
+ test("captured rules stage only available local sources for requested families", async () => {
83
+ const { stageCapturedFonts } = await import("./lib/captured-fonts.mjs");
84
+ const project = mkdtempSync(join(tmpdir(), "captured-font-sources-"));
85
+ try {
86
+ mkdirSync(join(project, "capture/extracted"), { recursive: true });
87
+ mkdirSync(join(project, "capture/assets/fonts"), { recursive: true });
88
+ writeFileSync(join(project, "capture/assets/fonts/local.woff2"), "first");
89
+ writeFileSync(
90
+ join(project, "capture/extracted/page.html"),
91
+ `<style>
92
+ @font-face { font-family: Other; src: url(assets/fonts/local.woff2); }
93
+ @font-face { font-family: Manrope; font-weight: 600; unicode-range: U+0000-00FF;
94
+ src: local(Manrope), url(https://example.com/remote.woff2), url(assets/fonts/missing.woff2), url(assets/fonts/local.woff2) format("woff2"); }
95
+ </style>`,
96
+ );
97
+ const first = stageCapturedFonts(project, ["Manrope"]);
98
+ assert.equal(first.faces.length, 1);
99
+ assert.deepEqual([...first.families], ["manrope"]);
100
+ assert.doesNotMatch(first.faces[0], /Other|https:|missing|local\(/);
101
+ assert.match(first.faces[0], /unicode-range: U\+0000-00FF/);
102
+ const [file] = first.files;
103
+ assert.equal(readFileSync(join(project, file), "utf8"), "first");
104
+ writeFileSync(join(project, "capture/assets/fonts/local.woff2"), "refreshed");
105
+ stageCapturedFonts(project, ["Manrope"]);
106
+ assert.equal(readFileSync(join(project, file), "utf8"), "refreshed");
107
+ } finally {
108
+ rmSync(project, { recursive: true, force: true });
109
+ }
110
+ });
111
+
112
+ for (const skill of workflows) {
113
+ test(
114
+ skill + " keeps captured family ownership when downloads are missing or names overlap",
115
+ async () => {
116
+ const { brandFontFaces } = await import(
117
+ new URL("../../" + skill + "/scripts/captions.mjs", import.meta.url)
118
+ );
119
+ const project = mkdtempSync(join(tmpdir(), "font-ownership-"));
120
+ try {
121
+ mkdirSync(join(project, "capture/extracted"), { recursive: true });
122
+ mkdirSync(join(project, "capture/assets/fonts"), { recursive: true });
123
+ writeFileSync(
124
+ join(project, "frame.md"),
125
+ 'typography:\n display: { fontFamily: "TT Norms Pro Mono", weight: 400 }\n body: { fontFamily: "TT Norms Pro", weight: 400 }\n',
126
+ );
127
+ writeFileSync(
128
+ join(project, "capture/assets/fonts/TT_Norms_Pro_Mono_Regular.woff2"),
129
+ "mono",
130
+ );
131
+ writeFileSync(
132
+ join(project, "capture/extracted/page.html"),
133
+ `<style>
134
+ @font-face { font-family: 'TT Norms Pro Mono'; src: url(assets/fonts/TT_Norms_Pro_Mono_Regular.woff2); unicode-range: U+0000-00FF; }
135
+ </style>`,
136
+ );
137
+ const mixed = brandFontFaces(join(project, "frame.md"), project);
138
+ assert.equal((mixed.match(/@font-face/g) ?? []).length, 1);
139
+ assert.doesNotMatch(mixed, /font-family: 'TT Norms Pro';/);
140
+ writeFileSync(
141
+ join(project, "capture/extracted/page.html"),
142
+ `<style>
143
+ @font-face { font-family: 'TT Norms Pro Mono'; src: url(assets/fonts/missing.woff2); }
144
+ </style>`,
145
+ );
146
+ assert.equal(brandFontFaces(join(project, "frame.md"), project), "");
147
+ } finally {
148
+ rmSync(project, { recursive: true, force: true });
149
+ }
150
+ },
151
+ );
152
+ }
@@ -42,6 +42,8 @@ import {
42
42
  UA_DEFAULT_COLORS,
43
43
  } from "./lib/tokens.mjs";
44
44
 
45
+ import { stageCapturedFonts } from "./lib/captured-fonts.mjs";
46
+
45
47
  const __dirname = dirname(fileURLToPath(import.meta.url));
46
48
  const argv = process.argv.slice(2);
47
49
  const flag = (name, def) => {
@@ -479,11 +481,12 @@ if (brandFonts.length) {
479
481
  const famOf = (f) =>
480
482
  fams.length === 1 ? fams[0] : ranked.find((x) => norm(f).includes(norm(x)));
481
483
  const outDir = join(hyperframesDir, "assets/fonts");
482
- const faces = [];
483
- const stagedNames = new Set();
484
+ const captured = stageCapturedFonts(hyperframesDir, fams);
485
+ const faces = [...captured.faces];
486
+ const stagedNames = new Set(captured.files);
484
487
  for (const { d, f } of files) {
485
488
  const fam = famOf(f);
486
- if (!fam) continue;
489
+ if (!fam || captured.families.has(fam.toLowerCase())) continue;
487
490
  const { n, w } = weightInfo(f);
488
491
  const style = styleOf(f);
489
492
  const clean = `${fam.replace(/[^A-Za-z0-9]/g, "")}-${w}${style === "italic" ? "-Italic" : ""}.${extOf(f)}`;
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { stageCapturedFonts } from "./lib/captured-fonts.mjs";
2
3
  // captions.mjs — build the captions sub-composition from STORYBOARD + audio_meta.
3
4
  //
4
5
  // One mode: `build`. Reads STORYBOARD.md (frame order + durations → cumulative
@@ -263,7 +264,7 @@ function buildFromSkin(skin, groups, total, W, H, tokens, die, faces = "", fonts
263
264
  out = fillOnce(
264
265
  out,
265
266
  /var GROUPS = \[\];/,
266
- `var GROUPS = ${JSON.stringify(groups)};`,
267
+ `var GROUPS = ${JSON.stringify(groups).replace(/</g, "\\u003c")};`,
267
268
  "`var GROUPS = [];` hole",
268
269
  );
269
270
  out = fillOnce(out, /var DURATION = 0;/, `var DURATION = ${total};`, "`var DURATION = 0;` hole");
@@ -344,7 +345,8 @@ function brandFontFaces(framePath, hyperframesDir) {
344
345
  // underscore/hyphen-named brand font (e.g. TT Norms Pro), which is exactly the
345
346
  // font_family_without_font_face bug.
346
347
  const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
347
- const faces = [];
348
+ const captured = stageCapturedFonts(hyperframesDir, families);
349
+ const faces = [...captured.faces];
348
350
  const seen = new Set();
349
351
  const claimed = new Set(); // each file is claimed by the MOST SPECIFIC family only
350
352
  // Match the longest family key first so "TT Norms Pro" can't swallow the files that
@@ -363,12 +365,13 @@ function brandFontFaces(framePath, hyperframesDir) {
363
365
  if (!/\.(woff2|woff|ttf|otf)$/i.test(f)) continue;
364
366
  if (claimed.has(f)) continue; // a more specific family already took this file
365
367
  if (!norm(f.replace(/\.(woff2|woff|ttf|otf)$/i, "")).startsWith(key)) continue;
368
+ claimed.add(f);
369
+ if (captured.families.has(fam.toLowerCase())) continue;
366
370
  const w = weightOf(f);
367
371
  const style = styleOf(f);
368
372
  const dedup = `${fam}-${w}-${style}`;
369
373
  if (seen.has(dedup)) continue; // one src per face; assets/fonts wins over capture
370
374
  seen.add(dedup);
371
- claimed.add(f);
372
375
  faces.push(
373
376
  ` @font-face { font-family: '${fam}'; src: url('${d.rel}/${f}') format('${fmtOf(f)}'); font-weight: ${w}; font-style: ${style}; font-display: block; }`,
374
377
  );
@@ -480,7 +483,7 @@ function buildCaptionsHtml(groups, total, W, H) {
480
483
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js" integrity="sha384-sG0Hv1tP1lZCk9KQmrIbY/XNwi+OY84GQqhMscbnsoBFqAz8KNCil1kvfL3Hbbk2" crossorigin="anonymous"></script>
481
484
  <script>
482
485
  (function () {
483
- var GROUPS = ${JSON.stringify(groups)};
486
+ var GROUPS = ${JSON.stringify(groups).replace(/</g, "\\u003c")};
484
487
  var cap = document.getElementById("cap");
485
488
  var tl = gsap.timeline({ paused: true });
486
489
  GROUPS.forEach(function (g) {
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Thin wrapper over the shared packet builder in hyperframes-core — this file only
3
+ // Thin wrapper over the shared packet builder in hyperframes — this file only
4
4
  // pins the paths that are specific to this workflow skill. The logic (frame
5
5
  // splitting, rule citation, packet bounds, `_role.md` assembly) has one owner:
6
- // ../../hyperframes-core/scripts/lib/frame-packets-core.mjs
6
+ // ../../hyperframes/scripts/lib/frame-packets-core.mjs
7
7
 
8
8
  import { dirname, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
- import * as core from "../../hyperframes-core/scripts/lib/frame-packets-core.mjs";
10
+ import * as core from "./lib/frame-packets-core.mjs";
11
11
 
12
12
  const SKILL_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
13
13
  const CONFIG = {
14
14
  animationDir: resolve(SKILL_DIR, "../hyperframes-animation"),
15
- corePath: resolve(SKILL_DIR, "../hyperframes-core/references/frame-worker-core.md"),
15
+ corePath: resolve(SKILL_DIR, "../hyperframes/references/frame-worker-core.md"),
16
16
  deltaPath: resolve(SKILL_DIR, "sub-agents/frame-worker.md"),
17
17
  };
18
18
 
@@ -0,0 +1,4 @@
1
+ // Music sits under narration; a silent film brings music forward.
2
+ export const BGM_BED_VOLUME = 0.12;
3
+ export const BGM_SILENT_VOLUME = 0.9;
4
+ export const bgmDefaultVolume = (hasVoice) => (hasVoice ? BGM_BED_VOLUME : BGM_SILENT_VOLUME);
@@ -0,0 +1,58 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ // Capture preserves subset and variable-axis descriptors in extracted/page.html.
5
+ // Filenames alone cannot identify a face's character coverage.
6
+ export function stageCapturedFonts(projectDir, families) {
7
+ const page = join(projectDir, "capture/extracted/page.html");
8
+ const result = { faces: [], families: new Set(), files: new Set() };
9
+ if (!existsSync(page)) return result;
10
+ const wanted = new Set(families.map((family) => family.toLowerCase()));
11
+ const html = readFileSync(page, "utf8");
12
+ const copied = new Set();
13
+ for (const style of html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)) {
14
+ const css = style[1].replace(/\/\*[\s\S]*?\*\//g, "");
15
+ for (const match of css.matchAll(/@font-face\s*\{([^}]+)\}/gi)) {
16
+ const body = match[1];
17
+ const familyValue = /(?:^|;)\s*font-family\s*:\s*([^;]+)/i.exec(body)?.[1]?.trim();
18
+ if (!familyValue) continue;
19
+ const family = familyValue.replace(/^(['"])(.*)\1$/, "$2");
20
+ if (!wanted.has(family.toLowerCase())) continue;
21
+ result.families.add(family.toLowerCase());
22
+ const source = /(?:^|;)\s*src\s*:\s*([^;]+)/i.exec(body);
23
+ if (!source) continue;
24
+ const sources = localFontSources(source[1], projectDir);
25
+ if (!sources.length) {
26
+ console.warn("Captured font has no downloaded source: " + family);
27
+ continue;
28
+ }
29
+ for (const file of sources) {
30
+ if (copied.has(file.name)) continue;
31
+ if (!copied.size) mkdirSync(join(projectDir, "assets/fonts"), { recursive: true });
32
+ copyFileSync(file.source, join(projectDir, file.target));
33
+ copied.add(file.name);
34
+ result.files.add(file.target);
35
+ }
36
+ const src = sources.map((file) => file.css).join(", ");
37
+ const rule = "@font-face{" + body.replace(source[1], src) + "}";
38
+ if (!result.faces.includes(rule)) result.faces.push(rule);
39
+ }
40
+ }
41
+ return result;
42
+ }
43
+
44
+ function localFontSources(source, projectDir) {
45
+ const files = [];
46
+ for (const match of source.matchAll(
47
+ /url\(\s*(?:"([^"]+)"|'([^']+)'|([^\s)]+))\s*\)(\s*format\([^)]*\))?/gi,
48
+ )) {
49
+ const url = match[1] ?? match[2] ?? match[3];
50
+ const name = /^assets\/fonts\/([a-z0-9_.-]+\.(?:woff2?|ttf|otf))$/i.exec(url)?.[1];
51
+ if (!name) continue;
52
+ const path = join(projectDir, "capture/assets/fonts", name);
53
+ if (!existsSync(path)) continue;
54
+ const target = "assets/fonts/captured-" + name;
55
+ files.push({ name, source: path, target, css: 'url("' + target + '")' + (match[4] ?? "") });
56
+ }
57
+ return files;
58
+ }
@@ -0,0 +1,194 @@
1
+ // Frame packets inline the storyboard frame, blueprint and cited recipes.
2
+ // Workflow wrappers supply resource paths and workflow-specific sections.
3
+ // The role combines the shared worker contract with the workflow delta.
4
+
5
+ import {
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ readdirSync,
10
+ realpathSync,
11
+ writeFileSync,
12
+ } from "node:fs";
13
+ import { basename, join, resolve } from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+
16
+ export function field(block, name) {
17
+ const match = block.match(new RegExp(`^-\\s+${name}:\\s*(.+)$`, "im"));
18
+ return match?.[1]?.trim() ?? null;
19
+ }
20
+
21
+ export function splitFrames(storyboard) {
22
+ const matches = [...storyboard.matchAll(/^## Frame\s+([^\n]+)$/gm)];
23
+ return matches.map((match, index) => {
24
+ const start = match.index;
25
+ const end = matches[index + 1]?.index ?? storyboard.length;
26
+ return {
27
+ heading: match[1].trim(),
28
+ block: storyboard.slice(start, end).trim(),
29
+ };
30
+ });
31
+ }
32
+
33
+ export function frameId(frame) {
34
+ const src = field(frame.block, "src");
35
+ if (!src) throw new Error(`${frame.heading}: missing src`);
36
+ return basename(src).replace(/\.html?$/i, "");
37
+ }
38
+
39
+ export function selectedFile(path, heading) {
40
+ if (!path || !existsSync(path)) return "";
41
+ return `\n## ${heading}\n\n${readFileSync(path, "utf8").trim()}\n`;
42
+ }
43
+
44
+ function escapeRegExp(id) {
45
+ return id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
+ }
47
+
48
+ export function knownRuleIds(animationDir) {
49
+ const rulesDir = join(animationDir, "rules");
50
+ if (!existsSync(rulesDir)) {
51
+ console.warn(
52
+ `frame-packets: no rules dir at ${rulesDir} — packets will inline no motion recipes`,
53
+ );
54
+ return [];
55
+ }
56
+ return readdirSync(rulesDir)
57
+ .filter((name) => name.endsWith(".md"))
58
+ .map((name) => name.replace(/\.md$/, ""));
59
+ }
60
+
61
+ export function citedRules(block, ruleIds) {
62
+ const explicit = (field(block, "rules") ?? "")
63
+ .split(/[,\s]+/)
64
+ .map((rule) => rule.trim())
65
+ .filter(Boolean);
66
+ const mentioned = ruleIds.filter((id) =>
67
+ new RegExp(`(?<![\\w-])${escapeRegExp(id)}(?![\\w-])`, "i").test(block),
68
+ );
69
+ return [...new Set([...explicit, ...mentioned])].filter((id) => ruleIds.includes(id));
70
+ }
71
+
72
+ // visual-design.md tells the author to write the blueprint as `<id> (Reproduce)`
73
+ // or `<id> (Adapt)` — the qualifier is direction for the frame worker, not part of
74
+ // the filename. Parse the field into the id it names (or null for `compose`), so
75
+ // no caller ever resolves a raw field value against the blueprints directory.
76
+ export function blueprintId(block) {
77
+ const raw = field(block, "blueprint");
78
+ if (!raw) return null;
79
+ const id = raw.replace(/\s*\([^)]*\)\s*$/, "").trim();
80
+ return id && id.toLowerCase() !== "compose" ? id : null;
81
+ }
82
+
83
+ export function resourceSections(block, { animationDir, ruleIds, frameId }) {
84
+ let sections = "";
85
+ const blueprint = blueprintId(block);
86
+ if (blueprint) {
87
+ const blueprintsDir = join(animationDir, "blueprints");
88
+ const path = join(blueprintsDir, `${blueprint}.md`);
89
+ // An installed library must contain the named blueprint.
90
+ // An absent on-demand library warns, like missing motion rules.
91
+ if (!existsSync(blueprintsDir)) {
92
+ console.warn(
93
+ `frame-packets: no blueprints dir at ${blueprintsDir} — packets will inline no blueprint`,
94
+ );
95
+ } else if (!existsSync(path)) {
96
+ throw new Error(`${frameId ?? "frame"}: blueprint "${blueprint}" has no file at ${path}`);
97
+ } else {
98
+ sections += selectedFile(path, `Selected blueprint: ${blueprint}`);
99
+ }
100
+ }
101
+ for (const rule of citedRules(block, ruleIds)) {
102
+ sections += selectedFile(
103
+ join(animationDir, "rules", `${rule}.md`),
104
+ `Selected motion rule: ${rule}`,
105
+ );
106
+ }
107
+ return sections;
108
+ }
109
+
110
+ export function buildRolePayload({ corePath, deltaPath, outDir }) {
111
+ const core = readFileSync(corePath, "utf8").trim();
112
+ const delta = readFileSync(deltaPath, "utf8").trim();
113
+ const role = `${core}\n\n---\n\n${delta}\n`;
114
+ mkdirSync(outDir, { recursive: true });
115
+ const path = join(outDir, "_role.md");
116
+ writeFileSync(path, role);
117
+ return { path, bytes: Buffer.byteLength(role) };
118
+ }
119
+
120
+ export function buildFramePackets({
121
+ projectDir,
122
+ storyboardPath = join(projectDir, "STORYBOARD.md"),
123
+ outDir = join(projectDir, ".hyperframes", "frame-packets"),
124
+ maxPacketBytes = 48_000,
125
+ animationDir,
126
+ corePath,
127
+ deltaPath,
128
+ // Per-workflow hooks (all optional):
129
+ // designTruthLine(projectDir) -> the packet's design-truth input line
130
+ // validateFrame(frame, id) -> throw to reject a frame before packing
131
+ // extraSections(block) -> extra packet sections appended after the rule recipes
132
+ designTruthLine = (dir) => `- Design tokens: ${join(resolve(dir), "frame.md")}`,
133
+ validateFrame,
134
+ extraSections,
135
+ }) {
136
+ const storyboard = readFileSync(storyboardPath, "utf8");
137
+ const frames = splitFrames(storyboard);
138
+ if (frames.length === 0) throw new Error("STORYBOARD.md has no frame blocks");
139
+ const ruleIds = knownRuleIds(animationDir);
140
+
141
+ const packets = frames.map((frame) => {
142
+ const id = frameId(frame);
143
+ if (validateFrame) validateFrame(frame, id);
144
+ const packet = `# Frame packet: ${id}\n\n## Project inputs\n\n- Project: ${resolve(projectDir)}\n${designTruthLine(projectDir)}\n- RULES_DIR: ${join(animationDir, "rules")}\n\n## Assigned storyboard block\n\n${frame.block}\n${resourceSections(frame.block, { animationDir, ruleIds, frameId: id })}${extraSections ? extraSections(frame.block) : ""}`;
145
+ const bytes = Buffer.byteLength(packet);
146
+ if (bytes > maxPacketBytes) {
147
+ throw new Error(`${id}: frame packet is ${bytes} bytes (limit ${maxPacketBytes})`);
148
+ }
149
+ return { frameId: id, path: join(outDir, `${id}.md`), bytes, packet };
150
+ });
151
+
152
+ mkdirSync(outDir, { recursive: true });
153
+ for (const { path, packet } of packets) writeFileSync(path, packet);
154
+ buildRolePayload({ corePath, deltaPath, outDir });
155
+ return packets.map(({ packet: _packet, ...result }) => result);
156
+ }
157
+
158
+ export function flag(argv, name, fallback) {
159
+ const index = argv.indexOf(`--${name}`);
160
+ return index >= 0 && argv[index + 1] ? argv[index + 1] : fallback;
161
+ }
162
+
163
+ // realpath both sides: on macOS /tmp → /private/tmp, and node resolves the main
164
+ // module's symlinks in import.meta.url while argv[1] keeps the invoked spelling —
165
+ // a raw compare silently skips main() when invoked through any symlinked path.
166
+ export function isMainModule(importMetaUrl) {
167
+ if (!process.argv[1]) return false;
168
+ try {
169
+ return pathToFileURL(realpathSync(process.argv[1])).href === importMetaUrl;
170
+ } catch {
171
+ return false;
172
+ }
173
+ }
174
+
175
+ export function runCli({ buildFramePackets: build, buildRolePayload: buildRole }) {
176
+ const argv = process.argv.slice(2);
177
+ const projectDir = resolve(flag(argv, "project", "."));
178
+ const outDir = resolve(flag(argv, "out-dir", join(projectDir, ".hyperframes", "frame-packets")));
179
+ try {
180
+ const packets = build({
181
+ projectDir,
182
+ storyboardPath: resolve(flag(argv, "storyboard", join(projectDir, "STORYBOARD.md"))),
183
+ outDir,
184
+ });
185
+ const role = buildRole({ outDir });
186
+ console.log(`✓ frame packets: ${packets.length} bounded packet(s)`);
187
+ for (const packet of packets)
188
+ console.log(` ${packet.frameId}: ${packet.bytes} bytes → ${packet.path}`);
189
+ console.log(` worker role: ${role.bytes} bytes → ${role.path}`);
190
+ } catch (error) {
191
+ console.error(`✗ frame packets: ${error.message}`);
192
+ process.exit(1);
193
+ }
194
+ }
@@ -5,7 +5,7 @@
5
5
  // ship standalone: installed via `npx skills add`, a skill's scripts can't reach
6
6
  // the monorepo's core package, and the core export points at .ts source that
7
7
  // `node` (which runs these scripts) can't load. CANONICAL contract = the core
8
- // parser + skills/hyperframes-core/references/storyboard-format.md; keep this in
8
+ // parser + skills/hyperframes/references/storyboard-format.md; keep this in
9
9
  // lockstep. Behavior: never throws, accepts freeform narrative, recognizes
10
10
  // Frame/Beat/Scene headings at H2/H3, preserves unknown keys verbatim under
11
11
  // `extra` (keys lowercased). Pure node — no deps.
@@ -34,11 +34,11 @@
34
34
  "note": "Directional. The injector picks __DX__/__DY__ from the direction and emits the horizontal OR vertical pair (not both).",
35
35
  "gsap_template_horizontal": [
36
36
  "tl.to(__OLD__, { x: __DX__, duration: __DUR__, ease: \"power3.inOut\" }, __T__);",
37
- "tl.fromTo(__NEW__, { x: __DXIN__, opacity: 1 }, { x: 0, duration: __DUR__, ease: \"power3.inOut\" }, __T__);"
37
+ "tl.fromTo(__NEW__, { x: __DXIN__, opacity: 1 }, { x: 0, opacity: 1, duration: __DUR__, ease: \"power3.inOut\" }, __T__);"
38
38
  ],
39
39
  "gsap_template_vertical": [
40
40
  "tl.to(__OLD__, { y: __DY__, duration: __DUR__, ease: \"power3.inOut\" }, __T__);",
41
- "tl.fromTo(__NEW__, { y: __DYIN__, opacity: 1 }, { y: 0, duration: __DUR__, ease: \"power3.inOut\" }, __T__);"
41
+ "tl.fromTo(__NEW__, { y: __DYIN__, opacity: 1 }, { y: 0, opacity: 1, duration: __DUR__, ease: \"power3.inOut\" }, __T__);"
42
42
  ]
43
43
  },
44
44
  {
@@ -61,7 +61,7 @@
61
61
  "note": "Old compresses to a vertical line on the left edge; new expands from the right edge. Incoming starts off (scaleX 0) so its higher-track stacking is harmless.",
62
62
  "gsap_template": [
63
63
  "tl.to(__OLD__, { scaleX: 0, transformOrigin: \"left center\", duration: __DUR__, ease: \"power3.inOut\" }, __T__);",
64
- "tl.fromTo(__NEW__, { scaleX: 0, transformOrigin: \"right center\", opacity: 1 }, { scaleX: 1, transformOrigin: \"right center\", duration: __DUR__, ease: \"power3.inOut\" }, __T__);"
64
+ "tl.fromTo(__NEW__, { scaleX: 0, transformOrigin: \"right center\", opacity: 1 }, { scaleX: 1, transformOrigin: \"right center\", opacity: 1, duration: __DUR__, ease: \"power3.inOut\" }, __T__);"
65
65
  ]
66
66
  }
67
67
  ],
@@ -117,16 +117,18 @@ function extendFrameTail(hyperframesDir, frame, baseDuration, targetDuration, di
117
117
  let extended = 0;
118
118
  const rewritten = html.replace(/<([A-Za-z][\w:-]*)\b([^>]*)>/g, (tag, name, attrs) => {
119
119
  const durationMatch = attrs.match(/\bdata-duration="([\d.]+)"/);
120
- if (!durationMatch) return tag;
121
- const duration = Number(durationMatch[1]);
122
- if (!Number.isFinite(duration)) return tag;
123
-
124
120
  const compositionMatch = attrs.match(/\bdata-composition-id="([^"]+)"/);
125
121
  if (compositionMatch?.[1] === compId && !foundRoot) {
126
122
  foundRoot = true;
127
- return tag.replace(/\bdata-duration="[\d.]+"/, `data-duration="${targetDuration}"`);
123
+ return durationMatch
124
+ ? tag.replace(/\bdata-duration="[\d.]+"/, `data-duration="${targetDuration}"`)
125
+ : tag.replace(/(\s*\/?>)$/, ` data-duration="${targetDuration}"$1`);
128
126
  }
129
127
 
128
+ if (!durationMatch) return tag;
129
+ const duration = Number(durationMatch[1]);
130
+ if (!Number.isFinite(duration)) return tag;
131
+
130
132
  if (name.toLowerCase() === "audio") return tag;
131
133
  const startMatch = attrs.match(/\bdata-start="([\d.]+)"/);
132
134
  if (!startMatch) return tag;
@@ -0,0 +1,68 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync } from "node:child_process";
3
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import test from "node:test";
7
+
8
+ const workflowScripts = [
9
+ ["product-launch-video", new URL("./transitions.mjs", import.meta.url).pathname],
10
+ ["pr-to-video", new URL("../../pr-to-video/scripts/transitions.mjs", import.meta.url).pathname],
11
+ ];
12
+
13
+ function runInject(script, rootId) {
14
+ const project = mkdtempSync(join(tmpdir(), "hf-transition-root-"));
15
+ const framesDir = join(project, "compositions", "frames");
16
+ mkdirSync(framesDir, { recursive: true });
17
+ writeFileSync(
18
+ join(project, "STORYBOARD.md"),
19
+ `---\nformat: 1920x1080\n---\n\n## Frame 1 — First\n\n- duration: 3s\n- transition_in: cut\n- src: compositions/frames/frame-01.html\n\n## Frame 2 — Second\n\n- duration: 3s\n- transition_in: crossfade 0.5s\n- src: compositions/frames/frame-02.html\n`,
20
+ );
21
+ writeFileSync(
22
+ join(framesDir, "frame-01.html"),
23
+ `<div data-composition-id="${rootId}" data-width="1920" data-height="1080"><div class="clip" data-start="0" data-duration="3"></div></div>`,
24
+ );
25
+ writeFileSync(
26
+ join(framesDir, "frame-02.html"),
27
+ '<div data-composition-id="frame-02" data-duration="3"></div>',
28
+ );
29
+ writeFileSync(
30
+ join(project, "index.html"),
31
+ `<div data-composition-id="main" data-duration="6">
32
+ <div id="el-frame-01" data-start="0" data-duration="3" data-track-index="0"></div>
33
+ <div id="el-frame-02" data-start="3" data-duration="3" data-track-index="0"></div>
34
+ </div><script>window.__timelines = {}; window.__timelines["main"] = gsap.timeline({ paused: true });</script>`,
35
+ );
36
+ const result = spawnSync(
37
+ process.execPath,
38
+ [script, "inject", "--storyboard", join(project, "STORYBOARD.md"), "--hyperframes", project],
39
+ { encoding: "utf8" },
40
+ );
41
+ return {
42
+ project,
43
+ result,
44
+ frameHtml: () => readFileSync(join(framesDir, "frame-01.html"), "utf8"),
45
+ };
46
+ }
47
+
48
+ for (const [workflow, script] of workflowScripts) {
49
+ test(`${workflow} inserts duration on a valid durationless frame root`, () => {
50
+ const fixture = runInject(script, "frame-01");
51
+ try {
52
+ assert.equal(fixture.result.status, 0, fixture.result.stderr);
53
+ assert.match(fixture.frameHtml(), /data-composition-id="frame-01"[^>]*data-duration="3\.5"/);
54
+ } finally {
55
+ rmSync(fixture.project, { recursive: true, force: true });
56
+ }
57
+ });
58
+
59
+ test(`${workflow} still rejects a genuinely mismatched frame root`, () => {
60
+ const fixture = runInject(script, "wrong-root");
61
+ try {
62
+ assert.notEqual(fixture.result.status, 0);
63
+ assert.match(fixture.result.stderr, /has no data-composition-id="frame-01" root/);
64
+ } finally {
65
+ rmSync(fixture.project, { recursive: true, force: true });
66
+ }
67
+ });
68
+ }
@@ -1,6 +1,6 @@
1
1
  # Frame worker — product-launch delta
2
2
 
3
- > The shared law is the core contract above (the packet builder prepends `../hyperframes-core/references/frame-worker-core.md` to this file as `_role.md`) — read the two as one role. This file carries only what's specific to a product-launch frame; you run N-up, **one frame each** — your dispatch carries exactly one packet. Tempted to add a generic GSAP / timeline rule here? Wrong home — it belongs in the core contract or `hyperframes-core`.
3
+ > The shared law is the core contract above (the packet builder prepends `../hyperframes/references/frame-worker-core.md` to this file as `_role.md`) — read the two as one role. This file carries only what's specific to a product-launch frame; you run N-up, **one frame each** — your dispatch carries exactly one packet. Tempted to add a generic GSAP / timeline rule here? Wrong home — it belongs in the core contract or `hyperframes-core`.
4
4
 
5
5
  ## Your `focal:` / `roles:` — real captured media
6
6