@mevdragon/vidfarm-devcli 0.14.0 → 0.15.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 (418) hide show
  1. package/.agents/skills/embedded-captions/CATALOG.md +93 -0
  2. package/.agents/skills/embedded-captions/SKILL.md +286 -0
  3. package/.agents/skills/embedded-captions/assets/brand/CDPR-fankit-terms.txt +35 -0
  4. package/.agents/skills/embedded-captions/assets/brand/cyberpunk-widths.json +115 -0
  5. package/.agents/skills/embedded-captions/assets/fonts/char-widths.json +2211 -0
  6. package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScript1.svg +242 -0
  7. package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScriptMed.svg +242 -0
  8. package/.agents/skills/embedded-captions/dna/README.md +148 -0
  9. package/.agents/skills/embedded-captions/dna/chrome.json +68 -0
  10. package/.agents/skills/embedded-captions/dna/cream.json +69 -0
  11. package/.agents/skills/embedded-captions/dna/documentary.json +62 -0
  12. package/.agents/skills/embedded-captions/dna/editorial.json +70 -0
  13. package/.agents/skills/embedded-captions/dna/glitch.json +82 -0
  14. package/.agents/skills/embedded-captions/dna/ink.json +65 -0
  15. package/.agents/skills/embedded-captions/dna/keynote.json +62 -0
  16. package/.agents/skills/embedded-captions/dna/loud.json +75 -0
  17. package/.agents/skills/embedded-captions/dna/neon.json +58 -0
  18. package/.agents/skills/embedded-captions/dna/velocity.json +89 -0
  19. package/.agents/skills/embedded-captions/modes/cinematic/README.md +48 -0
  20. package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/spec.md +98 -0
  21. package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/template.html +188 -0
  22. package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/spec.md +134 -0
  23. package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/template.html +179 -0
  24. package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/spec.md +81 -0
  25. package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/template.html +170 -0
  26. package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/spec.md +16 -0
  27. package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/template.html +226 -0
  28. package/.agents/skills/embedded-captions/modes/cinematic/engine.html +355 -0
  29. package/.agents/skills/embedded-captions/modes/standard/_anatomy.md +233 -0
  30. package/.agents/skills/embedded-captions/modes/standard/_motion.md +192 -0
  31. package/.agents/skills/embedded-captions/modes/standard/fonts/build-fonts-css.cjs +97 -0
  32. package/.agents/skills/embedded-captions/references/aesthetic-principles.md +168 -0
  33. package/.agents/skills/embedded-captions/references/anti-patterns.md +188 -0
  34. package/.agents/skills/embedded-captions/references/bespoke-vs-presets.md +178 -0
  35. package/.agents/skills/embedded-captions/references/caption-grouping.md +130 -0
  36. package/.agents/skills/embedded-captions/references/composition-craft.md +522 -0
  37. package/.agents/skills/embedded-captions/references/direction-catalog.md +138 -0
  38. package/.agents/skills/embedded-captions/references/example-renders/champion.html +371 -0
  39. package/.agents/skills/embedded-captions/references/example-renders/memory-wall.html +334 -0
  40. package/.agents/skills/embedded-captions/references/failure-modes.md +145 -0
  41. package/.agents/skills/embedded-captions/references/layout-heuristics.md +238 -0
  42. package/.agents/skills/embedded-captions/references/motion-vocabulary.md +148 -0
  43. package/.agents/skills/embedded-captions/references/rail.md +68 -0
  44. package/.agents/skills/embedded-captions/references/reference-bar.md +47 -0
  45. package/.agents/skills/embedded-captions/references/scene-types.md +94 -0
  46. package/.agents/skills/embedded-captions/references/test-set.md +66 -0
  47. package/.agents/skills/embedded-captions/references/typographic-moves.md +266 -0
  48. package/.agents/skills/embedded-captions/references/typography-presets.md +63 -0
  49. package/.agents/skills/embedded-captions/scripts/audio-envelope.cjs +95 -0
  50. package/.agents/skills/embedded-captions/scripts/check-occlusion.cjs +250 -0
  51. package/.agents/skills/embedded-captions/scripts/check-overflow.cjs +194 -0
  52. package/.agents/skills/embedded-captions/scripts/check-rail-climax.cjs +233 -0
  53. package/.agents/skills/embedded-captions/scripts/check-timing.cjs +173 -0
  54. package/.agents/skills/embedded-captions/scripts/fill-timings.cjs +124 -0
  55. package/.agents/skills/embedded-captions/scripts/fit-fonts.cjs +158 -0
  56. package/.agents/skills/embedded-captions/scripts/gen-stroke-path.py +46 -0
  57. package/.agents/skills/embedded-captions/scripts/inject-fonts.cjs +169 -0
  58. package/.agents/skills/embedded-captions/scripts/lib-dna.cjs +201 -0
  59. package/.agents/skills/embedded-captions/scripts/make-cinematic.cjs +1205 -0
  60. package/.agents/skills/embedded-captions/scripts/make-composition.cjs +404 -0
  61. package/.agents/skills/embedded-captions/scripts/make-theme.cjs +8781 -0
  62. package/.agents/skills/embedded-captions/scripts/matte.cjs +294 -0
  63. package/.agents/skills/embedded-captions/scripts/measure-layout.cjs +293 -0
  64. package/.agents/skills/embedded-captions/scripts/prepare.sh +39 -0
  65. package/.agents/skills/embedded-captions/scripts/preview-frames.cjs +268 -0
  66. package/.agents/skills/embedded-captions/scripts/render-and-composite.sh +509 -0
  67. package/.agents/skills/embedded-captions/scripts/render-theme.sh +34 -0
  68. package/.agents/skills/embedded-captions/scripts/safe-zones.cjs +813 -0
  69. package/.agents/skills/embedded-captions/scripts/transcribe.cjs +349 -0
  70. package/.agents/skills/embedded-captions/themes/PORTING.md +49 -0
  71. package/.agents/skills/embedded-captions/themes/README.md +244 -0
  72. package/.agents/skills/embedded-captions/themes/anchor.json +27 -0
  73. package/.agents/skills/embedded-captions/themes/arcade.json +51 -0
  74. package/.agents/skills/embedded-captions/themes/aurora.json +41 -0
  75. package/.agents/skills/embedded-captions/themes/biolume.json +35 -0
  76. package/.agents/skills/embedded-captions/themes/brush.json +74 -0
  77. package/.agents/skills/embedded-captions/themes/chalkboard.json +52 -0
  78. package/.agents/skills/embedded-captions/themes/dossier.json +56 -0
  79. package/.agents/skills/embedded-captions/themes/graffiti.json +62 -0
  80. package/.agents/skills/embedded-captions/themes/hologram.json +41 -0
  81. package/.agents/skills/embedded-captions/themes/inkwater.json +43 -0
  82. package/.agents/skills/embedded-captions/themes/laser.json +46 -0
  83. package/.agents/skills/embedded-captions/themes/lastpage.json +33 -0
  84. package/.agents/skills/embedded-captions/themes/neonsign.json +53 -0
  85. package/.agents/skills/embedded-captions/themes/nightcity.json +56 -0
  86. package/.agents/skills/embedded-captions/themes/ordnance.json +54 -0
  87. package/.agents/skills/embedded-captions/themes/papercut.json +37 -0
  88. package/.agents/skills/embedded-captions/themes/popup.json +46 -0
  89. package/.agents/skills/embedded-captions/themes/ransom.json +62 -0
  90. package/.agents/skills/embedded-captions/themes/scoreboard.json +54 -0
  91. package/.agents/skills/embedded-captions/themes/spectrum.json +46 -0
  92. package/.agents/skills/embedded-captions/themes/stardust.json +29 -0
  93. package/.agents/skills/embedded-captions/themes/stomp.json +43 -0
  94. package/.agents/skills/embedded-captions/themes/terminal.json +49 -0
  95. package/.agents/skills/embedded-captions/themes/thunder.json +44 -0
  96. package/.agents/skills/embedded-captions/themes/transit.json +44 -0
  97. package/.agents/skills/embedded-captions/themes/vhs.json +47 -0
  98. package/.agents/skills/faceless-explainer/SKILL.md +209 -0
  99. package/.agents/skills/faceless-explainer/references/cut-catalog.md +215 -0
  100. package/.agents/skills/faceless-explainer/references/motion-language.md +156 -0
  101. package/.agents/skills/faceless-explainer/references/story-design.md +248 -0
  102. package/.agents/skills/faceless-explainer/references/visual-design.md +146 -0
  103. package/.agents/skills/faceless-explainer/scripts/assemble-index.mjs +570 -0
  104. package/.agents/skills/faceless-explainer/scripts/audio.mjs +260 -0
  105. package/.agents/skills/faceless-explainer/scripts/build-frame.mjs +536 -0
  106. package/.agents/skills/faceless-explainer/scripts/captions.mjs +508 -0
  107. package/.agents/skills/faceless-explainer/scripts/lib/assets.mjs +55 -0
  108. package/.agents/skills/faceless-explainer/scripts/lib/dimensions.mjs +45 -0
  109. package/.agents/skills/faceless-explainer/scripts/lib/storyboard.mjs +249 -0
  110. package/.agents/skills/faceless-explainer/scripts/lib/tokens.mjs +204 -0
  111. package/.agents/skills/faceless-explainer/scripts/lib/transition-registry.mjs +38 -0
  112. package/.agents/skills/faceless-explainer/scripts/lib/transitions.json +71 -0
  113. package/.agents/skills/faceless-explainer/scripts/transitions.mjs +320 -0
  114. package/.agents/skills/faceless-explainer/sub-agents/frame-worker.md +75 -0
  115. package/.agents/skills/flockposter/LICENSE +17 -0
  116. package/.agents/skills/flockposter/README.md +72 -0
  117. package/.agents/skills/flockposter/SKILL.md +562 -0
  118. package/.agents/skills/flockposter/examples/cli-instagram-reel.sh +21 -0
  119. package/.agents/skills/flockposter/examples/cli-multi-platform.json +69 -0
  120. package/.agents/skills/flockposter/examples/cli-tiktok-upload-only.sh +22 -0
  121. package/.agents/skills/flockposter/examples/cli-x-thread.sh +17 -0
  122. package/.agents/skills/flockposter/examples/cli-youtube-short.sh +21 -0
  123. package/.agents/skills/flockposter/examples/public-api-instagram-reel.json +28 -0
  124. package/.agents/skills/general-video/SKILL.md +143 -0
  125. package/.agents/skills/hyperframes/SKILL.md +160 -0
  126. package/.agents/skills/hyperframes-animation/SKILL.md +84 -0
  127. package/.agents/skills/hyperframes-animation/adapters/animate-text.md +64 -0
  128. package/.agents/skills/hyperframes-animation/adapters/animejs.md +114 -0
  129. package/.agents/skills/hyperframes-animation/adapters/css-animations.md +143 -0
  130. package/.agents/skills/hyperframes-animation/adapters/gsap-easing-and-stagger.md +118 -0
  131. package/.agents/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md +96 -0
  132. package/.agents/skills/hyperframes-animation/adapters/gsap-transforms-and-perf.md +96 -0
  133. package/.agents/skills/hyperframes-animation/adapters/gsap.md +105 -0
  134. package/.agents/skills/hyperframes-animation/adapters/html-in-canvas-patterns.md +507 -0
  135. package/.agents/skills/hyperframes-animation/adapters/lottie.md +117 -0
  136. package/.agents/skills/hyperframes-animation/adapters/three.md +131 -0
  137. package/.agents/skills/hyperframes-animation/adapters/typegpu.md +178 -0
  138. package/.agents/skills/hyperframes-animation/adapters/waapi.md +101 -0
  139. package/.agents/skills/hyperframes-animation/blueprints/comparison-split.md +27 -0
  140. package/.agents/skills/hyperframes-animation/blueprints/constellation-hub.md +44 -0
  141. package/.agents/skills/hyperframes-animation/blueprints/cta-morph-press.md +28 -0
  142. package/.agents/skills/hyperframes-animation/blueprints/cursor-ui-demo.md +57 -0
  143. package/.agents/skills/hyperframes-animation/blueprints/dataviz-countup.md +46 -0
  144. package/.agents/skills/hyperframes-animation/blueprints/device-surface-showcase.md +53 -0
  145. package/.agents/skills/hyperframes-animation/blueprints/grid-card-assemble.md +66 -0
  146. package/.agents/skills/hyperframes-animation/blueprints/kinetic-type-beats.md +78 -0
  147. package/.agents/skills/hyperframes-animation/blueprints/logo-assemble-lockup.md +69 -0
  148. package/.agents/skills/hyperframes-animation/blueprints/overwhelm-surround.md +28 -0
  149. package/.agents/skills/hyperframes-animation/blueprints/spatial-pan-stations.md +37 -0
  150. package/.agents/skills/hyperframes-animation/blueprints/ticker-takeover.md +29 -0
  151. package/.agents/skills/hyperframes-animation/blueprints/titlecard-reveal.md +42 -0
  152. package/.agents/skills/hyperframes-animation/blueprints/typewriter-reveal.md +51 -0
  153. package/.agents/skills/hyperframes-animation/blueprints/video-text-pivot.md +30 -0
  154. package/.agents/skills/hyperframes-animation/blueprints-index.md +146 -0
  155. package/.agents/skills/hyperframes-animation/examples/assets/avatars/02.avif +0 -0
  156. package/.agents/skills/hyperframes-animation/examples/assets/brands/github.avif +0 -0
  157. package/.agents/skills/hyperframes-animation/examples/assets/brands/nvidia.avif +0 -0
  158. package/.agents/skills/hyperframes-animation/examples/assets/brands/visa.avif +0 -0
  159. package/.agents/skills/hyperframes-animation/examples/assets/brands/zoominfo.avif +0 -0
  160. package/.agents/skills/hyperframes-animation/examples/brand-reveal-assemble-zoom.html +382 -0
  161. package/.agents/skills/hyperframes-animation/examples/comparison-split-cards.html +649 -0
  162. package/.agents/skills/hyperframes-animation/examples/concept-demo-decode-pan.html +520 -0
  163. package/.agents/skills/hyperframes-animation/examples/cta-morph-press.html +468 -0
  164. package/.agents/skills/hyperframes-animation/examples/cta-orbit-collapse.html +1298 -0
  165. package/.agents/skills/hyperframes-animation/examples/demo-page-scroll-spotlight.html +759 -0
  166. package/.agents/skills/hyperframes-animation/examples/hook-counter-burst.html +729 -0
  167. package/.agents/skills/hyperframes-animation/examples/messaging-multi-phrase.html +352 -0
  168. package/.agents/skills/hyperframes-animation/examples/metric-video-text-pivot.html +779 -0
  169. package/.agents/skills/hyperframes-animation/examples/problem-mockup-overwhelm.html +1374 -0
  170. package/.agents/skills/hyperframes-animation/examples/proof-logo-chain.html +861 -0
  171. package/.agents/skills/hyperframes-animation/examples/takeover-ticker-displace.html +347 -0
  172. package/.agents/skills/hyperframes-animation/examples/workflow-approve-press.html +606 -0
  173. package/.agents/skills/hyperframes-animation/rules/3d-page-scroll.md +227 -0
  174. package/.agents/skills/hyperframes-animation/rules/3d-text-depth-layers.md +297 -0
  175. package/.agents/skills/hyperframes-animation/rules/ai-tracking-box.md +382 -0
  176. package/.agents/skills/hyperframes-animation/rules/ambient-glow-bloom.md +305 -0
  177. package/.agents/skills/hyperframes-animation/rules/asr-keyword-glow.md +286 -0
  178. package/.agents/skills/hyperframes-animation/rules/avatar-cloud-network.md +371 -0
  179. package/.agents/skills/hyperframes-animation/rules/camera-cursor-tracking.md +246 -0
  180. package/.agents/skills/hyperframes-animation/rules/card-morph-anchor.md +267 -0
  181. package/.agents/skills/hyperframes-animation/rules/center-outward-expansion.md +227 -0
  182. package/.agents/skills/hyperframes-animation/rules/context-sensitive-cursor.md +257 -0
  183. package/.agents/skills/hyperframes-animation/rules/coordinate-target-zoom.md +332 -0
  184. package/.agents/skills/hyperframes-animation/rules/counting-dynamic-scale.md +283 -0
  185. package/.agents/skills/hyperframes-animation/rules/css-marker-patterns.md +373 -0
  186. package/.agents/skills/hyperframes-animation/rules/cursor-click-ripple.md +262 -0
  187. package/.agents/skills/hyperframes-animation/rules/depth-of-field-blur.md +313 -0
  188. package/.agents/skills/hyperframes-animation/rules/depth-scatter-assemble.md +303 -0
  189. package/.agents/skills/hyperframes-animation/rules/discrete-text-sequence.md +273 -0
  190. package/.agents/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +307 -0
  191. package/.agents/skills/hyperframes-animation/rules/gsap-effects.md +299 -0
  192. package/.agents/skills/hyperframes-animation/rules/hacker-flip-3d.md +223 -0
  193. package/.agents/skills/hyperframes-animation/rules/kinetic-beat-slam.md +183 -0
  194. package/.agents/skills/hyperframes-animation/rules/motion-blur-streak.md +328 -0
  195. package/.agents/skills/hyperframes-animation/rules/multi-phase-camera.md +273 -0
  196. package/.agents/skills/hyperframes-animation/rules/orbit-3d-entry.md +301 -0
  197. package/.agents/skills/hyperframes-animation/rules/physics-press-reaction.md +350 -0
  198. package/.agents/skills/hyperframes-animation/rules/press-release-spring.md +296 -0
  199. package/.agents/skills/hyperframes-animation/rules/reactive-displacement.md +277 -0
  200. package/.agents/skills/hyperframes-animation/rules/scale-swap-transition.md +298 -0
  201. package/.agents/skills/hyperframes-animation/rules/sine-wave-loop.md +278 -0
  202. package/.agents/skills/hyperframes-animation/rules/split-tilt-cards.md +277 -0
  203. package/.agents/skills/hyperframes-animation/rules/spring-pop-entrance.md +273 -0
  204. package/.agents/skills/hyperframes-animation/rules/stat-bars-and-fills.md +156 -0
  205. package/.agents/skills/hyperframes-animation/rules/svg-icon-enrichment.md +329 -0
  206. package/.agents/skills/hyperframes-animation/rules/svg-path-draw.md +274 -0
  207. package/.agents/skills/hyperframes-animation/rules/vertical-spring-ticker.md +239 -0
  208. package/.agents/skills/hyperframes-animation/rules/viewport-change.md +349 -0
  209. package/.agents/skills/hyperframes-animation/rules-index.md +86 -0
  210. package/.agents/skills/hyperframes-animation/scripts/animation-map.mjs +606 -0
  211. package/.agents/skills/hyperframes-animation/scripts/package-loader.mjs +288 -0
  212. package/.agents/skills/hyperframes-animation/scripts/package-loader.test.mjs +62 -0
  213. package/.agents/skills/hyperframes-animation/techniques.md +507 -0
  214. package/.agents/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +167 -0
  215. package/.agents/skills/hyperframes-animation/transitions/catalog.md +127 -0
  216. package/.agents/skills/hyperframes-animation/transitions/css-3d.md +12 -0
  217. package/.agents/skills/hyperframes-animation/transitions/css-blur.md +51 -0
  218. package/.agents/skills/hyperframes-animation/transitions/css-cover.md +43 -0
  219. package/.agents/skills/hyperframes-animation/transitions/css-destruction.md +95 -0
  220. package/.agents/skills/hyperframes-animation/transitions/css-dissolve.md +66 -0
  221. package/.agents/skills/hyperframes-animation/transitions/css-distortion.md +45 -0
  222. package/.agents/skills/hyperframes-animation/transitions/css-grid.md +10 -0
  223. package/.agents/skills/hyperframes-animation/transitions/css-light.md +49 -0
  224. package/.agents/skills/hyperframes-animation/transitions/css-mechanical.md +30 -0
  225. package/.agents/skills/hyperframes-animation/transitions/css-other.md +25 -0
  226. package/.agents/skills/hyperframes-animation/transitions/css-push.md +41 -0
  227. package/.agents/skills/hyperframes-animation/transitions/css-radial.md +37 -0
  228. package/.agents/skills/hyperframes-animation/transitions/css-scale.md +24 -0
  229. package/.agents/skills/hyperframes-animation/transitions/overview.md +153 -0
  230. package/.agents/skills/hyperframes-cli/SKILL.md +113 -0
  231. package/.agents/skills/hyperframes-cli/references/doctor-browser.md +45 -0
  232. package/.agents/skills/hyperframes-cli/references/init-and-scaffold.md +55 -0
  233. package/.agents/skills/hyperframes-cli/references/lambda.md +132 -0
  234. package/.agents/skills/hyperframes-cli/references/lint-validate-inspect.md +121 -0
  235. package/.agents/skills/hyperframes-cli/references/preview-render.md +149 -0
  236. package/.agents/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
  237. package/.agents/skills/hyperframes-core/SKILL.md +78 -0
  238. package/.agents/skills/hyperframes-core/references/composition-patterns.md +263 -0
  239. package/.agents/skills/hyperframes-core/references/data-attributes.md +70 -0
  240. package/.agents/skills/hyperframes-core/references/determinism-rules.md +68 -0
  241. package/.agents/skills/hyperframes-core/references/full-screen-motion.md +62 -0
  242. package/.agents/skills/hyperframes-core/references/minimal-composition.md +66 -0
  243. package/.agents/skills/hyperframes-core/references/script-format.md +49 -0
  244. package/.agents/skills/hyperframes-core/references/storyboard-format.md +95 -0
  245. package/.agents/skills/hyperframes-core/references/sub-compositions.md +237 -0
  246. package/.agents/skills/hyperframes-core/references/subagent-dispatch.md +41 -0
  247. package/.agents/skills/hyperframes-core/references/tailwind.md +125 -0
  248. package/.agents/skills/hyperframes-core/references/tracks-and-clips.md +76 -0
  249. package/.agents/skills/hyperframes-core/references/variables-and-media.md +90 -0
  250. package/.agents/skills/hyperframes-creative/SKILL.md +70 -0
  251. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/FRAME.md +289 -0
  252. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/caption-skin.html +221 -0
  253. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/frame-showcase.html +1214 -0
  254. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/FRAME.md +275 -0
  255. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/caption-skin.html +224 -0
  256. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/frame-showcase.html +1347 -0
  257. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/FRAME.md +307 -0
  258. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/caption-skin.html +223 -0
  259. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/frame-showcase.html +1372 -0
  260. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/FRAME.md +275 -0
  261. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/caption-skin.html +230 -0
  262. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/frame-showcase.html +1140 -0
  263. package/.agents/skills/hyperframes-creative/frame-presets/broadside/FRAME.md +284 -0
  264. package/.agents/skills/hyperframes-creative/frame-presets/broadside/caption-skin.html +225 -0
  265. package/.agents/skills/hyperframes-creative/frame-presets/broadside/frame-showcase.html +1166 -0
  266. package/.agents/skills/hyperframes-creative/frame-presets/capsule/FRAME.md +284 -0
  267. package/.agents/skills/hyperframes-creative/frame-presets/capsule/caption-skin.html +224 -0
  268. package/.agents/skills/hyperframes-creative/frame-presets/capsule/frame-showcase.html +1465 -0
  269. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/FRAME.md +278 -0
  270. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/caption-skin.html +222 -0
  271. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/frame-showcase.html +1263 -0
  272. package/.agents/skills/hyperframes-creative/frame-presets/claude/FRAME.md +277 -0
  273. package/.agents/skills/hyperframes-creative/frame-presets/claude/caption-skin.html +224 -0
  274. package/.agents/skills/hyperframes-creative/frame-presets/claude/frame-showcase.html +1439 -0
  275. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/FRAME.md +265 -0
  276. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/caption-skin.html +238 -0
  277. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/frame-showcase.html +1468 -0
  278. package/.agents/skills/hyperframes-creative/frame-presets/coral/FRAME.md +314 -0
  279. package/.agents/skills/hyperframes-creative/frame-presets/coral/caption-skin.html +226 -0
  280. package/.agents/skills/hyperframes-creative/frame-presets/coral/frame-showcase.html +1268 -0
  281. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/FRAME.md +333 -0
  282. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/caption-skin.html +227 -0
  283. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/frame-showcase.html +1330 -0
  284. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/FRAME.md +274 -0
  285. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/caption-skin.html +230 -0
  286. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/frame-showcase.html +1444 -0
  287. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/FRAME.md +265 -0
  288. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/caption-skin.html +223 -0
  289. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/frame-showcase.html +1273 -0
  290. package/.agents/skills/hyperframes-creative/palettes/bold-energetic.md +14 -0
  291. package/.agents/skills/hyperframes-creative/palettes/clean-corporate.md +14 -0
  292. package/.agents/skills/hyperframes-creative/palettes/dark-premium.md +14 -0
  293. package/.agents/skills/hyperframes-creative/palettes/jewel-rich.md +14 -0
  294. package/.agents/skills/hyperframes-creative/palettes/monochrome.md +14 -0
  295. package/.agents/skills/hyperframes-creative/palettes/nature-earth.md +14 -0
  296. package/.agents/skills/hyperframes-creative/palettes/neon-electric.md +14 -0
  297. package/.agents/skills/hyperframes-creative/palettes/pastel-soft.md +14 -0
  298. package/.agents/skills/hyperframes-creative/palettes/warm-editorial.md +14 -0
  299. package/.agents/skills/hyperframes-creative/references/audio-reactive.md +76 -0
  300. package/.agents/skills/hyperframes-creative/references/beat-direction.md +164 -0
  301. package/.agents/skills/hyperframes-creative/references/composition-patterns.md +199 -0
  302. package/.agents/skills/hyperframes-creative/references/data-in-motion.md +19 -0
  303. package/.agents/skills/hyperframes-creative/references/design-adherence.md +19 -0
  304. package/.agents/skills/hyperframes-creative/references/design-picker.md +123 -0
  305. package/.agents/skills/hyperframes-creative/references/design-spec.md +55 -0
  306. package/.agents/skills/hyperframes-creative/references/house-style.md +73 -0
  307. package/.agents/skills/hyperframes-creative/references/motion-principles.md +150 -0
  308. package/.agents/skills/hyperframes-creative/references/narration.md +92 -0
  309. package/.agents/skills/hyperframes-creative/references/prompt-expansion.md +68 -0
  310. package/.agents/skills/hyperframes-creative/references/typography.md +218 -0
  311. package/.agents/skills/hyperframes-creative/references/video-composition.md +63 -0
  312. package/.agents/skills/hyperframes-creative/references/visual-styles.md +457 -0
  313. package/.agents/skills/hyperframes-creative/scripts/contrast-report.mjs +353 -0
  314. package/.agents/skills/hyperframes-creative/scripts/extract-audio-data.py +188 -0
  315. package/.agents/skills/hyperframes-creative/scripts/package-loader.mjs +288 -0
  316. package/.agents/skills/hyperframes-creative/scripts/package-loader.test.mjs +62 -0
  317. package/.agents/skills/hyperframes-creative/templates/design-picker.html +1432 -0
  318. package/.agents/skills/hyperframes-keyframes/SKILL.md +237 -0
  319. package/.agents/skills/hyperframes-keyframes/agents/openai.yaml +4 -0
  320. package/.agents/skills/hyperframes-keyframes/references/keyframe-patterns.md +106 -0
  321. package/.agents/skills/motion-graphics/SKILL.md +172 -0
  322. package/.agents/skills/motion-graphics/agents/builder.md +40 -0
  323. package/.agents/skills/motion-graphics/agents/director.md +53 -0
  324. package/.agents/skills/motion-graphics/agents/finalize.md +17 -0
  325. package/.agents/skills/motion-graphics/catalog-map.md +40 -0
  326. package/.agents/skills/motion-graphics/categories/asset-fusion/module.md +37 -0
  327. package/.agents/skills/motion-graphics/categories/charts/module.md +21 -0
  328. package/.agents/skills/motion-graphics/categories/kinetic-type/module.md +19 -0
  329. package/.agents/skills/motion-graphics/categories/logo-reveal/module.md +17 -0
  330. package/.agents/skills/motion-graphics/categories/lower-thirds/module.md +16 -0
  331. package/.agents/skills/motion-graphics/categories/maps/bake-basemap.mjs +273 -0
  332. package/.agents/skills/motion-graphics/categories/maps/module.md +61 -0
  333. package/.agents/skills/motion-graphics/categories/news/module.md +56 -0
  334. package/.agents/skills/motion-graphics/categories/stat/module.md +21 -0
  335. package/.agents/skills/motion-graphics/categories/tweet/module.md +16 -0
  336. package/.agents/skills/motion-graphics/categories/webpage/module.md +21 -0
  337. package/.agents/skills/motion-graphics/grounding/PROTOCOL.md +64 -0
  338. package/.agents/skills/motion-graphics/grounding/locate.mjs +227 -0
  339. package/.agents/skills/motion-graphics/phases/source/guide.md +21 -0
  340. package/.agents/skills/motion-graphics/references/builder-contract.md +38 -0
  341. package/.agents/skills/motion-graphics/references/motion-vocabulary.md +43 -0
  342. package/.agents/skills/motion-graphics/references/shot-plan-ir.md +54 -0
  343. package/.agents/skills/motion-graphics/samples/asset-fusion/_ref-circle-highlight.html +306 -0
  344. package/.agents/skills/product-launch-video/SKILL.md +208 -0
  345. package/.agents/skills/product-launch-video/references/cut-catalog.md +220 -0
  346. package/.agents/skills/product-launch-video/references/motion-language.md +156 -0
  347. package/.agents/skills/product-launch-video/references/story-design.md +373 -0
  348. package/.agents/skills/product-launch-video/references/visual-design.md +126 -0
  349. package/.agents/skills/product-launch-video/scripts/assemble-index.mjs +570 -0
  350. package/.agents/skills/product-launch-video/scripts/audio.mjs +260 -0
  351. package/.agents/skills/product-launch-video/scripts/build-frame.mjs +536 -0
  352. package/.agents/skills/product-launch-video/scripts/captions.mjs +508 -0
  353. package/.agents/skills/product-launch-video/scripts/lib/assets.mjs +55 -0
  354. package/.agents/skills/product-launch-video/scripts/lib/dimensions.mjs +45 -0
  355. package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.mjs +36 -0
  356. package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.test.mjs +76 -0
  357. package/.agents/skills/product-launch-video/scripts/lib/storyboard.mjs +249 -0
  358. package/.agents/skills/product-launch-video/scripts/lib/tokens.mjs +204 -0
  359. package/.agents/skills/product-launch-video/scripts/lib/transition-registry.mjs +38 -0
  360. package/.agents/skills/product-launch-video/scripts/lib/transitions.json +71 -0
  361. package/.agents/skills/product-launch-video/scripts/stage-assets.mjs +39 -0
  362. package/.agents/skills/product-launch-video/scripts/transitions.mjs +327 -0
  363. package/.agents/skills/product-launch-video/sub-agents/frame-worker.md +75 -0
  364. package/.agents/skills/slideshow/SKILL.md +550 -0
  365. package/.agents/skills/slideshow/references/standalone-harness.md +1020 -0
  366. package/.agents/skills/talking-head-recut/NOTICE.md +39 -0
  367. package/.agents/skills/talking-head-recut/SKILL.md +1195 -0
  368. package/.agents/skills/talking-head-recut/assets/vendor/gsap.min.js +10 -0
  369. package/.agents/skills/talking-head-recut/references/DESIGN_INDEX.md +145 -0
  370. package/.agents/skills/talking-head-recut/references/frames/clean.html +92 -0
  371. package/.agents/skills/talking-head-recut/references/frames/hairline.html +171 -0
  372. package/.agents/skills/talking-head-recut/references/frames/polaroid.html +131 -0
  373. package/.agents/skills/talking-head-recut/references/layouts/overlay.html +164 -0
  374. package/.agents/skills/talking-head-recut/references/layouts/pip.html +143 -0
  375. package/.agents/skills/talking-head-recut/references/layouts/split.html +121 -0
  376. package/.agents/skills/talking-head-recut/references/layouts/stack.html +109 -0
  377. package/.agents/skills/talking-head-recut/references/styles/academic.html +172 -0
  378. package/.agents/skills/talking-head-recut/references/styles/audit.html +170 -0
  379. package/.agents/skills/talking-head-recut/references/styles/editorial.html +189 -0
  380. package/.agents/skills/talking-head-recut/references/styles/geom.html +181 -0
  381. package/.agents/skills/talking-head-recut/references/styles/minimal.html +130 -0
  382. package/.agents/skills/talking-head-recut/references/styles/spotlight.html +142 -0
  383. package/.agents/skills/talking-head-recut/references/styles/swiss.html +188 -0
  384. package/.agents/skills/talking-head-recut/references/styles/terminal.html +196 -0
  385. package/.agents/skills/talking-head-recut/references/styles/whiteboard.html +170 -0
  386. package/.agents/skills/talking-head-recut/references/styles/xhs.html +206 -0
  387. package/.agents/skills/vidfarm-media/SKILL.md +85 -0
  388. package/.agents/skills/vidfarm-media/references/tts.md +54 -0
  389. package/.agents/skills/vidfarm-media/scripts/audio.mjs +628 -0
  390. package/.agents/skills/vidfarm-media/scripts/wait-bgm.mjs +31 -0
  391. package/.agents/skills/website-to-video/SKILL.md +145 -0
  392. package/.agents/skills/website-to-video/assets/sfx/CREDITS.md +35 -0
  393. package/.agents/skills/website-to-video/assets/sfx/manifest.json +97 -0
  394. package/.agents/skills/website-to-video/references/beat-builder-guide.md +263 -0
  395. package/.agents/skills/website-to-video/references/capabilities.md +713 -0
  396. package/.agents/skills/website-to-video/references/step-0-capture.md +55 -0
  397. package/.agents/skills/website-to-video/references/step-1-design.md +333 -0
  398. package/.agents/skills/website-to-video/references/step-2-brief.md +178 -0
  399. package/.agents/skills/website-to-video/references/step-3-storyboard.md +572 -0
  400. package/.agents/skills/website-to-video/references/step-4-vo.md +225 -0
  401. package/.agents/skills/website-to-video/references/step-5-build.md +483 -0
  402. package/.agents/skills/website-to-video/references/step-6-validate.md +341 -0
  403. package/.agents/skills/website-to-video/scripts/w2h-verify.mjs +759 -0
  404. package/SKILL.director.md +37 -0
  405. package/SKILL.platform.md +6 -1
  406. package/demo/dist/app.js +295 -11
  407. package/dist/src/app.js +317 -133
  408. package/dist/src/cli.js +529 -12
  409. package/dist/src/composition-runtime.js +262 -3
  410. package/dist/src/devcli/doctor.js +192 -0
  411. package/dist/src/devcli/hyperframes-cli.js +118 -0
  412. package/dist/src/devcli/skills.js +476 -0
  413. package/dist/src/devcli/stills.js +249 -0
  414. package/dist/src/editor-chat.js +255 -0
  415. package/dist/src/hyperframes/composition.js +5 -5
  416. package/dist/src/services/composition-lint.js +199 -0
  417. package/dist/src/services/hyperframes.js +67 -13
  418. package/package.json +2 -1
@@ -0,0 +1,813 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * safe-zones.cjs — PRECOMPUTE where captions can safely go, from the subject matte.
4
+ *
5
+ * node safe-zones.cjs <project-dir> → safe-zones.json (global + per-sentence windows) + summary
6
+ * node safe-zones.cjs <project-dir> <in> <out> → just that time window's zones (ad-hoc query)
7
+ *
8
+ * The inverse of check-occlusion: read the silhouette FIRST and hand the author the
9
+ * clean regions + an embed-vs-fg verdict, so layout is right the first time instead of
10
+ * after N occlusion-failure rounds.
11
+ *
12
+ * Uses the per-pixel ALPHA matte (not a bounding box): a cell counts as subject only
13
+ * where the silhouette actually is, so the empty pocket beside the head / above the
14
+ * shoulders stays FREE (a bbox would wrongly claim it). The subject MOVES, so zones are
15
+ * computed PER TIME WINDOW (the union over just that window's frames), not one global box.
16
+ */
17
+ const path = require("path");
18
+ const fs = require("fs");
19
+ const os = require("os");
20
+
21
+ const THRESH = 30 / 255; // a cell is "subject" if ≥12% covered at any sampled frame in the window
22
+ const SAMPLES = 48; // frames cached across the clip (windows aggregate the cached grids)
23
+
24
+ const HF_ROOTS = [
25
+ process.env.HYPERFRAMES_ROOT,
26
+ path.resolve(__dirname, "../../.."),
27
+ path.join(os.homedir(), "Downloads", "hyperframes"),
28
+ ].filter(Boolean);
29
+ let sharp = null;
30
+ for (const root of HF_ROOTS) {
31
+ const cands = [path.join(root, "node_modules", "sharp")];
32
+ const bunDir = path.join(root, "node_modules", ".bun");
33
+ try {
34
+ if (fs.existsSync(bunDir))
35
+ for (const d of fs.readdirSync(bunDir))
36
+ if (d.startsWith("sharp@")) cands.push(path.join(bunDir, d, "node_modules", "sharp"));
37
+ } catch {}
38
+ for (const c of cands) {
39
+ try {
40
+ if (fs.existsSync(c)) {
41
+ sharp = require(c);
42
+ break;
43
+ }
44
+ } catch {}
45
+ }
46
+ if (sharp) break;
47
+ }
48
+
49
+ // largest all-clear (1) rectangle within [c0,c1)×[r0,r1), in CELL units
50
+ function largestRect(safe, GW, c0, c1, r0, r1) {
51
+ const cols = c1 - c0;
52
+ const heights = new Array(cols).fill(0);
53
+ let best = { area: 0, x: 0, y: 0, w: 0, h: 0 };
54
+ for (let r = r0; r < r1; r++) {
55
+ for (let cc = 0; cc < cols; cc++) heights[cc] = safe[r * GW + (c0 + cc)] ? heights[cc] + 1 : 0;
56
+ const st = [];
57
+ for (let cc = 0; cc <= cols; cc++) {
58
+ const h = cc < cols ? heights[cc] : 0;
59
+ let start = cc;
60
+ while (st.length && st[st.length - 1].h >= h) {
61
+ const top = st.pop();
62
+ const area = top.h * (cc - top.i);
63
+ if (area > best.area)
64
+ best = { area, x: c0 + top.i, y: r - top.h + 1, w: cc - top.i, h: top.h };
65
+ start = top.i;
66
+ }
67
+ st.push({ i: start, h });
68
+ }
69
+ }
70
+ return best;
71
+ }
72
+
73
+ // turn a max-coverage grid into {coverage, subject, zones, recommendation}
74
+ function analyze(occ, GW, GH, W, H, lum, lumSeries) {
75
+ const occCell = new Uint8Array(GW * GH);
76
+ for (let c = 0; c < GW * GH; c++) occCell[c] = occ[c] >= THRESH ? 1 : 0;
77
+ const safe = new Uint8Array(GW * GH); // 1-cell dilation margin around the silhouette
78
+ for (let y = 0; y < GH; y++)
79
+ for (let x = 0; x < GW; x++) {
80
+ let o = 0;
81
+ for (let dy = -1; dy <= 1 && !o; dy++)
82
+ for (let dx = -1; dx <= 1 && !o; dx++) {
83
+ const nx = x + dx,
84
+ ny = y + dy;
85
+ if (nx >= 0 && nx < GW && ny >= 0 && ny < GH && occCell[ny * GW + nx]) o = 1;
86
+ }
87
+ safe[y * GW + x] = o ? 0 : 1;
88
+ }
89
+ const occupied = occCell.reduce((a, b) => a + b, 0);
90
+ const coverage = occupied / (GW * GH);
91
+ let colMin = GW,
92
+ colMax = -1,
93
+ rowMin = GH,
94
+ rowMax = -1;
95
+ for (let x = 0; x < GW; x++)
96
+ for (let y = 0; y < GH; y++)
97
+ if (occCell[y * GW + x]) {
98
+ if (x < colMin) colMin = x;
99
+ if (x > colMax) colMax = x;
100
+ if (y < rowMin) rowMin = y;
101
+ if (y > rowMax) rowMax = y;
102
+ }
103
+ if (colMax < 0) {
104
+ colMin = 0;
105
+ colMax = -1;
106
+ rowMin = 0;
107
+ rowMax = -1;
108
+ }
109
+ const clearerSide = colMin >= GW - 1 - colMax ? "left" : "right";
110
+ const cellW = W / GW,
111
+ cellH = H / GH;
112
+ // HERO anchor — where the ONE big promoted word should sit: ON the subject (centered,
113
+ // crossing it so the head/torso occludes the middle). The OPPOSITE of the clean zones
114
+ // above (those are for narration). A wide band ≈ centered on the subject, vertically
115
+ // crossing the head/upper torso. Only meaningful when there IS a subject.
116
+ let heroAnchor = null;
117
+ if (colMax >= 0) {
118
+ const subjCx = ((colMin + colMax + 1) / 2 / GW) * 100;
119
+ const subjWpct = ((colMax - colMin + 1) / GW) * 100;
120
+ const yTop = (rowMin / GH) * 100,
121
+ yBot = ((rowMax + 1) / GH) * 100;
122
+ const wPct = Math.round(Math.min(92, Math.max(58, subjWpct + 26)));
123
+ const xPct = Math.round(Math.min(98 - wPct, Math.max(2, subjCx - wPct / 2)));
124
+ const yPct = Math.round(yTop + (yBot - yTop) * 0.12); // band crosses the head / upper torso
125
+ let bandLuma = null;
126
+ if (lum) {
127
+ const y0 = Math.round((yPct / 100) * GH),
128
+ y1 = Math.min(GH, y0 + Math.max(2, Math.round(GH * 0.14)));
129
+ const x0 = Math.round((xPct / 100) * GW),
130
+ x1 = Math.min(GW, Math.round(((xPct + wPct) / 100) * GW));
131
+ // luma of the BACKGROUND cells only — that's where the hero's glyphs are visible
132
+ // (the subject-occluded middle doesn't show text; averaging it in hides washout).
133
+ let s2 = 0,
134
+ n = 0;
135
+ for (let y = y0; y < y1; y++)
136
+ for (let x = x0; x < x1; x++) {
137
+ if (occCell[y * GW + x]) continue;
138
+ s2 += lum[y * GW + x];
139
+ n++;
140
+ }
141
+ bandLuma = n ? Math.round(s2 / n) : null;
142
+ }
143
+ heroAnchor = {
144
+ centerXPct: +subjCx.toFixed(1),
145
+ plane: { xPct, yPct, wPct, align: "center" },
146
+ ...(bandLuma != null ? { bandLuma, washoutRisk: bandLuma > 175 } : {}),
147
+ note:
148
+ "Place the ONE big hero here (centered on the subject); the head/torso occludes its middle (~30-55%) — that is the embed. Do NOT put the hero in a clean zone." +
149
+ (bandLuma != null && bandLuma > 175
150
+ ? " ⚠ BAND IS BRIGHT (luma " +
151
+ bandLuma +
152
+ "): cream/screen text will wash out — lower the hero onto the darker subject body, or use a template/mode with opaque text."
153
+ : ""),
154
+ };
155
+ }
156
+ const zoneLuma = (r) => {
157
+ if (!lum || r.area === 0) return null;
158
+ let s2 = 0,
159
+ n = 0;
160
+ for (let y = r.y; y < r.y + r.h; y++)
161
+ for (let x = r.x; x < r.x + r.w; x++) {
162
+ s2 += lum[y * GW + x];
163
+ n++;
164
+ }
165
+ return n ? Math.round(s2 / n) : null;
166
+ };
167
+ // a TIME-AVERAGED map walks a moving minefield: a dark wall swept by a bright
168
+ // moving object (handheld drift, screens) averages "clean" while peaking hot.
169
+ // peakLuma = p95 of the zone's per-sample mean over the window.
170
+ const zoneLumaPeak = (r) => {
171
+ if (!lumSeries || !lumSeries.length || r.area === 0) return null;
172
+ const means = lumSeries
173
+ .map((Lg) => {
174
+ let s2 = 0,
175
+ n = 0;
176
+ for (let y = r.y; y < r.y + r.h; y++)
177
+ for (let x = r.x; x < r.x + r.w; x++) {
178
+ s2 += Lg[y * GW + x];
179
+ n++;
180
+ }
181
+ return n ? s2 / n : 0;
182
+ })
183
+ .sort((a, b) => a - b);
184
+ return Math.round(means[Math.max(0, Math.ceil(means.length * 0.95) - 1)]);
185
+ };
186
+ const toZone = (r) =>
187
+ r.area === 0
188
+ ? null
189
+ : {
190
+ xPct: +((r.x / GW) * 100).toFixed(1),
191
+ yPct: +((r.y / GH) * 100).toFixed(1),
192
+ wPct: +((r.w / GW) * 100).toFixed(1),
193
+ hPct: +((r.h / GH) * 100).toFixed(1),
194
+ areaPct: +(((r.w * r.h) / (GW * GH)) * 100).toFixed(1),
195
+ px: {
196
+ x: Math.round(r.x * cellW),
197
+ y: Math.round(r.y * cellH),
198
+ w: Math.round(r.w * cellW),
199
+ h: Math.round(r.h * cellH),
200
+ },
201
+ ...(zoneLuma(r) != null
202
+ ? {
203
+ meanLuma: zoneLuma(r),
204
+ bright: zoneLuma(r) > 180,
205
+ ...(zoneLumaPeak(r) != null ? { peakLuma: zoneLumaPeak(r) } : {}),
206
+ }
207
+ : {}),
208
+ };
209
+ const zones = {
210
+ largest: toZone(largestRect(safe, GW, 0, GW, 0, GH)),
211
+ left: toZone(largestRect(safe, GW, 0, Math.round(GW / 2), 0, GH)),
212
+ right: toZone(largestRect(safe, GW, Math.round(GW / 2), GW, 0, GH)),
213
+ top: toZone(largestRect(safe, GW, 0, GW, 0, Math.max(2, Math.round(GH * 0.38)))),
214
+ };
215
+ // HUGGING zones — clean strips that ABUT the silhouette (the embed aesthetic wants
216
+ // text NEAR the subject, not parked in the farthest corner). Grown outward from the
217
+ // subject's edge at upper-body height; prefer these for narration.
218
+ const hug = (side) => {
219
+ if (colMax < 0) return null;
220
+ const pad = Math.max(1, Math.round(GW * 0.02));
221
+ const y0 = rowMin,
222
+ y1 = Math.min(GH, rowMin + Math.max(3, Math.round((rowMax - rowMin + 1) * 0.45)));
223
+ let x0, x1;
224
+ if (side === "right") {
225
+ x0 = Math.min(GW - 1, colMax + 1 + pad);
226
+ x1 = GW - pad;
227
+ } else {
228
+ x1 = Math.max(1, colMin - pad);
229
+ x0 = pad;
230
+ }
231
+ if (x1 - x0 < Math.round(GW * 0.1)) return null;
232
+ // shrink until actually clean (≤8% occupied cells)
233
+ let occN = 0,
234
+ tot = 0;
235
+ for (let y = y0; y < y1; y++)
236
+ for (let x = x0; x < x1; x++) {
237
+ tot++;
238
+ if (occCell[y * GW + x]) occN++;
239
+ }
240
+ if (tot === 0 || occN / tot > 0.08) return null;
241
+ const r = { x: x0, y: y0, w: x1 - x0, h: y1 - y0, area: (x1 - x0) * (y1 - y0) };
242
+ const z2 = toZone(r);
243
+ // GLYPHS must hug, not just the plane: in a wide column, text aligned to the far
244
+ // edge parks the words a third of the frame away from the subject. Align TOWARD
245
+ // the silhouette: right-side column → text-align:left (text starts beside the
246
+ // subject); left-side column → text-align:right.
247
+ if (z2) z2.align = side === "right" ? "left" : "right";
248
+ return z2;
249
+ };
250
+ zones.hugLeft = hug("left");
251
+ zones.hugRight = hug("right");
252
+ // HERO BAND PROFILE — per-height predicted occlusion of a centered hero band. The hero
253
+ // WANTS ~30–55% (occlusion IS the embed); fg is the LAST resort, only when no height
254
+ // achieves ≤62%. Even an 88%-coverage frame usually has a feasible band over the hairline.
255
+ let heroBands = null;
256
+ if (colMax >= 0) {
257
+ const bandH = Math.max(2, Math.round(GH * 0.13));
258
+ const hx0 = Math.round(((heroAnchor ? heroAnchor.plane.xPct : 8) / 100) * GW);
259
+ const hx1 = Math.min(
260
+ GW,
261
+ Math.round(((heroAnchor ? heroAnchor.plane.xPct + heroAnchor.plane.wPct : 92) / 100) * GW),
262
+ );
263
+ const profile = [];
264
+ for (let y0 = 0; y0 + bandH <= GH; y0 += Math.max(1, Math.round(GH * 0.02))) {
265
+ let n = 0,
266
+ occN = 0,
267
+ lsum = 0;
268
+ for (let y = y0; y < y0 + bandH; y++)
269
+ for (let x = hx0; x < hx1; x++) {
270
+ n++;
271
+ if (occCell[y * GW + x]) occN++;
272
+ if (lum) lsum += lum[y * GW + x];
273
+ }
274
+ profile.push({
275
+ topPct: +((y0 / GH) * 100).toFixed(1),
276
+ occPct: +((occN / n) * 100).toFixed(1),
277
+ ...(lum ? { bgLuma: Math.round(lsum / n) } : {}),
278
+ });
279
+ }
280
+ const ok = profile.filter((b) => b.occPct >= 12 && b.occPct <= 62);
281
+ const best = (ok.length ? ok : profile).reduce((a, b) =>
282
+ Math.abs(b.occPct - 40) < Math.abs(a.occPct - 40) ? b : a,
283
+ );
284
+ heroBands = { feasible: ok.length > 0, best, profile };
285
+ }
286
+ const big = zones.largest;
287
+ const embeddable = !!big && big.areaPct >= 8 && big.hPct >= 10 && big.wPct >= 18;
288
+ return {
289
+ coverage: +(coverage * 100).toFixed(1),
290
+ subject: {
291
+ colMinPct: +((colMin / GW) * 100).toFixed(1),
292
+ colMaxPct: +(((colMax + 1) / GW) * 100).toFixed(1),
293
+ clearerSide,
294
+ },
295
+ zones,
296
+ heroAnchor,
297
+ heroBands,
298
+ recommendation: embeddable ? "embed" : "fg",
299
+ };
300
+ }
301
+
302
+ // ── SCENE OPTICS + PALETTE (v2) ──────────────────────────────────────────────
303
+ // Deterministic scene measurements that drive the DNA tokens, so "design that fits
304
+ // the scene" is a pipeline product, not agent inspiration:
305
+ // palette — dominant scene colors + a READABLE accent suggestion (sampled, then
306
+ // clamped to usable saturation/lightness) + warm/cool temperature
307
+ // optics — background vs subject sharpness (Laplacian proxy) → suggested text
308
+ // blur so embed type matches the scene's depth-of-field
309
+ // lighting — bright-side estimate → contact-shadow direction for embed type
310
+
311
+ function rgb2hsv(r, g, b) {
312
+ r /= 255;
313
+ g /= 255;
314
+ b /= 255;
315
+ const mx = Math.max(r, g, b),
316
+ mn = Math.min(r, g, b),
317
+ d = mx - mn;
318
+ let h = 0;
319
+ if (d > 0) {
320
+ if (mx === r) h = ((g - b) / d) % 6;
321
+ else if (mx === g) h = (b - r) / d + 2;
322
+ else h = (r - g) / d + 4;
323
+ h *= 60;
324
+ if (h < 0) h += 360;
325
+ }
326
+ return { h, s: mx === 0 ? 0 : d / mx, v: mx };
327
+ }
328
+ function hsv2hex(h, s, v) {
329
+ const c = v * s,
330
+ x = c * (1 - Math.abs(((h / 60) % 2) - 1)),
331
+ m = v - c;
332
+ let [r, g, b] =
333
+ h < 60
334
+ ? [c, x, 0]
335
+ : h < 120
336
+ ? [x, c, 0]
337
+ : h < 180
338
+ ? [0, c, x]
339
+ : h < 240
340
+ ? [0, x, c]
341
+ : h < 300
342
+ ? [x, 0, c]
343
+ : [c, 0, x];
344
+ const f = (n) =>
345
+ Math.round((n + m) * 255)
346
+ .toString(16)
347
+ .padStart(2, "0");
348
+ return `#${f(r)}${f(g)}${f(b)}`;
349
+ }
350
+
351
+ // dominant colors + accent suggestion from the BACKGROUND cells of a mid frame
352
+ async function scenePalette(bgPath, occCell, GW, GH) {
353
+ const { data, info } = await sharp(bgPath)
354
+ .resize(GW, GH, { fit: "fill" })
355
+ .removeAlpha()
356
+ .raw()
357
+ .toBuffer({ resolveWithObject: true });
358
+ const ch = info.channels;
359
+ const cells = [];
360
+ for (let c = 0; c < GW * GH; c++) {
361
+ if (occCell && occCell[c]) continue; // background only
362
+ cells.push([data[c * ch], data[c * ch + 1], data[c * ch + 2]]);
363
+ }
364
+ if (!cells.length) return null;
365
+ // dominant: quantize to 3 bits/channel, top buckets by count
366
+ const buckets = new Map();
367
+ for (const [r, g, b] of cells) {
368
+ const k = ((r >> 5) << 6) | ((g >> 5) << 3) | (b >> 5);
369
+ const e = buckets.get(k) || { n: 0, r: 0, g: 0, b: 0 };
370
+ e.n++;
371
+ e.r += r;
372
+ e.g += g;
373
+ e.b += b;
374
+ buckets.set(k, e);
375
+ }
376
+ const hex = (e) =>
377
+ "#" +
378
+ [e.r, e.g, e.b]
379
+ .map((x) =>
380
+ Math.round(x / e.n)
381
+ .toString(16)
382
+ .padStart(2, "0"),
383
+ )
384
+ .join("");
385
+ const dominant = [...buckets.values()]
386
+ .sort((a, b) => b.n - a.n)
387
+ .slice(0, 3)
388
+ .map((e) => ({ hex: hex(e), sharePct: +((e.n / cells.length) * 100).toFixed(1) }));
389
+ // chromatic accent: hue histogram over saturated cells, weighted s·v
390
+ const bins = Array.from({ length: 12 }, () => ({ w: 0, h: 0, s: 0, v: 0, n: 0 }));
391
+ let warmW = 0,
392
+ coolW = 0;
393
+ for (const [r, g, b] of cells) {
394
+ const { h, s, v } = rgb2hsv(r, g, b);
395
+ if (h <= 90 || h >= 330) warmW += s * v;
396
+ else if (h >= 150 && h <= 300) coolW += s * v;
397
+ if (s < 0.18 || v < 0.12 || v > 0.97) continue;
398
+ const bi = Math.floor(h / 30) % 12,
399
+ w = s * v;
400
+ const B = bins[bi];
401
+ B.w += w;
402
+ B.h += h * w;
403
+ B.s += s * w;
404
+ B.v += v * w;
405
+ B.n++;
406
+ }
407
+ const top = bins.reduce((a, b) => (b.w > a.w ? b : a));
408
+ let accent = null;
409
+ if (top.w > 0.5 && top.n >= 3) {
410
+ const h = top.h / top.w,
411
+ s = top.s / top.w,
412
+ v = top.v / top.w;
413
+ // clamp to a readable accent: saturated enough to read as a choice, light enough to sit on video
414
+ accent = hsv2hex(
415
+ h,
416
+ Math.min(0.78, Math.max(0.5, s * 1.2)),
417
+ Math.min(0.8, Math.max(0.55, v * 1.15)),
418
+ );
419
+ }
420
+ const temperature = warmW > coolW * 1.25 ? "warm" : coolW > warmW * 1.25 ? "cool" : "neutral";
421
+ return { dominant, accentSuggestion: accent, temperature };
422
+ }
423
+
424
+ // Laplacian-stdev sharpness proxy of a region crop (full-res)
425
+ async function regionSharpness(imgPath, rect, W, H) {
426
+ const x = Math.max(0, Math.min(W - 2, Math.round(rect.x))),
427
+ y = Math.max(0, Math.min(H - 2, Math.round(rect.y)));
428
+ const w = Math.max(2, Math.min(W - x, Math.round(rect.w))),
429
+ h = Math.max(2, Math.min(H - y, Math.round(rect.h)));
430
+ // two passes: crop to a buffer FIRST, then convolve+stats on the crop — sharp's
431
+ // internal pipeline ordering otherwise convolves/stats the full frame and the two
432
+ // regions measure identical.
433
+ const crop = await sharp(imgPath)
434
+ .extract({ left: x, top: y, width: w, height: h })
435
+ .png()
436
+ .toBuffer();
437
+ const st = await sharp(crop)
438
+ .greyscale()
439
+ .convolve({ width: 3, height: 3, kernel: [0, 1, 0, 1, -4, 1, 0, 1, 0], scale: 1, offset: 128 })
440
+ .stats();
441
+ return st.channels[0].stdev;
442
+ }
443
+
444
+ async function sceneOptics(project, bgPath, fgPath, zones, subjectBox, W, H) {
445
+ let bgSharp = null,
446
+ subjSharp = null;
447
+ const bigZone = zones && zones.largest && zones.largest.px;
448
+ try {
449
+ if (bigZone && bigZone.w >= 64 && bigZone.h >= 64)
450
+ bgSharp = await regionSharpness(bgPath, bigZone, W, H);
451
+ } catch {}
452
+ try {
453
+ if (subjectBox && subjectBox.w >= 64)
454
+ subjSharp = await regionSharpness(bgPath, subjectBox, W, H);
455
+ } catch {}
456
+ let suggestedTextBlurPx = 0,
457
+ ratio = null;
458
+ if (bgSharp != null && subjSharp != null && subjSharp > 1) {
459
+ ratio = +(bgSharp / subjSharp).toFixed(3);
460
+ // strong bokeh → text in that depth plane should soften to match
461
+ suggestedTextBlurPx = ratio < 0.35 ? 1.6 : ratio < 0.55 ? 1.0 : ratio < 0.8 ? 0.5 : 0;
462
+ }
463
+ return {
464
+ bgSharpness: bgSharp != null ? +bgSharp.toFixed(2) : null,
465
+ subjSharpness: subjSharp != null ? +subjSharp.toFixed(2) : null,
466
+ sharpnessRatio: ratio,
467
+ suggestedTextBlurPx,
468
+ };
469
+ }
470
+
471
+ // bright-side estimate from the luminance grid → contact-shadow vector (shadow falls AWAY from light)
472
+ function sceneLighting(lum, occCell, GW, GH) {
473
+ if (!lum) return null;
474
+ let sw = 0,
475
+ sx = 0,
476
+ sy = 0,
477
+ n = 0,
478
+ mean = 0;
479
+ for (let c = 0; c < GW * GH; c++) {
480
+ if (!occCell[c]) {
481
+ mean += lum[c];
482
+ n++;
483
+ }
484
+ }
485
+ if (!n) return null;
486
+ mean /= n;
487
+ for (let y = 0; y < GH; y++)
488
+ for (let x = 0; x < GW; x++) {
489
+ const c = y * GW + x;
490
+ if (occCell[c]) continue;
491
+ const w = Math.max(0, lum[c] - mean);
492
+ sw += w;
493
+ sx += w * (x / GW - 0.5);
494
+ sy += w * (y / GH - 0.5);
495
+ }
496
+ if (sw < 1) return { lightFrom: "flat", shadow: { dx: 0, dy: 3 } };
497
+ const lx = sx / sw,
498
+ ly = sy / sw; // light centroid offset from center, −0.5..0.5
499
+ const mag = Math.hypot(lx, ly);
500
+ if (mag < 0.04) return { lightFrom: "frontal", shadow: { dx: 0, dy: 3 } };
501
+ // shadow direction = opposite the light, scaled to a subtle px offset
502
+ const s = Math.min(1, mag / 0.25);
503
+ const dx = Math.round((-lx / mag) * 4 * s),
504
+ dy = Math.round(Math.max(1, (-ly / mag) * 4 * s + 2));
505
+ const compass =
506
+ Math.abs(lx) > Math.abs(ly) * 1.8
507
+ ? lx > 0
508
+ ? "right"
509
+ : "left"
510
+ : Math.abs(ly) > Math.abs(lx) * 1.8
511
+ ? ly > 0
512
+ ? "below"
513
+ : "above"
514
+ : `${ly > 0 ? "lower" : "upper"}-${lx > 0 ? "right" : "left"}`;
515
+ return { lightFrom: compass, shadow: { dx, dy } };
516
+ }
517
+
518
+ // split the transcript into sentence windows (punctuation, or a > 0.7s gap)
519
+ function sentenceWindows(project) {
520
+ const tp = path.join(project, "transcript.json");
521
+ if (!fs.existsSync(tp)) return [];
522
+ let words;
523
+ try {
524
+ words = (JSON.parse(fs.readFileSync(tp, "utf8")).words || []).filter((w) => w && "start" in w);
525
+ } catch {
526
+ return [];
527
+ }
528
+ const out = [];
529
+ let cur = [];
530
+ for (let i = 0; i < words.length; i++) {
531
+ cur.push(words[i]);
532
+ const w = words[i],
533
+ nx = words[i + 1];
534
+ const ends = /[.!?…]$/.test((w.text || "").trim());
535
+ const gap = nx ? nx.start - w.end > 0.7 : true;
536
+ if (ends || gap || !nx) {
537
+ if (cur.length)
538
+ out.push({
539
+ in: +cur[0].start.toFixed(2),
540
+ out: +cur[cur.length - 1].end.toFixed(2),
541
+ text: cur.map((x) => x.text).join(" "),
542
+ });
543
+ cur = [];
544
+ }
545
+ }
546
+ return out;
547
+ }
548
+
549
+ async function main() {
550
+ const project = path.resolve(process.argv[2] || "");
551
+ if (!process.argv[2]) {
552
+ console.error("usage: safe-zones.cjs <project-dir> [in out]");
553
+ process.exit(1);
554
+ }
555
+ const fgDir = path.join(project, "frames_fg");
556
+ if (!fs.existsSync(fgDir)) {
557
+ console.error(`[safe-zones] no ${fgDir} — run matte.cjs first`);
558
+ process.exit(2);
559
+ }
560
+ if (!sharp) {
561
+ console.error("[safe-zones] sharp unavailable — set HYPERFRAMES_ROOT");
562
+ process.exit(0);
563
+ }
564
+
565
+ const frames = fs
566
+ .readdirSync(fgDir)
567
+ .filter((f) => /\.png$/i.test(f))
568
+ .sort();
569
+ if (!frames.length) {
570
+ console.error("[safe-zones] no PNG frames");
571
+ process.exit(2);
572
+ }
573
+ const meta = await sharp(path.join(fgDir, frames[0])).metadata();
574
+ const W = meta.width,
575
+ H = meta.height;
576
+ const CELL = Math.max(W, H) / 48;
577
+ const GW = Math.max(8, Math.round(W / CELL)),
578
+ GH = Math.max(8, Math.round(H / CELL));
579
+ let fps = 24;
580
+ try {
581
+ const f = parseFloat(
582
+ String(fs.readFileSync(path.join(project, "matte.fps"), "utf8")).replace(/[^\d.]/g, ""),
583
+ );
584
+ if (f > 0) fps = f;
585
+ } catch {}
586
+
587
+ const bgDir = path.join(project, "frames_bg");
588
+ const hasBg = fs.existsSync(bgDir);
589
+ // cache evenly-sampled frame grids once (each = per-cell avg subject alpha 0..1,
590
+ // plus per-cell mean LUMINANCE from frames_bg — bright zones wash out cream/screen text)
591
+ const sampleIdx = [
592
+ ...new Set(
593
+ Array.from({ length: SAMPLES }, (_, i) =>
594
+ Math.min(frames.length - 1, Math.round((i / (SAMPLES - 1)) * (frames.length - 1))),
595
+ ),
596
+ ),
597
+ ];
598
+ const grids = [];
599
+ for (const i of sampleIdx) {
600
+ const { data, info } = await sharp(path.join(fgDir, frames[i]))
601
+ .resize(GW, GH, { fit: "fill" })
602
+ .raw()
603
+ .toBuffer({ resolveWithObject: true });
604
+ const ch = info.channels,
605
+ g = new Float32Array(GW * GH);
606
+ for (let c = 0; c < GW * GH; c++) g[c] = (ch >= 4 ? data[c * ch + 3] : 255) / 255;
607
+ let lum = null;
608
+ if (hasBg && fs.existsSync(path.join(bgDir, frames[i]))) {
609
+ const { data: bd, info: bi } = await sharp(path.join(bgDir, frames[i]))
610
+ .resize(GW, GH, { fit: "fill" })
611
+ .greyscale()
612
+ .raw()
613
+ .toBuffer({ resolveWithObject: true });
614
+ const bch = bi.channels;
615
+ lum = new Float32Array(GW * GH);
616
+ for (let c = 0; c < GW * GH; c++) lum[c] = bd[c * bch];
617
+ }
618
+ grids.push({ t: i / fps, g, lum });
619
+ }
620
+ const lumWindow = (t0, t1) => {
621
+ const acc = new Float32Array(GW * GH);
622
+ let n = 0;
623
+ let inWin = grids.filter((s2) => s2.t >= t0 - 1e-6 && s2.t <= t1 + 1e-6 && s2.lum);
624
+ if (!inWin.length) inWin = grids.filter((s2) => s2.lum);
625
+ for (const s2 of inWin) {
626
+ for (let c = 0; c < GW * GH; c++) acc[c] += s2.lum[c];
627
+ n++;
628
+ }
629
+ if (!n) return null;
630
+ for (let c = 0; c < GW * GH; c++) acc[c] /= n;
631
+ return acc;
632
+ };
633
+ const lumSeriesWindow = (t0, t1) => {
634
+ let inWin = grids.filter((s2) => s2.t >= t0 - 1e-6 && s2.t <= t1 + 1e-6 && s2.lum);
635
+ if (!inWin.length) inWin = grids.filter((s2) => s2.lum);
636
+ return inWin.map((s2) => s2.lum);
637
+ };
638
+ const occWindow = (t0, t1) => {
639
+ const occ = new Float32Array(GW * GH);
640
+ let inWin = grids.filter((s) => s.t >= t0 - 1e-6 && s.t <= t1 + 1e-6);
641
+ if (!inWin.length) {
642
+ // window between samples → use nearest grid
643
+ const mid = (t0 + t1) / 2;
644
+ inWin = [grids.reduce((a, b) => (Math.abs(b.t - mid) < Math.abs(a.t - mid) ? b : a))];
645
+ }
646
+ for (const s of inWin) for (let c = 0; c < GW * GH; c++) if (s.g[c] > occ[c]) occ[c] = s.g[c];
647
+ return occ;
648
+ };
649
+
650
+ // ad-hoc window query
651
+ const qIn = parseFloat(process.argv[3]),
652
+ qOut = parseFloat(process.argv[4]);
653
+ if (Number.isFinite(qIn) && Number.isFinite(qOut)) {
654
+ const a = analyze(
655
+ occWindow(qIn, qOut),
656
+ GW,
657
+ GH,
658
+ W,
659
+ H,
660
+ lumWindow(qIn, qOut),
661
+ lumSeriesWindow(qIn, qOut),
662
+ );
663
+ console.log(
664
+ `[safe-zones] window ${qIn}-${qOut}s: ${a.recommendation.toUpperCase()} coverage ${a.coverage}% clearer:${a.subject.clearerSide}`,
665
+ );
666
+ const z = a.zones;
667
+ for (const k of ["largest", "left", "right", "top"])
668
+ if (z[k]) console.log(` ${k}: ${z[k].wPct}%×${z[k].hPct}% @ (${z[k].xPct}%,${z[k].yPct}%)`);
669
+ console.log(JSON.stringify({ in: qIn, out: qOut, ...a }));
670
+ return;
671
+ }
672
+
673
+ const globalOcc = occWindow(-1e9, 1e9);
674
+ const globalLum = lumWindow(-1e9, 1e9);
675
+ const global = analyze(globalOcc, GW, GH, W, H, globalLum, lumSeriesWindow(-1e9, 1e9));
676
+ const windows = sentenceWindows(project).map((s) => {
677
+ const a = analyze(
678
+ occWindow(s.in, s.out),
679
+ GW,
680
+ GH,
681
+ W,
682
+ H,
683
+ lumWindow(s.in, s.out),
684
+ lumSeriesWindow(s.in, s.out),
685
+ );
686
+ return {
687
+ in: s.in,
688
+ out: s.out,
689
+ text: s.text.slice(0, 48),
690
+ coverage: a.coverage,
691
+ recommendation: a.recommendation,
692
+ clearerSide: a.subject.clearerSide,
693
+ zones: a.zones,
694
+ };
695
+ });
696
+
697
+ // ── v2: palette / optics / lighting from the mid frame + global grids ───────
698
+ let palette = null,
699
+ optics = null,
700
+ lighting = null;
701
+ try {
702
+ const occCellG = new Uint8Array(GW * GH);
703
+ for (let c = 0; c < GW * GH; c++) occCellG[c] = globalOcc[c] >= THRESH ? 1 : 0;
704
+ const midName = frames[Math.floor(frames.length / 2)];
705
+ const midBg = path.join(bgDir, midName);
706
+ if (hasBg && fs.existsSync(midBg)) {
707
+ palette = await scenePalette(midBg, occCellG, GW, GH);
708
+ // subject bbox in px from the global occupancy grid
709
+ let cx0 = GW,
710
+ cx1 = -1,
711
+ cy0 = GH,
712
+ cy1 = -1;
713
+ for (let y = 0; y < GH; y++)
714
+ for (let x = 0; x < GW; x++)
715
+ if (occCellG[y * GW + x]) {
716
+ if (x < cx0) cx0 = x;
717
+ if (x > cx1) cx1 = x;
718
+ if (y < cy0) cy0 = y;
719
+ if (y > cy1) cy1 = y;
720
+ }
721
+ const subjectBox =
722
+ cx1 >= 0
723
+ ? {
724
+ x: (cx0 / GW) * W,
725
+ y: (cy0 / GH) * H,
726
+ w: ((cx1 - cx0 + 1) / GW) * W,
727
+ h: ((cy1 - cy0 + 1) / GH) * H,
728
+ }
729
+ : null;
730
+ optics = await sceneOptics(
731
+ project,
732
+ midBg,
733
+ path.join(fgDir, midName),
734
+ global.zones,
735
+ subjectBox,
736
+ W,
737
+ H,
738
+ );
739
+ lighting = sceneLighting(globalLum, occCellG, GW, GH);
740
+ }
741
+ } catch (e) {
742
+ console.error(`[safe-zones] scene optics skipped — ${e.message}`);
743
+ }
744
+
745
+ const out = {
746
+ width: W,
747
+ height: H,
748
+ fps,
749
+ grid: { cols: GW, rows: GH },
750
+ ...global,
751
+ palette,
752
+ optics,
753
+ lighting,
754
+ windows,
755
+ };
756
+ fs.writeFileSync(path.join(project, "safe-zones.json"), JSON.stringify(out, null, 2));
757
+
758
+ const z = (n, zn) =>
759
+ zn
760
+ ? `${n}: ${zn.wPct}%×${zn.hPct}% @ (${zn.xPct}%,${zn.yPct}%) [${zn.areaPct}%${zn.meanLuma != null ? ` · luma ${zn.meanLuma}${zn.bright ? " ⚠BRIGHT" : ""}` : ""}]`
761
+ : `${n}: —`;
762
+ console.log(
763
+ `[safe-zones] ${W}×${H} grid ${GW}×${GH} @ ${fps}fps · GLOBAL coverage ${global.coverage}% · clearer ${global.subject.clearerSide} · verdict ${global.recommendation.toUpperCase()}`,
764
+ );
765
+ console.log(
766
+ ` ${z("largest", global.zones.largest)} | ${z("left", global.zones.left)} | ${z("right", global.zones.right)} | ${z("top", global.zones.top)}`,
767
+ );
768
+ if (global.recommendation === "embed") {
769
+ console.log(
770
+ `[safe-zones] ✅ EMBED — NARRATION planes go in the clean zones (prefer ${global.subject.clearerSide}/top).`,
771
+ );
772
+ if (global.heroAnchor)
773
+ console.log(
774
+ `[safe-zones] 🎯 HERO → centered ON the subject: plane ≈ x${global.heroAnchor.plane.xPct}% y${global.heroAnchor.plane.yPct}% w${global.heroAnchor.plane.wPct}% center · BIG (~0.22–0.34·h) · target ~30–55% occlusion${global.heroAnchor.bandLuma != null ? ` · band luma ${global.heroAnchor.bandLuma}${global.heroAnchor.washoutRisk ? " ⚠WASHOUT RISK — see heroAnchor.note" : ""}` : ""}`,
775
+ );
776
+ if (global.heroBands)
777
+ console.log(
778
+ `[safe-zones] hero bands: best top ${global.heroBands.best.topPct}% (predicted occlusion ${global.heroBands.best.occPct}%) · bg-hero ${global.heroBands.feasible ? "FEASIBLE — keep the hero EMBEDDED (fg is last resort)" : "INFEASIBLE (no band ≤62%) → hero fg"}`,
779
+ );
780
+ if (global.zones.hugLeft || global.zones.hugRight)
781
+ console.log(
782
+ `[safe-zones] hugging zones (narration belongs HERE, abutting the silhouette): L ${global.zones.hugLeft ? global.zones.hugLeft.wPct + "%×" + global.zones.hugLeft.hPct + "%@x" + global.zones.hugLeft.xPct + "%" + (global.zones.hugLeft.bright ? "⚠bright" : "") : "—"} · R ${global.zones.hugRight ? global.zones.hugRight.wPct + "%×" + global.zones.hugRight.hPct + "%@x" + global.zones.hugRight.xPct + "%" + (global.zones.hugRight.bright ? "⚠bright" : "") : "—"}`,
783
+ );
784
+ } else {
785
+ console.log(
786
+ `[safe-zones] ⚠ FG — subject fills the frame; use caption_layer:"fg" (no clean region to embed behind).`,
787
+ );
788
+ }
789
+ if (palette)
790
+ console.log(
791
+ `[safe-zones] 🎨 palette: dominant ${palette.dominant.map((d) => d.hex).join(" ")} · accent suggestion ${palette.accentSuggestion || "— (no chromatic anchor; use the DNA default)"} · ${palette.temperature}`,
792
+ );
793
+ if (optics && optics.sharpnessRatio != null)
794
+ console.log(
795
+ `[safe-zones] 🔭 depth: bg/subject sharpness ${optics.sharpnessRatio} → embed text blur ${optics.suggestedTextBlurPx}px${optics.suggestedTextBlurPx ? " (match the scene's depth-of-field)" : " (scene is uniformly sharp)"}`,
796
+ );
797
+ if (lighting)
798
+ console.log(
799
+ `[safe-zones] 💡 light from ${lighting.lightFrom} → contact shadow offset (${lighting.shadow.dx}px, ${lighting.shadow.dy}px)`,
800
+ );
801
+ if (windows.length) {
802
+ console.log(`[safe-zones] per-sentence windows (place each group using ITS window's zones):`);
803
+ for (const w of windows)
804
+ console.log(
805
+ ` [${w.in}-${w.out}s] ${w.recommendation.toUpperCase()} cov ${w.coverage}% clear:${w.clearerSide} ${w.zones.largest ? `best ${w.zones.largest.wPct}%×${w.zones.largest.hPct}%@(${w.zones.largest.xPct}%,${w.zones.largest.yPct}%)` : ""} "${w.text}"`,
806
+ );
807
+ }
808
+ console.log(`[safe-zones] → ${path.join(project, "safe-zones.json")}`);
809
+ }
810
+ main().catch((e) => {
811
+ console.error(`[safe-zones] (skipped — ${e.message})`);
812
+ process.exit(0);
813
+ });