@ignix-social/editor 0.1.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 (586) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +54 -0
  3. package/README.md +23 -0
  4. package/bin/ignix-editor.mjs +8 -0
  5. package/package.json +53 -0
  6. package/patches/index.mjs +53 -0
  7. package/scripts/build-ffmpeg-macos.sh +35 -0
  8. package/scripts/build-whisper-macos.sh +21 -0
  9. package/scripts/vendor.mjs +41 -0
  10. package/skills/ignix-editor/SKILL.md +132 -0
  11. package/src/audio.mjs +31 -0
  12. package/src/binaries.json +46 -0
  13. package/src/binaries.mjs +83 -0
  14. package/src/cli.mjs +254 -0
  15. package/src/prepare.mjs +74 -0
  16. package/src/runtime/ignix-frame.js +92 -0
  17. package/src/transcribe.mjs +71 -0
  18. package/src/validate.mjs +71 -0
  19. package/templates/blank/index.html +41 -0
  20. package/vendor/hyperframes/README.md +181 -0
  21. package/vendor/hyperframes/UPSTREAM.json +12 -0
  22. package/vendor/hyperframes/bin/hyperframes-localize-fonts.mjs +12 -0
  23. package/vendor/hyperframes/bin/hyperframes.mjs +11 -0
  24. package/vendor/hyperframes/dist/add-LR3ZR2A5.js +79 -0
  25. package/vendor/hyperframes/dist/agentPromptGenerator-VIIPZCJ3.js +143 -0
  26. package/vendor/hyperframes/dist/animatedGifPrep-P2T6YM7Z.js +34 -0
  27. package/vendor/hyperframes/dist/asset-BE2DOV72.js +26 -0
  28. package/vendor/hyperframes/dist/assetCataloger-VBLPHX24.js +340 -0
  29. package/vendor/hyperframes/dist/auth-RCQB2ZT7.js +60 -0
  30. package/vendor/hyperframes/dist/autoUpdate-RJAX3DAZ.js +268 -0
  31. package/vendor/hyperframes/dist/backgroundChecks-H2LCUIMJ.js +61 -0
  32. package/vendor/hyperframes/dist/backgroundChecksWorker.js +26 -0
  33. package/vendor/hyperframes/dist/batchRender-FT7AP7XH.js +355 -0
  34. package/vendor/hyperframes/dist/beat-analyzer.global.js +338 -0
  35. package/vendor/hyperframes/dist/beats-Y2EOP72P.js +309 -0
  36. package/vendor/hyperframes/dist/benchmark-MZ7GY3JW.js +231 -0
  37. package/vendor/hyperframes/dist/browser-HUTAMRQE.js +237 -0
  38. package/vendor/hyperframes/dist/bundleWithLocalizedFonts-5QOPOB5D.js +52 -0
  39. package/vendor/hyperframes/dist/capture-RLZA6EMR.js +4079 -0
  40. package/vendor/hyperframes/dist/capture-SZZ4IYJK.js +290 -0
  41. package/vendor/hyperframes/dist/captureFile-LYOJOUIE.js +15 -0
  42. package/vendor/hyperframes/dist/captureTimeout-PA4FYWT5.js +27 -0
  43. package/vendor/hyperframes/dist/catalog-KYU55EWR.js +1001 -0
  44. package/vendor/hyperframes/dist/check-TCG4PXLR.js +1697 -0
  45. package/vendor/hyperframes/dist/checkBrowser-RPMO4OW5.js +1032 -0
  46. package/vendor/hyperframes/dist/chunk-2EAXUCCE.js +88 -0
  47. package/vendor/hyperframes/dist/chunk-2EGFTPWD.js +1077 -0
  48. package/vendor/hyperframes/dist/chunk-2PSPARWP.js +6058 -0
  49. package/vendor/hyperframes/dist/chunk-2YN64IN5.js +5747 -0
  50. package/vendor/hyperframes/dist/chunk-3GXQPCJC.js +682 -0
  51. package/vendor/hyperframes/dist/chunk-3ZP5DXE7.js +15 -0
  52. package/vendor/hyperframes/dist/chunk-4NWOGZZS.js +1990 -0
  53. package/vendor/hyperframes/dist/chunk-4PFZPP2N.js +587 -0
  54. package/vendor/hyperframes/dist/chunk-4THIAPBL.js +128 -0
  55. package/vendor/hyperframes/dist/chunk-4YYJOFKS.js +28 -0
  56. package/vendor/hyperframes/dist/chunk-54HJ63Q2.js +29 -0
  57. package/vendor/hyperframes/dist/chunk-5ETJX6WM.js +89 -0
  58. package/vendor/hyperframes/dist/chunk-5IAKAQA7.js +389 -0
  59. package/vendor/hyperframes/dist/chunk-5Z63TBTX.js +37 -0
  60. package/vendor/hyperframes/dist/chunk-6BEFEWFB.js +25 -0
  61. package/vendor/hyperframes/dist/chunk-6CSYCFQV.js +68 -0
  62. package/vendor/hyperframes/dist/chunk-6CTIGJY7.js +154 -0
  63. package/vendor/hyperframes/dist/chunk-7HG4BFIN.js +93 -0
  64. package/vendor/hyperframes/dist/chunk-7JZ4FXP2.js +118 -0
  65. package/vendor/hyperframes/dist/chunk-A2Z4Z4VL.js +2961 -0
  66. package/vendor/hyperframes/dist/chunk-AMKC3AXA.js +25 -0
  67. package/vendor/hyperframes/dist/chunk-AVUUN2XD.js +4847 -0
  68. package/vendor/hyperframes/dist/chunk-AWDFDFI3.js +46 -0
  69. package/vendor/hyperframes/dist/chunk-AWNBM3MZ.js +94 -0
  70. package/vendor/hyperframes/dist/chunk-BXCMXMJQ.js +73 -0
  71. package/vendor/hyperframes/dist/chunk-BZ56MTDX.js +6 -0
  72. package/vendor/hyperframes/dist/chunk-CFKU7HTK.js +171 -0
  73. package/vendor/hyperframes/dist/chunk-CWPVM2QV.js +308 -0
  74. package/vendor/hyperframes/dist/chunk-DPVXBVLL.js +31292 -0
  75. package/vendor/hyperframes/dist/chunk-DQBY7FRR.js +397 -0
  76. package/vendor/hyperframes/dist/chunk-E6RPWJVC.js +213 -0
  77. package/vendor/hyperframes/dist/chunk-EISRN4E6.js +294 -0
  78. package/vendor/hyperframes/dist/chunk-EOVYQ3IG.js +143 -0
  79. package/vendor/hyperframes/dist/chunk-F3WZGU7C.js +479 -0
  80. package/vendor/hyperframes/dist/chunk-FCSIGXXP.js +366 -0
  81. package/vendor/hyperframes/dist/chunk-FI4I7CME.js +56 -0
  82. package/vendor/hyperframes/dist/chunk-FLWXXDNS.js +15 -0
  83. package/vendor/hyperframes/dist/chunk-GK4AIFIH.js +11171 -0
  84. package/vendor/hyperframes/dist/chunk-GMECXFAN.js +57 -0
  85. package/vendor/hyperframes/dist/chunk-GULOKZLI.js +155 -0
  86. package/vendor/hyperframes/dist/chunk-H4YZZ2VQ.js +68 -0
  87. package/vendor/hyperframes/dist/chunk-H6QMKUYA.js +477 -0
  88. package/vendor/hyperframes/dist/chunk-HBBJFK6I.js +273 -0
  89. package/vendor/hyperframes/dist/chunk-HRUGC23L.js +212 -0
  90. package/vendor/hyperframes/dist/chunk-HVW7ABXD.js +19 -0
  91. package/vendor/hyperframes/dist/chunk-HW6OYASV.js +344 -0
  92. package/vendor/hyperframes/dist/chunk-HWKCIWQT.js +3578 -0
  93. package/vendor/hyperframes/dist/chunk-I465DBM6.js +1214 -0
  94. package/vendor/hyperframes/dist/chunk-IGLEPPTN.js +24 -0
  95. package/vendor/hyperframes/dist/chunk-IGNAAHS3.js +433 -0
  96. package/vendor/hyperframes/dist/chunk-IHFUKJOR.js +85 -0
  97. package/vendor/hyperframes/dist/chunk-IQ3OUQ5Z.js +213 -0
  98. package/vendor/hyperframes/dist/chunk-ISN5GRYI.js +20 -0
  99. package/vendor/hyperframes/dist/chunk-IUZFXWSV.js +446 -0
  100. package/vendor/hyperframes/dist/chunk-JH556Q42.js +647 -0
  101. package/vendor/hyperframes/dist/chunk-JTHMTOMQ.js +601 -0
  102. package/vendor/hyperframes/dist/chunk-K2EQN4VU.js +405 -0
  103. package/vendor/hyperframes/dist/chunk-K2FTXJYH.js +16044 -0
  104. package/vendor/hyperframes/dist/chunk-K4WIOZFP.js +32 -0
  105. package/vendor/hyperframes/dist/chunk-K4WKGXRC.js +18 -0
  106. package/vendor/hyperframes/dist/chunk-KBJUXVOJ.js +172 -0
  107. package/vendor/hyperframes/dist/chunk-KC5XXX3X.js +590 -0
  108. package/vendor/hyperframes/dist/chunk-KNU5COPB.js +29 -0
  109. package/vendor/hyperframes/dist/chunk-KZI44OPA.js +185 -0
  110. package/vendor/hyperframes/dist/chunk-L3YOGIPG.js +48 -0
  111. package/vendor/hyperframes/dist/chunk-LAOCA3MQ.js +189 -0
  112. package/vendor/hyperframes/dist/chunk-LCSMOLGX.js +312 -0
  113. package/vendor/hyperframes/dist/chunk-LFGJWOVA.js +61 -0
  114. package/vendor/hyperframes/dist/chunk-LHSR3M4M.js +498 -0
  115. package/vendor/hyperframes/dist/chunk-LYNI3FOL.js +123 -0
  116. package/vendor/hyperframes/dist/chunk-M2WBOQ2Q.js +54 -0
  117. package/vendor/hyperframes/dist/chunk-MRF4AMKD.js +104 -0
  118. package/vendor/hyperframes/dist/chunk-MWB3ZIFM.js +852 -0
  119. package/vendor/hyperframes/dist/chunk-NGKM64BP.js +39 -0
  120. package/vendor/hyperframes/dist/chunk-NXMYHJAL.js +43 -0
  121. package/vendor/hyperframes/dist/chunk-OAF4XJOW.js +192 -0
  122. package/vendor/hyperframes/dist/chunk-OFQWTKPW.js +117 -0
  123. package/vendor/hyperframes/dist/chunk-P6LJ4Y32.js +4662 -0
  124. package/vendor/hyperframes/dist/chunk-PBN6GBJE.js +108 -0
  125. package/vendor/hyperframes/dist/chunk-PDAMK4Q5.js +28 -0
  126. package/vendor/hyperframes/dist/chunk-PFCFR3KZ.js +20 -0
  127. package/vendor/hyperframes/dist/chunk-PJ3HDUCY.js +108 -0
  128. package/vendor/hyperframes/dist/chunk-PMRBLBUP.js +152 -0
  129. package/vendor/hyperframes/dist/chunk-PPQUYWTW.js +240 -0
  130. package/vendor/hyperframes/dist/chunk-Q5X2B5WT.js +29 -0
  131. package/vendor/hyperframes/dist/chunk-Q7IU7MOR.js +351 -0
  132. package/vendor/hyperframes/dist/chunk-QC4FJVLM.js +371 -0
  133. package/vendor/hyperframes/dist/chunk-QNYYOUU3.js +1318 -0
  134. package/vendor/hyperframes/dist/chunk-RDNNAKEP.js +161 -0
  135. package/vendor/hyperframes/dist/chunk-RMHUQGRK.js +657 -0
  136. package/vendor/hyperframes/dist/chunk-RQVDTPGD.js +6 -0
  137. package/vendor/hyperframes/dist/chunk-RVCWIIZ7.js +68 -0
  138. package/vendor/hyperframes/dist/chunk-S3D6GE5W.js +72 -0
  139. package/vendor/hyperframes/dist/chunk-S3S4TGEH.js +424 -0
  140. package/vendor/hyperframes/dist/chunk-S7NPLFWV.js +11395 -0
  141. package/vendor/hyperframes/dist/chunk-SHB5VDMH.js +51 -0
  142. package/vendor/hyperframes/dist/chunk-TCLGPL65.js +860 -0
  143. package/vendor/hyperframes/dist/chunk-THXFSPLV.js +40 -0
  144. package/vendor/hyperframes/dist/chunk-TJSTGSAW.js +354 -0
  145. package/vendor/hyperframes/dist/chunk-TQFG7HTI.js +1024 -0
  146. package/vendor/hyperframes/dist/chunk-TVCRUG2S.js +265 -0
  147. package/vendor/hyperframes/dist/chunk-TWWSYAKJ.js +24 -0
  148. package/vendor/hyperframes/dist/chunk-U7CZBDMV.js +27 -0
  149. package/vendor/hyperframes/dist/chunk-UEZXMWG3.js +139 -0
  150. package/vendor/hyperframes/dist/chunk-UYEZTK4U.js +61 -0
  151. package/vendor/hyperframes/dist/chunk-VC76R6W2.js +27 -0
  152. package/vendor/hyperframes/dist/chunk-VFCKXTBB.js +159 -0
  153. package/vendor/hyperframes/dist/chunk-VWG7OQKU.js +22 -0
  154. package/vendor/hyperframes/dist/chunk-VXVRPIJA.js +105 -0
  155. package/vendor/hyperframes/dist/chunk-WBLXWOYK.js +15 -0
  156. package/vendor/hyperframes/dist/chunk-WERLJGIC.js +20 -0
  157. package/vendor/hyperframes/dist/chunk-WMQNIQDJ.js +2057 -0
  158. package/vendor/hyperframes/dist/chunk-WPHMNO44.js +4528 -0
  159. package/vendor/hyperframes/dist/chunk-X5HWXQPU.js +113 -0
  160. package/vendor/hyperframes/dist/chunk-X7MTZ4T2.js +6 -0
  161. package/vendor/hyperframes/dist/chunk-XH4DSOS5.js +59 -0
  162. package/vendor/hyperframes/dist/chunk-XS4EJSKU.js +23 -0
  163. package/vendor/hyperframes/dist/chunk-XT5P4QMO.js +181 -0
  164. package/vendor/hyperframes/dist/chunk-XT7X6QCP.js +6 -0
  165. package/vendor/hyperframes/dist/chunk-XUHOIDKG.js +6 -0
  166. package/vendor/hyperframes/dist/chunk-XUXGIA7H.js +485 -0
  167. package/vendor/hyperframes/dist/chunk-XZABUQTX.js +113 -0
  168. package/vendor/hyperframes/dist/chunk-Y6W4DVPS.js +356 -0
  169. package/vendor/hyperframes/dist/chunk-YEIFI443.js +44 -0
  170. package/vendor/hyperframes/dist/chunk-YQZNILVH.js +375 -0
  171. package/vendor/hyperframes/dist/chunk-Z27A7NUB.js +65 -0
  172. package/vendor/hyperframes/dist/chunk-Z6B37SNM.js +3081 -0
  173. package/vendor/hyperframes/dist/chunk-ZJUKFZ2P.js +10954 -0
  174. package/vendor/hyperframes/dist/chunk-ZKDMAOFU.js +32 -0
  175. package/vendor/hyperframes/dist/chunk-ZMXUQLI2.js +17219 -0
  176. package/vendor/hyperframes/dist/chunk-ZTZ7NCJT.js +58 -0
  177. package/vendor/hyperframes/dist/chunk-ZTZA4UWZ.js +108 -0
  178. package/vendor/hyperframes/dist/clean-W57E5YQ3.js +293 -0
  179. package/vendor/hyperframes/dist/cli.js +495 -0
  180. package/vendor/hyperframes/dist/cloud-FFTJGZ24.js +70 -0
  181. package/vendor/hyperframes/dist/cloudrun-AJUTHUEA.js +763 -0
  182. package/vendor/hyperframes/dist/colors-HKWSZCGK.js +15 -0
  183. package/vendor/hyperframes/dist/commands/contrast-audit.browser.js +459 -0
  184. package/vendor/hyperframes/dist/commands/layout-audit.browser.js +2102 -0
  185. package/vendor/hyperframes/dist/commands/motion-sample.browser.js +74 -0
  186. package/vendor/hyperframes/dist/commands/motion-signature.browser.js +374 -0
  187. package/vendor/hyperframes/dist/compare-AYCTT55F.js +421 -0
  188. package/vendor/hyperframes/dist/compiler-QO36EXLS.js +121 -0
  189. package/vendor/hyperframes/dist/component-VFFR2O7T.js +176 -0
  190. package/vendor/hyperframes/dist/compositions-QTVN3U2A.js +228 -0
  191. package/vendor/hyperframes/dist/contactSheet-27SRU7A4.js +22 -0
  192. package/vendor/hyperframes/dist/delete-ZAKYCB7I.js +114 -0
  193. package/vendor/hyperframes/dist/deploy-MX76RZ36.js +101 -0
  194. package/vendor/hyperframes/dist/destroy-H2TZBYHH.js +42 -0
  195. package/vendor/hyperframes/dist/deterministicFonts-HAEMV4TM.js +50 -0
  196. package/vendor/hyperframes/dist/dist-EWXJGN63.js +129 -0
  197. package/vendor/hyperframes/dist/dist-FNNGCKFS.js +29 -0
  198. package/vendor/hyperframes/dist/dist-S3PJNPXQ.js +392 -0
  199. package/vendor/hyperframes/dist/docker/Dockerfile.render +65 -0
  200. package/vendor/hyperframes/dist/docs/compositions.md +67 -0
  201. package/vendor/hyperframes/dist/docs/data-attributes.md +27 -0
  202. package/vendor/hyperframes/dist/docs/examples.md +19 -0
  203. package/vendor/hyperframes/dist/docs/gsap.md +26 -0
  204. package/vendor/hyperframes/dist/docs/rendering.md +35 -0
  205. package/vendor/hyperframes/dist/docs/troubleshooting.md +29 -0
  206. package/vendor/hyperframes/dist/docs-QAPJEGLS.js +131 -0
  207. package/vendor/hyperframes/dist/doctor-AI7C3FON.js +67 -0
  208. package/vendor/hyperframes/dist/dom-IROKGVJI.js +14 -0
  209. package/vendor/hyperframes/dist/dotEnv-AECLXQSO.js +14 -0
  210. package/vendor/hyperframes/dist/events-7LALYNKZ.js +92 -0
  211. package/vendor/hyperframes/dist/events-FQY3HGKZ.js +88 -0
  212. package/vendor/hyperframes/dist/feedback-WCMDMSTK.js +472 -0
  213. package/vendor/hyperframes/dist/ffmpeg-QIUFEFTY.js +35 -0
  214. package/vendor/hyperframes/dist/figma-64WY462P.js +63 -0
  215. package/vendor/hyperframes/dist/fontData.generated-NXAOI4GH.js +13 -0
  216. package/vendor/hyperframes/dist/fontLocalizeCli.js +132 -0
  217. package/vendor/hyperframes/dist/get-FPOMJMZB.js +103 -0
  218. package/vendor/hyperframes/dist/gpuPolicy-2ZUZE5D6.js +38 -0
  219. package/vendor/hyperframes/dist/grade-compare-E2CKPST4.js +647 -0
  220. package/vendor/hyperframes/dist/gsapParser-DIYYX6JT.js +2846 -0
  221. package/vendor/hyperframes/dist/gsapParserAcorn-E3GEIRZB.js +28 -0
  222. package/vendor/hyperframes/dist/help-YRQIDDML.js +197 -0
  223. package/vendor/hyperframes/dist/history-PP5NDRV3.js +571 -0
  224. package/vendor/hyperframes/dist/hyperframe-runtime.js +1081 -0
  225. package/vendor/hyperframes/dist/hyperframe.manifest.json +22 -0
  226. package/vendor/hyperframes/dist/hyperframe.runtime.iife.js +1081 -0
  227. package/vendor/hyperframes/dist/hyperframes-player.global.js +474 -0
  228. package/vendor/hyperframes/dist/hyperframes-slideshow.global.js +149 -0
  229. package/vendor/hyperframes/dist/info-SEX3B7JL.js +153 -0
  230. package/vendor/hyperframes/dist/init-U3AKHO5B.js +1147 -0
  231. package/vendor/hyperframes/dist/inspect-Q3K6EDEK.js +97 -0
  232. package/vendor/hyperframes/dist/installer-K6O5WF44.js +37 -0
  233. package/vendor/hyperframes/dist/keyframes-DC64JCI3.js +2602 -0
  234. package/vendor/hyperframes/dist/lambda-D7UMN4DJ.js +451 -0
  235. package/vendor/hyperframes/dist/layout-XSPPDHOO.js +86 -0
  236. package/vendor/hyperframes/dist/lib-TPG2ZMAX.js +128 -0
  237. package/vendor/hyperframes/dist/lint-7DQZE7L3.js +164 -0
  238. package/vendor/hyperframes/dist/lintProject-OHBBM3DJ.js +27 -0
  239. package/vendor/hyperframes/dist/list-4K5MVDQX.js +162 -0
  240. package/vendor/hyperframes/dist/login-NTX4OWXL.js +377 -0
  241. package/vendor/hyperframes/dist/logout-Q654A3SI.js +100 -0
  242. package/vendor/hyperframes/dist/manager-67P2XFXQ.js +34 -0
  243. package/vendor/hyperframes/dist/manager-CT3D67CG.js +38 -0
  244. package/vendor/hyperframes/dist/manager-GFN75DM4.js +30 -0
  245. package/vendor/hyperframes/dist/media-treatment-5FYAOPNL.js +972 -0
  246. package/vendor/hyperframes/dist/media-use-4XXGABIL.js +113 -0
  247. package/vendor/hyperframes/dist/models-C4E4IWJD.js +137 -0
  248. package/vendor/hyperframes/dist/motionShot-S4MIGMT5.js +614 -0
  249. package/vendor/hyperframes/dist/normalize-O76AC4HE.js +333 -0
  250. package/vendor/hyperframes/dist/normalize-audio-CSFTXLXD.js +276 -0
  251. package/vendor/hyperframes/dist/open-2ZD6BHZG.js +115 -0
  252. package/vendor/hyperframes/dist/pipeline-TNGWUZQC.js +54 -0
  253. package/vendor/hyperframes/dist/play-CTMUXX6E.js +349 -0
  254. package/vendor/hyperframes/dist/policies-FSDZVS2E.js +292 -0
  255. package/vendor/hyperframes/dist/preflight-SMNV6RW2.js +45 -0
  256. package/vendor/hyperframes/dist/present-WQVHV5CR.js +350 -0
  257. package/vendor/hyperframes/dist/preview-JOJXTH75.js +1820 -0
  258. package/vendor/hyperframes/dist/producer-ECLEN5RP.js +13 -0
  259. package/vendor/hyperframes/dist/progress-FBIEEB7Y.js +73 -0
  260. package/vendor/hyperframes/dist/projectConfig-HYJMJ5BY.js +56 -0
  261. package/vendor/hyperframes/dist/publication-HQ5Y3KBM.js +29 -0
  262. package/vendor/hyperframes/dist/publish-VHDYQASC.js +430 -0
  263. package/vendor/hyperframes/dist/refresh-K24ETHMA.js +53 -0
  264. package/vendor/hyperframes/dist/remove-background-W2YX2NVH.js +253 -0
  265. package/vendor/hyperframes/dist/render-AO5VX3YI.js +177 -0
  266. package/vendor/hyperframes/dist/render-LNO62LEL.js +2367 -0
  267. package/vendor/hyperframes/dist/render-TTQI7TGN.js +796 -0
  268. package/vendor/hyperframes/dist/render-batch-YH7CTWNY.js +284 -0
  269. package/vendor/hyperframes/dist/renderCancellation-JPIHE47X.js +35 -0
  270. package/vendor/hyperframes/dist/renderSetupWorker.js +79 -0
  271. package/vendor/hyperframes/dist/resolver-IZ647GBZ.js +38 -0
  272. package/vendor/hyperframes/dist/runtimeVersion.js +18 -0
  273. package/vendor/hyperframes/dist/screenshotCapture-ECBTLCU6.js +179 -0
  274. package/vendor/hyperframes/dist/shaderTransitionWorker.js +44 -0
  275. package/vendor/hyperframes/dist/sherpa-KKLNW6U2.js +314 -0
  276. package/vendor/hyperframes/dist/sherpaWorker.js +77 -0
  277. package/vendor/hyperframes/dist/sites-TNVKZS73.js +52 -0
  278. package/vendor/hyperframes/dist/skill-ZHVLH7KX.js +15 -0
  279. package/vendor/hyperframes/dist/skills/hyperframes/SKILL.md +152 -0
  280. package/vendor/hyperframes/dist/skills/hyperframes/references/brief-contract.md +108 -0
  281. package/vendor/hyperframes/dist/skills/hyperframes/references/brief-format.md +72 -0
  282. package/vendor/hyperframes/dist/skills/hyperframes/references/capability-menu.md +47 -0
  283. package/vendor/hyperframes/dist/skills/hyperframes/references/frame-worker-core.md +80 -0
  284. package/vendor/hyperframes/dist/skills/hyperframes/references/intent-interview.md +73 -0
  285. package/vendor/hyperframes/dist/skills/hyperframes/references/pitch-round.md +40 -0
  286. package/vendor/hyperframes/dist/skills/hyperframes/references/plugin-installation.md +43 -0
  287. package/vendor/hyperframes/dist/skills/hyperframes/references/production-loop.md +26 -0
  288. package/vendor/hyperframes/dist/skills/hyperframes/references/review-loop.md +37 -0
  289. package/vendor/hyperframes/dist/skills/hyperframes/references/route-briefs.md +11 -0
  290. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/embedded-captions.md +11 -0
  291. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/faceless-explainer.md +12 -0
  292. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/general-video.md +12 -0
  293. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/motion-graphics.md +11 -0
  294. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/music-to-video.md +12 -0
  295. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/pr-to-video.md +28 -0
  296. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/product-launch-video.md +13 -0
  297. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/remotion-to-hyperframes.md +9 -0
  298. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/slideshow.md +11 -0
  299. package/vendor/hyperframes/dist/skills/hyperframes/references/routes/talking-head-recut.md +11 -0
  300. package/vendor/hyperframes/dist/skills/hyperframes/references/script-format.md +52 -0
  301. package/vendor/hyperframes/dist/skills/hyperframes/references/skill-lifecycle.md +35 -0
  302. package/vendor/hyperframes/dist/skills/hyperframes/references/storyboard-format.md +99 -0
  303. package/vendor/hyperframes/dist/skills/hyperframes/references/subagent-dispatch.md +35 -0
  304. package/vendor/hyperframes/dist/skills/hyperframes/references/workflow-catalog.md +10 -0
  305. package/vendor/hyperframes/dist/skills/hyperframes/scripts/lib/frame-packets-core.mjs +194 -0
  306. package/vendor/hyperframes/dist/skills/hyperframes/scripts/plugin-cli.mjs +88 -0
  307. package/vendor/hyperframes/dist/skills/hyperframes/scripts/plugin-cli.test.mjs +117 -0
  308. package/vendor/hyperframes/dist/skills/hyperframes-cli/SKILL.md +189 -0
  309. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/beats.md +19 -0
  310. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/cloud.md +163 -0
  311. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/cloudrun.md +62 -0
  312. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/compare-and-batch.md +105 -0
  313. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/doctor-browser.md +57 -0
  314. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/init-and-scaffold.md +74 -0
  315. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/lambda.md +185 -0
  316. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +132 -0
  317. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/preview-render.md +193 -0
  318. package/vendor/hyperframes/dist/skills/hyperframes-cli/references/upgrade-info-misc.md +134 -0
  319. package/vendor/hyperframes/dist/skills/media-use/SKILL.md +100 -0
  320. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/CREDITS.md +35 -0
  321. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/chime.mp3 +0 -0
  322. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/click-soft.mp3 +0 -0
  323. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/click.mp3 +0 -0
  324. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/error.mp3 +0 -0
  325. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/glitch-1.mp3 +0 -0
  326. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/glitch-2.mp3 +0 -0
  327. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/glitch-3.mp3 +0 -0
  328. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/impact-bass-1.mp3 +0 -0
  329. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/impact-bass-2.mp3 +0 -0
  330. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/key-press.mp3 +0 -0
  331. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/manifest.json +97 -0
  332. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/notification.mp3 +0 -0
  333. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/ping.mp3 +0 -0
  334. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/pop.mp3 +0 -0
  335. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/riser.mp3 +0 -0
  336. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/sparkle.mp3 +0 -0
  337. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/typing.mp3 +0 -0
  338. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/whoosh-cinematic.mp3 +0 -0
  339. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/whoosh-short.mp3 +0 -0
  340. package/vendor/hyperframes/dist/skills/media-use/audio/assets/sfx/whoosh.mp3 +0 -0
  341. package/vendor/hyperframes/dist/skills/media-use/audio/references/bgm.md +76 -0
  342. package/vendor/hyperframes/dist/skills/media-use/audio/references/captions/authoring.md +163 -0
  343. package/vendor/hyperframes/dist/skills/media-use/audio/references/captions/motion.md +87 -0
  344. package/vendor/hyperframes/dist/skills/media-use/audio/references/captions/transcript-handling.md +97 -0
  345. package/vendor/hyperframes/dist/skills/media-use/audio/references/remove-background.md +143 -0
  346. package/vendor/hyperframes/dist/skills/media-use/audio/references/requirements.md +29 -0
  347. package/vendor/hyperframes/dist/skills/media-use/audio/references/sfx.md +42 -0
  348. package/vendor/hyperframes/dist/skills/media-use/audio/references/transcribe.md +52 -0
  349. package/vendor/hyperframes/dist/skills/media-use/audio/references/tts-to-captions.md +35 -0
  350. package/vendor/hyperframes/dist/skills/media-use/audio/references/tts.md +260 -0
  351. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/audio.mjs +311 -0
  352. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/audio.test.mjs +159 -0
  353. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/gemini-pipeline.test.mjs +131 -0
  354. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/heygen-tts.mjs +122 -0
  355. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/heygen-tts.test.mjs +38 -0
  356. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/heygen-voice.mjs +98 -0
  357. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/heygen-voice.test.mjs +233 -0
  358. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/audio-meta.mjs +39 -0
  359. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/audio-meta.test.mjs +136 -0
  360. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/bgm-volume.mjs +4 -0
  361. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/bgm.mjs +257 -0
  362. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/bgm.test.mjs +74 -0
  363. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/concurrency.mjs +14 -0
  364. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/concurrency.test.mjs +41 -0
  365. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/gemini-auth.mjs +43 -0
  366. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/gemini-auth.py +52 -0
  367. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/gemini-auth.test.mjs +86 -0
  368. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/gemini-auth_test.py +90 -0
  369. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/gemini-tts.mjs +122 -0
  370. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/gemini-tts.test.mjs +243 -0
  371. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/heygen.mjs +197 -0
  372. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/heygen.test.mjs +185 -0
  373. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/media-record.mjs +114 -0
  374. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/media-record.test.mjs +205 -0
  375. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/python.mjs +77 -0
  376. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/python.test.mjs +136 -0
  377. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/sfx.mjs +165 -0
  378. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/sfx.test.mjs +121 -0
  379. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/tts.mjs +389 -0
  380. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/tts.spawn.test.mjs +158 -0
  381. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lib/tts.test.mjs +230 -0
  382. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/lyria-recipe.py +136 -0
  383. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/wait-bgm.mjs +175 -0
  384. package/vendor/hyperframes/dist/skills/media-use/audio/scripts/wait-bgm.test.mjs +108 -0
  385. package/vendor/hyperframes/dist/skills/media-use/luts/README.md +28 -0
  386. package/vendor/hyperframes/dist/skills/media-use/luts/index.json +57 -0
  387. package/vendor/hyperframes/dist/skills/media-use/references/audio.md +20 -0
  388. package/vendor/hyperframes/dist/skills/media-use/references/grading.md +160 -0
  389. package/vendor/hyperframes/dist/skills/media-use/references/media-treatment-recipes.md +929 -0
  390. package/vendor/hyperframes/dist/skills/media-use/references/media-treatments.md +202 -0
  391. package/vendor/hyperframes/dist/skills/media-use/references/memory.md +37 -0
  392. package/vendor/hyperframes/dist/skills/media-use/references/meta.md +61 -0
  393. package/vendor/hyperframes/dist/skills/media-use/references/operations.md +327 -0
  394. package/vendor/hyperframes/dist/skills/media-use/references/resolve.md +150 -0
  395. package/vendor/hyperframes/dist/skills/media-use/references/setup-providers.md +80 -0
  396. package/vendor/hyperframes/dist/skills/media-use/references/telemetry-dashboard.md +49 -0
  397. package/vendor/hyperframes/dist/skills/media-use/scripts/audio-duck.mjs +113 -0
  398. package/vendor/hyperframes/dist/skills/media-use/scripts/compatibility.test.mjs +96 -0
  399. package/vendor/hyperframes/dist/skills/media-use/scripts/dither.mjs +328 -0
  400. package/vendor/hyperframes/dist/skills/media-use/scripts/dither.test.mjs +176 -0
  401. package/vendor/hyperframes/dist/skills/media-use/scripts/eval.mjs +369 -0
  402. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/adopt.mjs +128 -0
  403. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/adopt.test.mjs +95 -0
  404. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/bgm-provider.mjs +20 -0
  405. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/brand-provider.mjs +59 -0
  406. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +119 -0
  407. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +139 -0
  408. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/cache.mjs +148 -0
  409. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/candidates.mjs +91 -0
  410. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/candidates.test.mjs +155 -0
  411. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/codex-provider.mjs +161 -0
  412. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/codex-provider.test.mjs +34 -0
  413. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/config-lock.d.mts +3 -0
  414. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/config-lock.mjs +45 -0
  415. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/config-lock.test.mjs +79 -0
  416. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/coverage.test.mjs +136 -0
  417. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/cube-build.mjs +181 -0
  418. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/cube-build.test.mjs +80 -0
  419. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/cube-validate.mjs +198 -0
  420. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/cube-validate.test.mjs +125 -0
  421. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/cutlist.mjs +184 -0
  422. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/cutlist.test.mjs +162 -0
  423. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/duck.mjs +95 -0
  424. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/duck.test.mjs +153 -0
  425. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/error-diffusion.mjs +230 -0
  426. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/error-diffusion.test.mjs +102 -0
  427. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/ff-binaries.mjs +37 -0
  428. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/freeze.mjs +72 -0
  429. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/freeze.test.mjs +138 -0
  430. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/grade-analyzer.mjs +265 -0
  431. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/grade-analyzer.test.mjs +285 -0
  432. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/heygen-cli.mjs +191 -0
  433. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/heygen-cli.test.mjs +301 -0
  434. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/heygen-search.mjs +45 -0
  435. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/heygen-search.test.mjs +49 -0
  436. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/heygen-video-provider.mjs +138 -0
  437. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +435 -0
  438. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/image-provider.mjs +44 -0
  439. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/index-gen.mjs +63 -0
  440. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/local-media-search.mjs +32 -0
  441. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/local-models.mjs +318 -0
  442. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/local-models.test.mjs +220 -0
  443. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/local-run.mjs +78 -0
  444. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/local-run.test.mjs +121 -0
  445. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/logo-provider.mjs +244 -0
  446. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/logo-provider.test.mjs +298 -0
  447. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/ltx-video-provider.mjs +94 -0
  448. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +316 -0
  449. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/lut-preset-provider.mjs +271 -0
  450. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +199 -0
  451. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/manifest.d.mts +19 -0
  452. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/manifest.mjs +279 -0
  453. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/manifest.test.mjs +397 -0
  454. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/match.mjs +47 -0
  455. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/media-fetch.mjs +85 -0
  456. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/media-fetch.test.mjs +197 -0
  457. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/media-home.mjs +20 -0
  458. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/media-home.test.mjs +61 -0
  459. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/media-search.d.mts +9 -0
  460. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/media-search.mjs +107 -0
  461. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/mflux-provider.mjs +128 -0
  462. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/mflux-provider.test.mjs +174 -0
  463. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/misses.mjs +49 -0
  464. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/misses.test.mjs +89 -0
  465. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/npx-sync.mjs +31 -0
  466. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/npx-sync.test.mjs +50 -0
  467. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/parakeet-words.mjs +27 -0
  468. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/parakeet-words.test.mjs +44 -0
  469. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/prefs-store.mjs +180 -0
  470. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/prefs-store.test.mjs +189 -0
  471. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/probe.mjs +41 -0
  472. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/probe.test.mjs +148 -0
  473. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/providers.mjs +5 -0
  474. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/recipe-store.mjs +367 -0
  475. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/recipe-store.test.mjs +367 -0
  476. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/registry.mjs +212 -0
  477. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/registry.test.mjs +262 -0
  478. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/search.mjs +24 -0
  479. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/search.test.mjs +32 -0
  480. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/sfx-provider.mjs +23 -0
  481. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/specs.mjs +132 -0
  482. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/specs.test.mjs +157 -0
  483. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/stats.mjs +117 -0
  484. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/stats.test.mjs +165 -0
  485. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/svg-sanitize.mjs +21 -0
  486. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/telemetry.mjs +210 -0
  487. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/telemetry.test.mjs +437 -0
  488. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/test-home.test.mjs +10 -0
  489. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/transcriptCutFade.mjs +21 -0
  490. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/transcriptCutFade.test.mjs +33 -0
  491. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/tts-local-provider.mjs +94 -0
  492. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/tts-local-provider.test.mjs +72 -0
  493. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/usage.mjs +57 -0
  494. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/usage.test.mjs +54 -0
  495. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/voice-provider.mjs +57 -0
  496. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/voice-provider.test.mjs +46 -0
  497. package/vendor/hyperframes/dist/skills/media-use/scripts/lib/words.mjs +18 -0
  498. package/vendor/hyperframes/dist/skills/media-use/scripts/prefs.mjs +70 -0
  499. package/vendor/hyperframes/dist/skills/media-use/scripts/recipe.mjs +103 -0
  500. package/vendor/hyperframes/dist/skills/media-use/scripts/resolve-plugin.test.mjs +62 -0
  501. package/vendor/hyperframes/dist/skills/media-use/scripts/resolve.mjs +1369 -0
  502. package/vendor/hyperframes/dist/skills/media-use/scripts/transcribe.mjs +194 -0
  503. package/vendor/hyperframes/dist/skills/media-use/scripts/transcribe.test.mjs +112 -0
  504. package/vendor/hyperframes/dist/skills/media-use/scripts/transcript-cut.mjs +270 -0
  505. package/vendor/hyperframes/dist/skills/media-use/scripts/transcript-cut.test.mjs +117 -0
  506. package/vendor/hyperframes/dist/skills-MJ2J4GEM.js +808 -0
  507. package/vendor/hyperframes/dist/skillsUpdateCheck-TKTYQN5R.js +26 -0
  508. package/vendor/hyperframes/dist/slideshow-EJ4IMY6P.js +24 -0
  509. package/vendor/hyperframes/dist/snapshot-KIVDUUMN.js +875 -0
  510. package/vendor/hyperframes/dist/src-6YQBJGWR.js +202 -0
  511. package/vendor/hyperframes/dist/src-PVYUYV3M.js +471 -0
  512. package/vendor/hyperframes/dist/state-KAXGHF77.js +26 -0
  513. package/vendor/hyperframes/dist/staticProjectServer-7EU666GS.js +49 -0
  514. package/vendor/hyperframes/dist/status-YM52BQ6S.js +311 -0
  515. package/vendor/hyperframes/dist/studio/assets/hyperframes-player-Cp8e_Zst.js +473 -0
  516. package/vendor/hyperframes/dist/studio/assets/index-BX3KHhGX.js +71 -0
  517. package/vendor/hyperframes/dist/studio/assets/index-CASEBNy2.js +1 -0
  518. package/vendor/hyperframes/dist/studio/assets/index-D11atkBt.css +1 -0
  519. package/vendor/hyperframes/dist/studio/assets/index-DMTt9nTf.js +531 -0
  520. package/vendor/hyperframes/dist/studio/assets/index-oE1pOdAy.js +399 -0
  521. package/vendor/hyperframes/dist/studio/chunk-5VSXRGIX.js +948 -0
  522. package/vendor/hyperframes/dist/studio/chunk-5VSXRGIX.js.map +1 -0
  523. package/vendor/hyperframes/dist/studio/chunk-7UQWVN52.js +938 -0
  524. package/vendor/hyperframes/dist/studio/chunk-7UQWVN52.js.map +1 -0
  525. package/vendor/hyperframes/dist/studio/chunk-EEXSYY5F.js +190 -0
  526. package/vendor/hyperframes/dist/studio/chunk-EEXSYY5F.js.map +1 -0
  527. package/vendor/hyperframes/dist/studio/chunk-FOEID72M.js +1441 -0
  528. package/vendor/hyperframes/dist/studio/chunk-FOEID72M.js.map +1 -0
  529. package/vendor/hyperframes/dist/studio/chunk-PZ5AY32C.js +10 -0
  530. package/vendor/hyperframes/dist/studio/chunk-PZ5AY32C.js.map +1 -0
  531. package/vendor/hyperframes/dist/studio/chunk-TDWGXBUT.js +1103 -0
  532. package/vendor/hyperframes/dist/studio/chunk-TDWGXBUT.js.map +1 -0
  533. package/vendor/hyperframes/dist/studio/domEditingLayers-IEVLO54Z.js +47 -0
  534. package/vendor/hyperframes/dist/studio/domEditingLayers-IEVLO54Z.js.map +1 -0
  535. package/vendor/hyperframes/dist/studio/favicon.svg +14 -0
  536. package/vendor/hyperframes/dist/studio/icons/timeline/audio.svg +7 -0
  537. package/vendor/hyperframes/dist/studio/icons/timeline/captions.svg +5 -0
  538. package/vendor/hyperframes/dist/studio/icons/timeline/composition.svg +12 -0
  539. package/vendor/hyperframes/dist/studio/icons/timeline/image.svg +18 -0
  540. package/vendor/hyperframes/dist/studio/icons/timeline/music.svg +10 -0
  541. package/vendor/hyperframes/dist/studio/icons/timeline/text.svg +3 -0
  542. package/vendor/hyperframes/dist/studio/index.css +165 -0
  543. package/vendor/hyperframes/dist/studio/index.css.map +1 -0
  544. package/vendor/hyperframes/dist/studio/index.d.ts +4296 -0
  545. package/vendor/hyperframes/dist/studio/index.html +23 -0
  546. package/vendor/hyperframes/dist/studio/index.js +99693 -0
  547. package/vendor/hyperframes/dist/studio/index.js.map +1 -0
  548. package/vendor/hyperframes/dist/studio/player.d.ts +30 -0
  549. package/vendor/hyperframes/dist/studio/player.js +10 -0
  550. package/vendor/hyperframes/dist/studio/player.js.map +1 -0
  551. package/vendor/hyperframes/dist/studio/styles/tailwind-preset.d.ts +39 -0
  552. package/vendor/hyperframes/dist/studio/styles/tailwind-preset.js +50 -0
  553. package/vendor/hyperframes/dist/studio/styles/tailwind-preset.js.map +1 -0
  554. package/vendor/hyperframes/dist/studio/styles.css +1 -0
  555. package/vendor/hyperframes/dist/studio/ui.d.ts +296 -0
  556. package/vendor/hyperframes/dist/studio/ui.js +66 -0
  557. package/vendor/hyperframes/dist/studio/ui.js.map +1 -0
  558. package/vendor/hyperframes/dist/studio-api-PNQZRPX5.js +136 -0
  559. package/vendor/hyperframes/dist/studioSelectionClient-IUUPKUHZ.js +25 -0
  560. package/vendor/hyperframes/dist/studioServer-UWRBPIOJ.js +1457 -0
  561. package/vendor/hyperframes/dist/synthesize-K62TIZWI.js +182 -0
  562. package/vendor/hyperframes/dist/system-EZYZNUFD.js +44 -0
  563. package/vendor/hyperframes/dist/telemetry-2YC4SGQU.js +144 -0
  564. package/vendor/hyperframes/dist/telemetry-QMYM4WME.js +95 -0
  565. package/vendor/hyperframes/dist/templates/_shared/AGENTS.md +107 -0
  566. package/vendor/hyperframes/dist/templates/_shared/CLAUDE.md +107 -0
  567. package/vendor/hyperframes/dist/templates/blank/index.html +55 -0
  568. package/vendor/hyperframes/dist/templates/from-file/index.html +75 -0
  569. package/vendor/hyperframes/dist/templates/warm-grain/compositions/captions.html +154 -0
  570. package/vendor/hyperframes/dist/templates/warm-grain/compositions/graphics.html +162 -0
  571. package/vendor/hyperframes/dist/templates/warm-grain/compositions/intro.html +87 -0
  572. package/vendor/hyperframes/dist/templates/warm-grain/index.html +264 -0
  573. package/vendor/hyperframes/dist/templates/warm-grain/registry-item.json +34 -0
  574. package/vendor/hyperframes/dist/timeline-77Z3BBQM.js +1377 -0
  575. package/vendor/hyperframes/dist/tokens-OBGTYE4Z.js +92 -0
  576. package/vendor/hyperframes/dist/transcribe-7NVFHCGA.js +387 -0
  577. package/vendor/hyperframes/dist/transcribe-BNSJRR4M.js +62 -0
  578. package/vendor/hyperframes/dist/tts-P3LIPIL5.js +234 -0
  579. package/vendor/hyperframes/dist/updateCheck-UDWIJ4ZQ.js +39 -0
  580. package/vendor/hyperframes/dist/upgrade-CT3CPO3E.js +231 -0
  581. package/vendor/hyperframes/dist/urlDownloader-H6WRI2UE.js +39 -0
  582. package/vendor/hyperframes/dist/usage-EC3KFUIJ.js +2139 -0
  583. package/vendor/hyperframes/dist/validate-65QDZJWZ.js +70 -0
  584. package/vendor/hyperframes/dist/video-FGSGKRSV.js +261 -0
  585. package/vendor/hyperframes/dist/workspaceBuildCheck-TOLGAI4T.js +133 -0
  586. package/vendor/hyperframes/package.json +80 -0
@@ -0,0 +1,4079 @@
1
+ import { createRequire as __hf_createRequire } from "node:module";
2
+ import { fileURLToPath as __hf_fileURLToPath } from "node:url";
3
+ import { dirname as __hf_dirname } from "node:path";
4
+ var require = __hf_createRequire(import.meta.url);
5
+ var __filename = __hf_fileURLToPath(import.meta.url);
6
+ var __dirname = __hf_dirname(__filename);
7
+ import {
8
+ createCaptureWatchdog,
9
+ runWithWatchdog
10
+ } from "./chunk-UYEZTK4U.js";
11
+ import {
12
+ CAPTURE_USER_AGENT,
13
+ downloadAndRewriteFonts,
14
+ downloadAssets,
15
+ isPrivateUrl,
16
+ mergeDrops,
17
+ noDrops,
18
+ safeFetch,
19
+ totalDrops
20
+ } from "./chunk-MWB3ZIFM.js";
21
+ import {
22
+ captureProtocolTimeoutMs,
23
+ isDegradableEvaluateTimeoutError,
24
+ isNavigationTimeoutError,
25
+ withRemainingBudget
26
+ } from "./chunk-AWNBM3MZ.js";
27
+ import {
28
+ CaptureDirRefusedError,
29
+ NavigationDeadlineError,
30
+ ensureCaptureDirSync,
31
+ writeCaptureFileSync
32
+ } from "./chunk-MRF4AMKD.js";
33
+ import {
34
+ launchManagedBrowser
35
+ } from "./chunk-7HG4BFIN.js";
36
+ import "./chunk-XS4EJSKU.js";
37
+ import {
38
+ writeNewFileSync
39
+ } from "./chunk-4YYJOFKS.js";
40
+ import {
41
+ applyDotEnv
42
+ } from "./chunk-NGKM64BP.js";
43
+ import "./chunk-IGLEPPTN.js";
44
+ import {
45
+ createCaptureDownloadBudget,
46
+ readBoundedResponse
47
+ } from "./chunk-AWDFDFI3.js";
48
+ import {
49
+ diag
50
+ } from "./chunk-VWG7OQKU.js";
51
+ import "./chunk-PBN6GBJE.js";
52
+ import "./chunk-K2FTXJYH.js";
53
+ import "./chunk-2YN64IN5.js";
54
+ import "./chunk-AVUUN2XD.js";
55
+ import "./chunk-DPVXBVLL.js";
56
+ import "./chunk-Z6B37SNM.js";
57
+ import "./chunk-HWKCIWQT.js";
58
+ import "./chunk-VC76R6W2.js";
59
+ import "./chunk-OFQWTKPW.js";
60
+ import "./chunk-TWWSYAKJ.js";
61
+ import "./chunk-ZJUKFZ2P.js";
62
+ import "./chunk-HBBJFK6I.js";
63
+ import "./chunk-PJ3HDUCY.js";
64
+ import "./chunk-TQFG7HTI.js";
65
+ import {
66
+ parseHTML
67
+ } from "./chunk-GK4AIFIH.js";
68
+ import "./chunk-2PSPARWP.js";
69
+ import {
70
+ normalizeErrorMessage
71
+ } from "./chunk-KNU5COPB.js";
72
+ import {
73
+ loadOptionalPackage
74
+ } from "./chunk-XT5P4QMO.js";
75
+ import "./chunk-6BEFEWFB.js";
76
+ import "./chunk-3GXQPCJC.js";
77
+ import "./chunk-RQVDTPGD.js";
78
+ import "./chunk-PMRBLBUP.js";
79
+ import "./chunk-M2WBOQ2Q.js";
80
+
81
+ // src/capture/partialCapture.ts
82
+ import { join } from "path";
83
+ function serializeTokensForCapture(tokens) {
84
+ const tokensForDisk = {
85
+ ...tokens,
86
+ svgs: tokens.svgs.map(({ outerHTML: _, ...rest }) => rest)
87
+ };
88
+ return JSON.stringify(tokensForDisk, null, 2);
89
+ }
90
+ function createPartialCaptureState(opts) {
91
+ return {
92
+ tokens: {
93
+ title: "",
94
+ description: "",
95
+ cssVariables: {},
96
+ fonts: [],
97
+ colors: [],
98
+ headings: [],
99
+ ctas: [],
100
+ svgs: [],
101
+ sections: []
102
+ },
103
+ designStyles: {
104
+ typography: [],
105
+ spacing: { observed: [], baseUnit: 0 },
106
+ radius: [],
107
+ shadows: [],
108
+ buttons: [],
109
+ cards: [],
110
+ nav: null
111
+ },
112
+ extracted: {
113
+ headHtml: "",
114
+ bodyHtml: "",
115
+ cssomRules: "",
116
+ htmlAttrs: "",
117
+ viewportWidth: opts.viewportWidth ?? 1920,
118
+ viewportHeight: opts.viewportHeight ?? 1080,
119
+ fullPageHeight: 0
120
+ },
121
+ pageHtml: "<!doctype html>\n<html><head></head><body></body></html>\n",
122
+ screenshots: [],
123
+ assets: [],
124
+ dropped: noDrops(),
125
+ warnings: []
126
+ };
127
+ }
128
+ function writePartialCaptureBundle(opts, state, lastPhase) {
129
+ const hostname = new URL(opts.url).hostname.replace(/^www\./, "");
130
+ const extractedDir = ensureCaptureDirSync(opts.outputDir, join(opts.outputDir, "extracted"));
131
+ writeCaptureFileSync(join(extractedDir, "tokens.json"), serializeTokensForCapture(state.tokens));
132
+ writeCaptureFileSync(
133
+ join(extractedDir, "design-styles.json"),
134
+ JSON.stringify(state.designStyles, null, 2)
135
+ );
136
+ writeCaptureFileSync(join(extractedDir, "page.html"), state.pageHtml);
137
+ const metaPath = join(opts.outputDir, "meta.json");
138
+ try {
139
+ writeCaptureFileSync(
140
+ metaPath,
141
+ JSON.stringify({ id: hostname + "-video", name: hostname, partial: true }, null, 2),
142
+ { flag: "wx" }
143
+ );
144
+ } catch (err) {
145
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
146
+ if (code !== "EEXIST") throw err;
147
+ }
148
+ return {
149
+ ok: false,
150
+ projectDir: opts.outputDir,
151
+ url: opts.url,
152
+ httpStatus: null,
153
+ title: state.tokens.title,
154
+ extracted: state.extracted,
155
+ screenshots: state.screenshots,
156
+ tokens: state.tokens,
157
+ assets: state.assets,
158
+ dropped: state.dropped,
159
+ warnings: [
160
+ ...state.warnings,
161
+ "Capture deadline reached; returning the partial capture bundle."
162
+ ],
163
+ lastPhase
164
+ };
165
+ }
166
+
167
+ // src/capture/lottieValidation.ts
168
+ import AdmZip from "adm-zip";
169
+
170
+ // src/capture/lottieArchivePreflight.ts
171
+ function preflightLottieArchive(bytes) {
172
+ const end = findDirectoryEnd(bytes);
173
+ if (end < 0) return false;
174
+ if (!unambiguousFooter(bytes, end)) return false;
175
+ const count = bytes.readUInt16LE(end + 10);
176
+ if (count > 256 || bytes.readUInt16LE(end + 8) !== count) return false;
177
+ if (bytes.readUInt32LE(end + 4) !== 0) return false;
178
+ let offset = bytes.readUInt32LE(end + 16);
179
+ const directoryEnd = offset + bytes.readUInt32LE(end + 12);
180
+ if (directoryEnd > end) return false;
181
+ for (let i = 0; i < count; i++) {
182
+ const next = nextBoundedEntry(bytes, offset, directoryEnd);
183
+ if (next === null) return false;
184
+ offset = next;
185
+ }
186
+ return offset === directoryEnd;
187
+ }
188
+ function unambiguousFooter(bytes, end) {
189
+ const signature = Buffer.from([80, 75, 5, 6]);
190
+ if (bytes.lastIndexOf(signature) !== end) return false;
191
+ const preceding = bytes.subarray(Math.max(0, end - 76), end);
192
+ return ![
193
+ signature,
194
+ Buffer.from([80, 75, 6, 6]),
195
+ Buffer.from([80, 75, 6, 7])
196
+ ].some((marker) => preceding.includes(marker));
197
+ }
198
+ function findDirectoryEnd(bytes) {
199
+ for (let at = bytes.length - 22; at >= Math.max(0, bytes.length - 65557); at--) {
200
+ if (bytes.readUInt32LE(at) === 101010256 && at + 22 + bytes.readUInt16LE(at + 20) === bytes.length)
201
+ return at;
202
+ }
203
+ return -1;
204
+ }
205
+ function nextBoundedEntry(bytes, offset, end) {
206
+ if (offset + 46 > end || bytes.readUInt32LE(offset) !== 33639248) return null;
207
+ const length = bytes.readUInt16LE(offset + 28);
208
+ const next = offset + 46 + length + bytes.readUInt16LE(offset + 30) + bytes.readUInt16LE(offset + 32);
209
+ if (length > 512 || next > end) return null;
210
+ const name = bytes.toString("utf8", offset + 46, offset + 46 + length);
211
+ return safeLottieArchivePath(name) ? next : null;
212
+ }
213
+ function safeLottieArchivePath(path) {
214
+ if (path.length > 512 || path.split("/").length > 16) return false;
215
+ if (path.startsWith("/") || /[\\:]/.test(path)) return false;
216
+ if (Array.from(path).some((char) => char.charCodeAt(0) < 32)) return false;
217
+ return path.split("/").every((segment) => segment !== ".." && segment !== ".");
218
+ }
219
+
220
+ // src/capture/lottieValidation.ts
221
+ var MAX_LOTTIE_BYTES = 10 * 1024 * 1024;
222
+ function readLottieArchive(bytes) {
223
+ if (bytes.length > MAX_LOTTIE_BYTES) return null;
224
+ if (!preflightLottieArchive(bytes)) return null;
225
+ try {
226
+ const zip = new AdmZip(bytes);
227
+ if (zip.getEntryCount() > 256) return null;
228
+ const entries = zip.getEntries();
229
+ if (entries.length > 256) return null;
230
+ if (!validArchiveEntries(entries)) return null;
231
+ const animation = entries.find(
232
+ (entry) => /^(?:a|animations)\/[^/]+\.json$/.test(entry.entryName)
233
+ );
234
+ if (!animation || animation.header.size <= 0) return null;
235
+ const data = animation.getData();
236
+ if (data.length !== animation.header.size || data.length > MAX_LOTTIE_BYTES) return null;
237
+ return data.toString("utf8");
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ function validArchiveEntries(entries) {
243
+ let total = 0;
244
+ for (const entry of entries) {
245
+ if (!safeLottieArchivePath(entry.entryName)) return false;
246
+ const size = entry.header.size;
247
+ if (!Number.isSafeInteger(size) || size < 0 || size > MAX_LOTTIE_BYTES) return false;
248
+ total += size;
249
+ if (total > 50 * 1024 * 1024) return false;
250
+ }
251
+ return true;
252
+ }
253
+ function record(value) {
254
+ return typeof value === "object" && value !== null && !Array.isArray(value);
255
+ }
256
+ function dimension(value) {
257
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= 16384;
258
+ }
259
+ function validLottieJson(source) {
260
+ if (Buffer.byteLength(source) > MAX_LOTTIE_BYTES) return false;
261
+ try {
262
+ const value = JSON.parse(source);
263
+ if (!record(value) || !dimension(value.w) || !dimension(value.h)) return false;
264
+ if (value.w * value.h > 4e7 || !Array.isArray(value.layers)) return false;
265
+ return validLottieTiming(value) && boundedLottieTree(value);
266
+ } catch {
267
+ return false;
268
+ }
269
+ }
270
+ function validLottieTiming(value) {
271
+ if (value.fr !== void 0 && (typeof value.fr !== "number" || value.fr <= 0 || value.fr > 240))
272
+ return false;
273
+ if (![value.ip, value.op].every(validOptionalFrame)) return false;
274
+ const start = numberOr(value.ip, 0);
275
+ const end = numberOr(value.op, 0);
276
+ const rate = numberOr(value.fr, 30);
277
+ const duration = (end - start) / rate;
278
+ return Number.isFinite(duration) && duration >= 0 && duration <= 3600;
279
+ }
280
+ function numberOr(value, fallback) {
281
+ return typeof value === "number" ? value : fallback;
282
+ }
283
+ function validOptionalFrame(frame) {
284
+ return frame === void 0 || typeof frame === "number" && Number.isFinite(frame) && Math.abs(frame) <= 1e7;
285
+ }
286
+ function jsonChildren(value) {
287
+ return value !== null && typeof value === "object" ? Object.entries(value) : [];
288
+ }
289
+ function boundedLottieTree(root) {
290
+ const pending = [{ value: root, depth: 0 }];
291
+ let count = 0;
292
+ while (pending.length) {
293
+ const item = pending.pop();
294
+ if (++count > 1e5 || item.depth > 128) return false;
295
+ if (typeof item.value === "number" && !Number.isFinite(item.value)) return false;
296
+ for (const [key, value] of jsonChildren(item.value)) {
297
+ if (!validLottieEntry(key, value)) return false;
298
+ if (count + pending.length >= 1e5) return false;
299
+ pending.push({ value, depth: item.depth + 1 });
300
+ }
301
+ }
302
+ return true;
303
+ }
304
+ function validLottieEntry(key, value) {
305
+ if (["__proto__", "constructor", "prototype"].includes(key)) return false;
306
+ return key !== "layers" || Array.isArray(value) && value.every(record);
307
+ }
308
+
309
+ // src/capture/lottieDiscovery.ts
310
+ async function discoverLottieResponse(response, budget, timeoutMs = 1e4) {
311
+ const url = response.url();
312
+ const pathname = new URL(url).pathname;
313
+ if (pathname.endsWith(".lottie")) return { url };
314
+ const contentType = response.headers()["content-type"] ?? "";
315
+ if (!pathname.endsWith(".json") && !/application\/json|text\/plain/i.test(contentType))
316
+ return null;
317
+ if (budget.remainingBytes <= 0) return null;
318
+ const fetched = await safeFetch(url, {
319
+ signal: AbortSignal.timeout(timeoutMs),
320
+ headers: { "User-Agent": CAPTURE_USER_AGENT }
321
+ });
322
+ if (!fetched?.ok) return null;
323
+ const bytes = await readBoundedResponse(fetched, 5e6, budget);
324
+ if (!bytes) return null;
325
+ const source = bytes.toString("utf8");
326
+ if (!validLottieJson(source)) return null;
327
+ const data = JSON.parse(source);
328
+ if (!hasDiscoveryFields(data)) return null;
329
+ return { url, data, dataBudget: budget };
330
+ }
331
+ function hasDiscoveryFields(data) {
332
+ if (data === null || typeof data !== "object") return false;
333
+ return ["v", "ip", "op", "layers", "w", "h", "fr"].every((key) => key in data);
334
+ }
335
+ var LottieDiscovery = class {
336
+ candidates = /* @__PURE__ */ new Map();
337
+ closed = false;
338
+ collect(response) {
339
+ if (this.closed) return;
340
+ const url = response.url();
341
+ if (this.candidates.has(url)) return;
342
+ const contentType = response.headers()["content-type"] ?? "";
343
+ const priority = candidatePriority(url, contentType);
344
+ if (priority === null) return;
345
+ if (this.candidates.size >= 32) {
346
+ const worst = [...this.candidates.values()].sort((a, b) => b.priority - a.priority).find((candidate) => candidate.priority > priority);
347
+ if (!worst) return;
348
+ this.candidates.delete(worst.url);
349
+ }
350
+ this.candidates.set(url, { url, contentType, priority });
351
+ }
352
+ async run(sharedBudget, remainingMs) {
353
+ if (this.closed) return [];
354
+ this.closed = true;
355
+ const candidates = [...this.candidates.values()].sort((a, b) => a.priority - b.priority);
356
+ this.candidates.clear();
357
+ const deadline = Date.now() + Math.min(1e4, remainingMs());
358
+ const budget = { remainingBytes: Math.min(20 * 1024 * 1024, sharedBudget.remainingBytes) };
359
+ const found = [];
360
+ for (const candidate of candidates) {
361
+ const timeout = Math.floor(Math.min(deadline - Date.now(), remainingMs()));
362
+ if (timeout <= 0 || budget.remainingBytes <= 0 || found.length >= 10) break;
363
+ const before = budget.remainingBytes;
364
+ try {
365
+ const result = await discoverLottieResponse(
366
+ { url: () => candidate.url, headers: () => ({ "content-type": candidate.contentType }) },
367
+ budget,
368
+ timeout
369
+ );
370
+ if (result) found.push({ ...result, dataBudget: sharedBudget });
371
+ } catch {
372
+ } finally {
373
+ sharedBudget.remainingBytes -= before - budget.remainingBytes;
374
+ }
375
+ }
376
+ return found;
377
+ }
378
+ };
379
+ function candidatePriority(url, contentType) {
380
+ const path = new URL(url).pathname;
381
+ if (path.endsWith(".lottie")) return 0;
382
+ if (path.endsWith(".json")) return 1;
383
+ return /application\/json|text\/plain/i.test(contentType) ? 2 : null;
384
+ }
385
+
386
+ // src/capture/captureAttempt.ts
387
+ import { existsSync as existsSync5 } from "fs";
388
+ import { join as join10 } from "path";
389
+
390
+ // src/capture/lazyScrollForCapture.ts
391
+ var LAZY_SCROLL_STEP_DELAY_MS = 400;
392
+ var LAZY_SCROLL_MAX_IMAGE_WAIT_MS = 5e3;
393
+ var LAZY_SCROLL_BOTTOM_SETTLE_MS = 800;
394
+ var LAZY_SCROLL_IMAGE_POLL_MS = 250;
395
+ async function evaluateWithinBudget(page, expression, deadline, label) {
396
+ return withRemainingBudget(page.evaluate(expression), deadline - Date.now(), label);
397
+ }
398
+ async function settleAfterScroll(page, deadline, sleep) {
399
+ const bottomSettleMs = Math.min(LAZY_SCROLL_BOTTOM_SETTLE_MS, Math.max(0, deadline - Date.now()));
400
+ if (bottomSettleMs > 0) {
401
+ await evaluateWithinBudget(
402
+ page,
403
+ `window.scrollTo(0, document.body.scrollHeight)`,
404
+ deadline,
405
+ "lazy-scroll-bottom"
406
+ );
407
+ await sleep(bottomSettleMs);
408
+ }
409
+ const imageDeadline = Math.min(deadline, Date.now() + LAZY_SCROLL_MAX_IMAGE_WAIT_MS);
410
+ while (Date.now() < imageDeadline) {
411
+ const pending = await evaluateWithinBudget(
412
+ page,
413
+ `Array.from(document.images).filter(function(img) { return !img.complete; }).length`,
414
+ imageDeadline,
415
+ "lazy-scroll-image-pending"
416
+ );
417
+ if (!(pending > 0)) {
418
+ break;
419
+ }
420
+ const waitMs = Math.min(LAZY_SCROLL_IMAGE_POLL_MS, imageDeadline - Date.now());
421
+ if (waitMs <= 0) {
422
+ break;
423
+ }
424
+ await sleep(waitMs);
425
+ }
426
+ if (deadline - Date.now() > 0) {
427
+ await evaluateWithinBudget(page, `window.scrollTo(0, 0)`, deadline, "lazy-scroll-reset");
428
+ }
429
+ }
430
+ async function lazyScrollForCapture(page, budgetMs, opts = {}) {
431
+ if (budgetMs <= 0) {
432
+ return { steps: 0, timedOut: false, degraded: false };
433
+ }
434
+ const stepDelayMs = opts.stepDelayMs ?? LAZY_SCROLL_STEP_DELAY_MS;
435
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
436
+ const deadline = Date.now() + budgetMs;
437
+ let steps = 0;
438
+ let timedOut = false;
439
+ let degraded = false;
440
+ try {
441
+ while (Date.now() < deadline) {
442
+ const state = await evaluateWithinBudget(
443
+ page,
444
+ `(() => {
445
+ var y = window.scrollY || window.pageYOffset || 0;
446
+ var view = window.innerHeight || 0;
447
+ var height = document.body ? document.body.scrollHeight : 0;
448
+ var next = Math.min(y + view * 0.7, Math.max(0, height - view));
449
+ var atBottom = height <= view + 2 || next <= y + 1;
450
+ window.scrollTo(0, atBottom ? height : next);
451
+ return { atBottom: atBottom };
452
+ })()`,
453
+ deadline,
454
+ "lazy-scroll-step"
455
+ );
456
+ steps += 1;
457
+ if (state.atBottom) {
458
+ break;
459
+ }
460
+ const remaining = deadline - Date.now();
461
+ if (remaining <= 0) {
462
+ timedOut = true;
463
+ break;
464
+ }
465
+ await sleep(Math.min(stepDelayMs, remaining));
466
+ }
467
+ if (Date.now() >= deadline) {
468
+ timedOut = true;
469
+ }
470
+ if (deadline - Date.now() > 0) {
471
+ await settleAfterScroll(page, deadline, sleep);
472
+ }
473
+ } catch (err) {
474
+ if (!isDegradableEvaluateTimeoutError(err)) {
475
+ throw err;
476
+ }
477
+ degraded = true;
478
+ timedOut = true;
479
+ opts.onWarning?.("lazy-scroll evaluate timed out; continuing with current page state");
480
+ if (deadline - Date.now() > 0) {
481
+ try {
482
+ await evaluateWithinBudget(page, `window.scrollTo(0, 0)`, deadline, "lazy-scroll-recovery");
483
+ } catch {
484
+ }
485
+ }
486
+ }
487
+ return { steps, timedOut, degraded };
488
+ }
489
+
490
+ // src/capture/animationCataloger.ts
491
+ async function setupAnimationCapture(page) {
492
+ await page.evaluateOnNewDocument(`
493
+ window.__hf_io_targets = [];
494
+ var OrigIO = window.IntersectionObserver;
495
+ window.IntersectionObserver = function(callback, options) {
496
+ var observer = new OrigIO(callback, options);
497
+ var origObserve = observer.observe.bind(observer);
498
+ observer.observe = function(target) {
499
+ var sel = target.id ? '#' + target.id : target.tagName.toLowerCase();
500
+ if (target.className && typeof target.className === 'string') {
501
+ var cls = Array.from(target.classList).slice(0, 2).join('.');
502
+ if (cls) sel += '.' + cls;
503
+ }
504
+ try {
505
+ var rect = target.getBoundingClientRect();
506
+ window.__hf_io_targets.push({
507
+ selector: sel,
508
+ rect: { top: Math.round(rect.top + window.scrollY), height: Math.round(rect.height), width: Math.round(rect.width) }
509
+ });
510
+ } catch(e) {}
511
+ return origObserve(target);
512
+ };
513
+ return observer;
514
+ };
515
+ window.IntersectionObserver.prototype = OrigIO.prototype;
516
+ `);
517
+ }
518
+ async function startCdpAnimationCapture(page) {
519
+ const cdp = await page.createCDPSession();
520
+ await cdp.send("Animation.enable");
521
+ const animations = [];
522
+ cdp.on("Animation.animationStarted", (event) => {
523
+ animations.push({
524
+ id: event.animation.id,
525
+ name: event.animation.name || "",
526
+ type: event.animation.type,
527
+ duration: event.animation.source?.duration,
528
+ delay: event.animation.source?.delay
529
+ });
530
+ });
531
+ return { cdp, animations };
532
+ }
533
+ var EMPTY_ANIMATION_SCAN = {
534
+ webAnimations: [],
535
+ cssDeclarations: [],
536
+ scrollTargets: [],
537
+ canvasCount: 0
538
+ };
539
+ async function collectAnimationCatalog(page, cdpAnimations, cdp, opts = {}) {
540
+ const scrollBudgetMs = opts.scrollBudgetMs ?? 8e3;
541
+ const evaluateBudgetMs = opts.evaluateBudgetMs ?? 15e3;
542
+ const scroll = await lazyScrollForCapture(page, scrollBudgetMs);
543
+ let timedOut = scroll.degraded || scroll.timedOut;
544
+ let result = EMPTY_ANIMATION_SCAN;
545
+ try {
546
+ result = await withRemainingBudget(
547
+ page.evaluate(`(() => {
548
+ var webAnimations = [];
549
+ var cssDeclarations = [];
550
+
551
+ try {
552
+ var anims = document.getAnimations();
553
+ webAnimations = anims.map(function(anim) {
554
+ var r = { type: anim.constructor.name, playState: anim.playState, animationName: anim.animationName || null };
555
+ var effect = anim.effect;
556
+ if (effect && effect.target) {
557
+ var t = effect.target;
558
+ r.targetSelector = t.id ? '#' + t.id : t.tagName.toLowerCase();
559
+ if (t.className && typeof t.className === 'string') {
560
+ var cls = Array.from(t.classList).slice(0, 3).join('.');
561
+ if (cls) r.targetSelector += '.' + cls;
562
+ }
563
+ try { r.targetRect = t.getBoundingClientRect().toJSON(); } catch(e) {}
564
+ }
565
+ if (effect && typeof effect.getKeyframes === 'function') {
566
+ try { r.keyframes = effect.getKeyframes(); } catch(e) {}
567
+ }
568
+ if (effect && typeof effect.getComputedTiming === 'function') {
569
+ try {
570
+ var timing = effect.getComputedTiming();
571
+ r.timing = { duration: timing.duration, delay: timing.delay, iterations: timing.iterations, easing: timing.easing, direction: timing.direction };
572
+ } catch(e) {}
573
+ }
574
+ return r;
575
+ });
576
+ } catch(e) {}
577
+
578
+ var allEls = document.querySelectorAll('*');
579
+ for (var i = 0; i < allEls.length && i < 5000; i++) {
580
+ var el = allEls[i];
581
+ try {
582
+ var cs = getComputedStyle(el);
583
+ var hasAnim = cs.animationName && cs.animationName !== 'none';
584
+ var hasTrans = cs.transitionProperty && cs.transitionProperty !== 'all' && cs.transitionProperty !== 'none' && cs.transitionDuration !== '0s';
585
+ if (hasAnim || hasTrans) {
586
+ var sel = el.id ? '#' + el.id : el.tagName.toLowerCase();
587
+ if (el.className && typeof el.className === 'string') {
588
+ var cls = Array.from(el.classList).slice(0, 2).join('.');
589
+ if (cls) sel += '.' + cls;
590
+ }
591
+ var entry = { selector: sel };
592
+ if (hasAnim) entry.animation = { name: cs.animationName, duration: cs.animationDuration, easing: cs.animationTimingFunction };
593
+ if (hasTrans) entry.transition = { property: cs.transitionProperty, duration: cs.transitionDuration };
594
+ cssDeclarations.push(entry);
595
+ }
596
+ } catch(e) {}
597
+ }
598
+
599
+ var scrollTargets = (window.__hf_io_targets || []).map(function(t) {
600
+ return { selector: t.selector, rect: t.rect };
601
+ });
602
+
603
+ var canvasCount = document.querySelectorAll('canvas').length;
604
+
605
+ return { webAnimations: webAnimations, cssDeclarations: cssDeclarations, scrollTargets: scrollTargets, canvasCount: canvasCount };
606
+ })()`),
607
+ evaluateBudgetMs,
608
+ "animation-catalog"
609
+ );
610
+ } catch (err) {
611
+ if (!isDegradableEvaluateTimeoutError(err)) {
612
+ throw err;
613
+ }
614
+ timedOut = true;
615
+ }
616
+ try {
617
+ await cdp.send("Animation.disable");
618
+ } catch {
619
+ }
620
+ return {
621
+ catalog: {
622
+ webAnimations: result.webAnimations,
623
+ cssDeclarations: result.cssDeclarations,
624
+ scrollTargets: result.scrollTargets,
625
+ cdpAnimations,
626
+ summary: {
627
+ webAnimations: result.webAnimations.length,
628
+ cssDeclarations: result.cssDeclarations.length,
629
+ scrollTargets: result.scrollTargets.length,
630
+ cdpAnimations: cdpAnimations.length,
631
+ canvases: result.canvasCount
632
+ }
633
+ },
634
+ timedOut
635
+ };
636
+ }
637
+
638
+ // src/capture/scaffolding.ts
639
+ import { existsSync, readFileSync } from "fs";
640
+ import { join as join2, resolve } from "path";
641
+ function loadEnvFile(startDir) {
642
+ try {
643
+ let dir = resolve(startDir);
644
+ for (let i = 0; i < 5; i++) {
645
+ const envPath = resolve(dir, ".env");
646
+ try {
647
+ applyDotEnv(readFileSync(envPath, "utf-8"), process.env);
648
+ break;
649
+ } catch {
650
+ dir = resolve(dir, "..");
651
+ }
652
+ }
653
+ } catch {
654
+ }
655
+ }
656
+ async function generateProjectScaffold(outputDir, url, tokens, animationCatalog, hasScreenshots, hasLotties, hasShaders, catalogedAssets, progress, warnings, detectedLibraries) {
657
+ const metaPath = join2(outputDir, "meta.json");
658
+ if (!existsSync(metaPath)) {
659
+ const hostname = new URL(url).hostname.replace(/^www\./, "");
660
+ writeNewFileSync(
661
+ metaPath,
662
+ JSON.stringify({ id: hostname + "-video", name: tokens.title || hostname }, null, 2)
663
+ );
664
+ }
665
+ try {
666
+ const { generateAgentPrompt } = await import("./agentPromptGenerator-VIIPZCJ3.js");
667
+ generateAgentPrompt(
668
+ outputDir,
669
+ url,
670
+ tokens,
671
+ animationCatalog,
672
+ hasScreenshots,
673
+ hasLotties,
674
+ hasShaders,
675
+ catalogedAssets,
676
+ detectedLibraries
677
+ );
678
+ progress("agent", "AGENTS.md + CLAUDE.md generated");
679
+ } catch (err) {
680
+ warnings.push(`AGENTS.md/CLAUDE.md generation failed: ${err}`);
681
+ }
682
+ }
683
+
684
+ // src/capture/types.ts
685
+ var CAPTURE_PHASE_SCHEMA = "hyperframes.capture.phase.v1";
686
+
687
+ // src/capture/browserLaunchArgs.ts
688
+ function captureBrowserArgs(disableWebgl, viewportWidth, viewportHeight) {
689
+ return [
690
+ "--no-sandbox",
691
+ "--disable-dev-shm-usage",
692
+ ...disableWebgl ? ["--disable-gpu"] : ["--enable-webgl", "--ignore-gpu-blocklist", "--use-gl=angle", "--use-angle=swiftshader"],
693
+ "--disable-blink-features=AutomationControlled",
694
+ "--disable-background-timer-throttling",
695
+ "--disable-renderer-backgrounding",
696
+ `--window-size=${viewportWidth},${viewportHeight}`
697
+ ];
698
+ }
699
+
700
+ // src/capture/pageBlockDetection.ts
701
+ function detectBlockedPage(evidence) {
702
+ const isMinimalDom = evidence.bodyChildCount <= 5 && evidence.textLength < 500;
703
+ const hasBlockedStatus = evidence.httpStatus === 401 || evidence.httpStatus === 403 || evidence.httpStatus === 429;
704
+ const hasBlockedTitle = /^(?:(?:error\s*)?(?:401|403|429)(?:\s*(?:[-:—]\s*)?(?:forbidden|unauthorized|access denied|too many requests))?|forbidden|access denied|attention required!?|just a moment(?:\.{3})?)(?:\s*[|—-]\s*(?:cloudflare|sucuri website firewall))?$/i.test(
705
+ evidence.title.trim()
706
+ );
707
+ if (!isMinimalDom || !evidence.hasChallengeElement && !hasBlockedStatus && !hasBlockedTitle) {
708
+ return void 0;
709
+ }
710
+ const statusDetail = evidence.httpStatus === null ? "no HTTP status" : `HTTP ${evidence.httpStatus}`;
711
+ return `Website capture blocked: the loaded page matched an access-protection response (${statusDetail}, title ${JSON.stringify(evidence.title)}, ${evidence.textLength} text chars). The site may reject automated or data-center traffic; retry from an allowed network or provide source assets directly.`;
712
+ }
713
+
714
+ // src/capture/navigateForCapture.ts
715
+ var NETWORK_IDLE_ATTEMPT_MS = 3e4;
716
+ function networkIdleAttemptTimeoutMs(totalTimeoutMs) {
717
+ return Math.min(NETWORK_IDLE_ATTEMPT_MS, Math.max(0, totalTimeoutMs));
718
+ }
719
+ async function navigateForCapture(page, url, totalTimeoutMs) {
720
+ const networkIdleTimeoutMs = networkIdleAttemptTimeoutMs(totalTimeoutMs);
721
+ try {
722
+ const response2 = await page.goto(url, {
723
+ waitUntil: "networkidle2",
724
+ timeout: networkIdleTimeoutMs
725
+ });
726
+ return {
727
+ response: response2,
728
+ waitUntil: "networkidle2",
729
+ networkIdleTimeoutMs,
730
+ fellBackFromNetworkIdle: false
731
+ };
732
+ } catch (err) {
733
+ if (!isNavigationTimeoutError(err) || networkIdleTimeoutMs >= totalTimeoutMs) {
734
+ throw err;
735
+ }
736
+ }
737
+ const fallbackTimeoutMs = totalTimeoutMs - networkIdleTimeoutMs;
738
+ const response = await page.goto(url, {
739
+ waitUntil: "domcontentloaded",
740
+ timeout: fallbackTimeoutMs
741
+ });
742
+ return {
743
+ response,
744
+ waitUntil: "domcontentloaded",
745
+ networkIdleTimeoutMs,
746
+ fellBackFromNetworkIdle: true
747
+ };
748
+ }
749
+
750
+ // src/capture/responseRecord.ts
751
+ import { join as join3 } from "path";
752
+ var RESPONSE_RECORD_FILENAME = "response.json";
753
+ function writeResponseRecord(extractedDir, record2) {
754
+ writeCaptureFileSync(
755
+ join3(extractedDir, RESPONSE_RECORD_FILENAME),
756
+ JSON.stringify(record2, null, 2),
757
+ "utf-8"
758
+ );
759
+ }
760
+
761
+ // src/capture/navigationPhase.ts
762
+ import { join as join4 } from "path";
763
+ async function navigatePage(context) {
764
+ let navigation;
765
+ try {
766
+ navigation = await navigateForCapture(context.page1, context.url, context.timeout);
767
+ } catch (err) {
768
+ if (isNavigationTimeoutError(err)) {
769
+ let webglObserved = false;
770
+ try {
771
+ webglObserved = Boolean(await context.page1.evaluate("Boolean(window.__hfWebglSeen)"));
772
+ } catch {
773
+ }
774
+ throw new NavigationDeadlineError(err, webglObserved);
775
+ }
776
+ throw err;
777
+ }
778
+ if (navigation.fellBackFromNetworkIdle) {
779
+ context.warnings.push(
780
+ `networkidle2 timed out after ${navigation.networkIdleTimeoutMs}ms; continued with domcontentloaded`
781
+ );
782
+ context.progress(
783
+ "warn",
784
+ `networkidle2 timed out after ${navigation.networkIdleTimeoutMs}ms; continuing with domcontentloaded`
785
+ );
786
+ }
787
+ context.postNavigationDeadline = Date.now() + context.budgetMs;
788
+ await new Promise((resolve2) => setTimeout(resolve2, context.settleTime));
789
+ return navigation;
790
+ }
791
+ async function checkPageContent(context) {
792
+ try {
793
+ return await withRemainingBudget(
794
+ context.page1.evaluate(`(() => {
795
+ var text = (document.body && document.body.innerText || "").trim();
796
+ var title = document.title || "";
797
+ var hasCfTurnstile = !!document.querySelector('.cf-turnstile, [data-sitekey], iframe[src*="challenges.cloudflare.com"], #challenge-running, #challenge-form');
798
+ var bodyChildCount = document.body ? document.body.children.length : 0;
799
+ return { textLength: text.length, title: title, hasChallengeElement: hasCfTurnstile, bodyChildCount: bodyChildCount };
800
+ })()`),
801
+ Math.min(5e3, context.remainingMs()),
802
+ "content-check"
803
+ );
804
+ } catch (err) {
805
+ if (!isDegradableEvaluateTimeoutError(err)) throw err;
806
+ context.contentCheckTimedOut = true;
807
+ const message = "post-navigation content check timed out; continuing with HTTP-status blocked-page detection only";
808
+ context.warnings.push(message);
809
+ context.progress("warn", message);
810
+ return context.pageContentCheck;
811
+ }
812
+ }
813
+ function persistNavigationResult(context, navigationResponse, pageContentCheck) {
814
+ context.httpStatus = navigationResponse ? navigationResponse.status() : null;
815
+ if (context.canWrite()) {
816
+ writeResponseRecord(join4(context.outputDir, "extracted"), { status: context.httpStatus });
817
+ }
818
+ const blockedReason = detectBlockedPage({
819
+ httpStatus: context.httpStatus,
820
+ ...context.contentCheckTimedOut ? { title: "", textLength: 0, bodyChildCount: 0, hasChallengeElement: false } : pageContentCheck
821
+ });
822
+ if (blockedReason) {
823
+ context.phase("navigation", "degraded", "blocked");
824
+ throw new Error(blockedReason);
825
+ }
826
+ }
827
+ async function runNavigationChecks(context) {
828
+ const navigation = await navigatePage(context);
829
+ const pageContentCheck = await checkPageContent(context);
830
+ persistNavigationResult(context, navigation.response, pageContentCheck);
831
+ context.phase("navigation", "completed");
832
+ context.phase("core-extraction", "started");
833
+ return {
834
+ pageContentCheck,
835
+ contentCheckTimedOut: context.contentCheckTimedOut,
836
+ httpStatus: context.httpStatus,
837
+ postNavigationDeadline: context.postNavigationDeadline
838
+ };
839
+ }
840
+
841
+ // src/capture/contentExtractor.ts
842
+ import {
843
+ existsSync as existsSync2,
844
+ readdirSync,
845
+ statSync,
846
+ readFileSync as readFileSync2,
847
+ openSync,
848
+ fstatSync,
849
+ closeSync
850
+ } from "fs";
851
+ import { basename, join as join5 } from "path";
852
+ var DEFAULT_VISION_REQUEST_TIMEOUT_MS = 3e4;
853
+ function resolveVisionPhaseCompletion(outcome, remainingMs) {
854
+ if (outcome.budgetExhausted || remainingMs <= 0) {
855
+ return { status: "degraded", reason: "budget-exhausted" };
856
+ }
857
+ if (outcome.internalError) {
858
+ return { status: "degraded", reason: "internal-error" };
859
+ }
860
+ if (outcome.timedOutRequests > 0) {
861
+ return { status: "degraded", reason: "request-timeout" };
862
+ }
863
+ if (outcome.failedRequests > 0) {
864
+ return { status: "degraded", reason: "provider-error" };
865
+ }
866
+ return { status: "completed" };
867
+ }
868
+ var VisionRequestTimeoutError = class extends Error {
869
+ constructor(timeoutMs) {
870
+ super(`Vision request timed out after ${timeoutMs}ms`);
871
+ this.name = "VisionRequestTimeoutError";
872
+ }
873
+ };
874
+ function resolveVisionRequestTimeoutMs() {
875
+ const configured = Number(process.env.HYPERFRAMES_VISION_TIMEOUT_MS);
876
+ return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_VISION_REQUEST_TIMEOUT_MS;
877
+ }
878
+ async function runBoundedVisionRequest(request, timeoutMs) {
879
+ const controller = new AbortController();
880
+ let timer;
881
+ const timeout = new Promise((_resolve, reject) => {
882
+ timer = setTimeout(() => {
883
+ reject(new VisionRequestTimeoutError(timeoutMs));
884
+ controller.abort();
885
+ }, timeoutMs);
886
+ });
887
+ try {
888
+ return await Promise.race([request(controller.signal), timeout]);
889
+ } finally {
890
+ if (timer) clearTimeout(timer);
891
+ }
892
+ }
893
+ async function detectLibraries(page, capturedShaders) {
894
+ let detectedLibraries = [];
895
+ try {
896
+ detectedLibraries = await page.evaluate(`(() => {
897
+ var libs = [];
898
+ function add(name) { if (libs.indexOf(name) === -1) libs.push(name); }
899
+
900
+ // 1. Window globals (works for CDN-loaded / non-bundled libraries)
901
+ if (typeof window.gsap !== 'undefined' || typeof window.TweenMax !== 'undefined') add('GSAP');
902
+ if (typeof window.ScrollTrigger !== 'undefined') add('GSAP ScrollTrigger');
903
+ if (typeof window.THREE !== 'undefined') add('Three.js');
904
+ if (typeof window.PIXI !== 'undefined') add('PixiJS');
905
+ if (typeof window.BABYLON !== 'undefined') add('Babylon.js');
906
+ if (typeof window.Lottie !== 'undefined' || typeof window.lottie !== 'undefined') add('Lottie');
907
+ if (typeof window.__NEXT_DATA__ !== 'undefined') add('Next.js');
908
+ if (typeof window.__NUXT__ !== 'undefined') add('Nuxt');
909
+ if (typeof window.Webflow !== 'undefined') add('Webflow');
910
+
911
+ // 2. DOM fingerprints (survive bundling \u2014 most reliable for modern sites)
912
+ // Three.js sets data-engine on every canvas it creates
913
+ var threeCanvas = document.querySelector('canvas[data-engine*="three"]');
914
+ if (threeCanvas) add('Three.js (' + (threeCanvas.getAttribute('data-engine') || '') + ')');
915
+ // Babylon.js also sets data-engine
916
+ var babylonCanvas = document.querySelector('canvas[data-engine*="Babylon"]');
917
+ if (babylonCanvas) add('Babylon.js');
918
+ // Lottie web components
919
+ if (document.querySelector('dotlottie-wc, lottie-player, dotlottie-player')) add('Lottie');
920
+ // Rive
921
+ if (document.querySelector('canvas[class*="rive"], rive-canvas')) add('Rive');
922
+ // React/Next.js
923
+ if (document.getElementById('__next')) add('Next.js');
924
+ if (document.getElementById('__nuxt')) add('Nuxt');
925
+ if (document.querySelector('[data-reactroot], [data-react-helmet]')) add('React');
926
+ // Svelte
927
+ if (document.querySelector('[class*="svelte-"]')) add('Svelte');
928
+ // Tailwind (utility class detection)
929
+ if (document.querySelector('[class*="flex "], [class*="grid "], [class*="px-"], [class*="py-"]')) add('Tailwind CSS');
930
+ // Framer Motion
931
+ if (document.querySelector('[style*="--framer-"], [data-framer-component-type]')) add('Framer Motion');
932
+
933
+ // 3. Script URL patterns
934
+ document.querySelectorAll('script[src]').forEach(function(s) {
935
+ var src = s.src.toLowerCase();
936
+ if (src.includes('gsap') || src.includes('tweenmax') || src.includes('greensock')) add('GSAP');
937
+ if (src.includes('scrolltrigger')) add('GSAP ScrollTrigger');
938
+ if (src.includes('three.module') || src.includes('three.min')) add('Three.js');
939
+ if (src.includes('pixi')) add('PixiJS');
940
+ if (src.includes('lottie') || src.includes('bodymovin')) add('Lottie');
941
+ if (src.includes('framer-motion')) add('Framer Motion');
942
+ if (src.includes('anime.min') || src.includes('animejs')) add('Anime.js');
943
+ if (src.includes('matter.min') || src.includes('matter-js')) add('Matter.js');
944
+ if (src.includes('lenis')) add('Lenis (smooth scroll)');
945
+ });
946
+
947
+ return libs;
948
+ })()`);
949
+ } catch {
950
+ }
951
+ try {
952
+ const shaders = capturedShaders || [];
953
+ if (shaders.length > 0) {
954
+ const allSource = shaders.map((s) => s.source).join("\n");
955
+ const add = (name) => {
956
+ if (!detectedLibraries.includes(name)) detectedLibraries.push(name);
957
+ };
958
+ add("WebGL");
959
+ if (allSource.includes("modelViewMatrix") && allSource.includes("projectionMatrix"))
960
+ add("Three.js (confirmed via shaders)");
961
+ else if (allSource.includes("vTextureCoord") && allSource.includes("uSampler") && !allSource.includes("modelViewMatrix"))
962
+ add("PixiJS (confirmed via shaders)");
963
+ else if (allSource.includes("viewProjection") && allSource.includes("world"))
964
+ add("Babylon.js (confirmed via shaders)");
965
+ }
966
+ } catch {
967
+ }
968
+ return detectedLibraries;
969
+ }
970
+ async function extractVisibleText(page) {
971
+ let visibleTextContent = "";
972
+ try {
973
+ visibleTextContent = await page.evaluate(`(() => {
974
+ var cookieRe = /^(accept|cookie|privacy|that's fine|got it|i agree|reject all|accept all|manage cookies|consent)$/i;
975
+ var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
976
+ var texts = [];
977
+ var node;
978
+ while (node = walker.nextNode()) {
979
+ var text = (node.textContent || '').trim();
980
+ if (text.length < 3) continue;
981
+ var el = node.parentElement;
982
+ if (!el) continue;
983
+ var style = getComputedStyle(el);
984
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
985
+ var tag = el.tagName.toLowerCase();
986
+ if (tag === 'script' || tag === 'style' || tag === 'noscript') continue;
987
+ // Skip very short text inside nav/footer (catches single-word nav links)
988
+ // Threshold is 8 chars to preserve footer copy like "\xA9 2026 Stripe" (16 chars)
989
+ var inNavOrFooter = el.closest('nav, footer, [role="navigation"]');
990
+ if (inNavOrFooter && text.length < 8) continue;
991
+ // Skip common cookie/consent patterns
992
+ if (cookieRe.test(text)) continue;
993
+ texts.push('[' + tag + '] ' + text);
994
+ }
995
+ return texts.join('\\n');
996
+ })()`);
997
+ if (visibleTextContent.length > 3e4) {
998
+ visibleTextContent = visibleTextContent.slice(0, 3e4) + "\n[...truncated]";
999
+ }
1000
+ } catch {
1001
+ }
1002
+ return visibleTextContent;
1003
+ }
1004
+ async function captionImagesWithGemini(outputDir, progress, warnings, options = {}) {
1005
+ const geminiCaptions = {};
1006
+ let timedOutCount = 0;
1007
+ let failedRequestCount = 0;
1008
+ let budgetExhausted = false;
1009
+ let internalError = false;
1010
+ const reportOutcome = () => {
1011
+ const outcome = {
1012
+ timedOutRequests: timedOutCount,
1013
+ failedRequests: failedRequestCount,
1014
+ budgetExhausted
1015
+ };
1016
+ if (internalError) outcome.internalError = true;
1017
+ options.onOutcome?.(outcome);
1018
+ };
1019
+ if (options.skipVision) {
1020
+ reportOutcome();
1021
+ return geminiCaptions;
1022
+ }
1023
+ const openRouterKey = process.env.OPENROUTER_API_KEY;
1024
+ const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
1025
+ const vertexProject = process.env.HYPERFRAMES_VERTEX_PROJECT_ID;
1026
+ const vertexServiceAccount = process.env.HYPERFRAMES_VERTEX_SERVICE_ACCOUNT;
1027
+ const useVertex = Boolean(vertexProject && vertexServiceAccount);
1028
+ if (!openRouterKey && !useVertex && !geminiKey) {
1029
+ reportOutcome();
1030
+ return geminiCaptions;
1031
+ }
1032
+ const provider = openRouterKey ? "openrouter" : useVertex ? "vertex" : "gemini";
1033
+ const providerName = { openrouter: "OpenRouter", vertex: "Vertex AI", gemini: "Gemini" }[provider];
1034
+ const model = {
1035
+ openrouter: process.env.HYPERFRAMES_OPENROUTER_MODEL || "google/gemini-3.1-flash-lite",
1036
+ vertex: process.env.HYPERFRAMES_VERTEX_MODEL || "gemini-2.5-flash",
1037
+ gemini: process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview"
1038
+ }[provider];
1039
+ const requestTimeoutMs = resolveVisionRequestTimeoutMs();
1040
+ progress("design", `Captioning images with ${providerName} vision...`);
1041
+ try {
1042
+ let captionOne;
1043
+ if (provider === "openrouter") {
1044
+ captionOne = async ({ mimeType, base64, prompt, maxTokens, timeoutMs }) => {
1045
+ return runBoundedVisionRequest(async (signal) => {
1046
+ const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
1047
+ method: "POST",
1048
+ headers: {
1049
+ Authorization: `Bearer ${openRouterKey}`,
1050
+ "Content-Type": "application/json"
1051
+ },
1052
+ signal,
1053
+ body: JSON.stringify({
1054
+ model,
1055
+ messages: [
1056
+ {
1057
+ role: "user",
1058
+ content: [
1059
+ { type: "text", text: prompt },
1060
+ {
1061
+ type: "image_url",
1062
+ image_url: { url: `data:${mimeType};base64,${base64}` }
1063
+ }
1064
+ ]
1065
+ }
1066
+ ],
1067
+ max_tokens: maxTokens
1068
+ })
1069
+ });
1070
+ if (!res.ok) {
1071
+ await res.text();
1072
+ throw new Error(`OpenRouter request failed with HTTP ${res.status}`);
1073
+ }
1074
+ const data = await res.json();
1075
+ return data.choices?.[0]?.message?.content?.trim() || "";
1076
+ }, timeoutMs);
1077
+ };
1078
+ } else {
1079
+ let GoogleGenAI;
1080
+ try {
1081
+ ({ GoogleGenAI } = await loadOptionalPackage("@google/genai", "vision captioning"));
1082
+ } catch (err) {
1083
+ warnings.push(`Skipped vision captioning: ${err.message}`);
1084
+ internalError = true;
1085
+ reportOutcome();
1086
+ return geminiCaptions;
1087
+ }
1088
+ let ai;
1089
+ if (provider === "vertex") {
1090
+ if (!vertexProject || !vertexServiceAccount) return geminiCaptions;
1091
+ let credentials;
1092
+ try {
1093
+ credentials = JSON.parse(vertexServiceAccount);
1094
+ } catch {
1095
+ warnings.push(
1096
+ "HYPERFRAMES_VERTEX_SERVICE_ACCOUNT is not valid JSON; skipped vision captioning."
1097
+ );
1098
+ internalError = true;
1099
+ reportOutcome();
1100
+ return geminiCaptions;
1101
+ }
1102
+ ai = new GoogleGenAI({
1103
+ vertexai: true,
1104
+ project: vertexProject,
1105
+ location: process.env.HYPERFRAMES_VERTEX_LOCATION || "us-central1",
1106
+ googleAuthOptions: { credentials }
1107
+ });
1108
+ } else {
1109
+ if (!geminiKey) return geminiCaptions;
1110
+ ai = new GoogleGenAI({ apiKey: geminiKey });
1111
+ }
1112
+ captionOne = async ({ mimeType, base64, prompt, maxTokens, timeoutMs }) => {
1113
+ const response = await runBoundedVisionRequest(
1114
+ (signal) => ai.models.generateContent({
1115
+ model,
1116
+ contents: [
1117
+ {
1118
+ role: "user",
1119
+ parts: [{ inlineData: { mimeType, data: base64 } }, { text: prompt }]
1120
+ }
1121
+ ],
1122
+ config: {
1123
+ maxOutputTokens: maxTokens,
1124
+ // A one-line factual caption needs no reasoning, and leaving thinking on is
1125
+ // not merely wasteful: thinking tokens are drawn from maxOutputTokens, so the
1126
+ // model can spend the whole budget and return empty text. That surfaces as a
1127
+ // successful request with no caption — the capture then logs "Captioned N/N
1128
+ // images" followed by "0 images captioned", which is what production shows.
1129
+ thinkingConfig: { thinkingBudget: 0 },
1130
+ abortSignal: signal,
1131
+ httpOptions: { timeout: timeoutMs }
1132
+ }
1133
+ }),
1134
+ timeoutMs
1135
+ );
1136
+ return response.text?.trim() || "";
1137
+ };
1138
+ }
1139
+ const imageFiles = readdirSync(join5(outputDir, "assets")).filter(
1140
+ (f) => /\.(png|jpg|jpeg|webp|gif)$/i.test(f)
1141
+ );
1142
+ const BATCH_SIZE = 20;
1143
+ const collectCaptionResults = (results) => {
1144
+ let timeouts = 0;
1145
+ let failures = 0;
1146
+ for (const result of results) {
1147
+ if (result.status === "fulfilled" && result.value.caption) {
1148
+ geminiCaptions[result.value.file] = result.value.caption;
1149
+ } else if (result.status === "rejected" && result.reason instanceof VisionRequestTimeoutError) {
1150
+ timeouts++;
1151
+ } else if (result.status === "rejected") {
1152
+ failures++;
1153
+ }
1154
+ }
1155
+ return { timeouts, failures };
1156
+ };
1157
+ for (let i = 0; i < imageFiles.length; i += BATCH_SIZE) {
1158
+ const remainingMs = options.remainingMs?.() ?? Number.POSITIVE_INFINITY;
1159
+ if (remainingMs <= 0) {
1160
+ budgetExhausted = true;
1161
+ break;
1162
+ }
1163
+ const timeoutMs = Math.max(1, Math.min(requestTimeoutMs, remainingMs));
1164
+ const batch = imageFiles.slice(i, i + BATCH_SIZE);
1165
+ const results = await Promise.allSettled(
1166
+ batch.map(async (file) => {
1167
+ const filePath = join5(outputDir, "assets", file);
1168
+ const fd = openSync(filePath, "r", 384);
1169
+ let buffer;
1170
+ try {
1171
+ const stat = fstatSync(fd);
1172
+ if (stat.size > 4e6) return { file, caption: "" };
1173
+ buffer = readFileSync2(fd);
1174
+ } finally {
1175
+ closeSync(fd);
1176
+ }
1177
+ const base64 = buffer.toString("base64");
1178
+ const ext = file.split(".").pop()?.toLowerCase() || "png";
1179
+ const mimeType = ext === "jpg" ? "image/jpeg" : `image/${ext}`;
1180
+ const caption = await captionOne({
1181
+ mimeType,
1182
+ base64,
1183
+ prompt: "Describe this website image in ONE short sentence for a video storyboard. Focus on: what it shows, dominant colors, whether background is light or dark. Be factual, not creative.",
1184
+ maxTokens: 500,
1185
+ timeoutMs
1186
+ });
1187
+ return { file, caption };
1188
+ })
1189
+ );
1190
+ const counts = collectCaptionResults(results);
1191
+ timedOutCount += counts.timeouts;
1192
+ failedRequestCount += counts.failures;
1193
+ if (i + BATCH_SIZE < imageFiles.length) {
1194
+ await new Promise((r) => setTimeout(r, 2e3));
1195
+ }
1196
+ progress(
1197
+ "design",
1198
+ `Captioned ${Math.min(i + BATCH_SIZE, imageFiles.length)}/${imageFiles.length} images...`
1199
+ );
1200
+ }
1201
+ progress(
1202
+ "design",
1203
+ `${Object.keys(geminiCaptions).length} images captioned with ${providerName}`
1204
+ );
1205
+ const svgFiles = [];
1206
+ const assetsDir = join5(outputDir, "assets");
1207
+ for (const f of readdirSync(assetsDir)) {
1208
+ if (/\.svg$/i.test(f)) svgFiles.push({ file: f, relPath: f });
1209
+ }
1210
+ const svgsSubdir = join5(assetsDir, "svgs");
1211
+ if (existsSync2(svgsSubdir)) {
1212
+ for (const f of readdirSync(svgsSubdir)) {
1213
+ if (/\.svg$/i.test(f)) svgFiles.push({ file: f, relPath: `svgs/${f}` });
1214
+ }
1215
+ }
1216
+ if (svgFiles.length > 0) {
1217
+ let sharp;
1218
+ try {
1219
+ sharp = (await import("sharp")).default;
1220
+ } catch (err) {
1221
+ warnings.push(
1222
+ `Skipped ${svgFiles.length} SVG caption(s): sharp could not load (${err.message}). Reinstall with optional dependencies enabled (e.g. \`npm i sharp\`) to caption SVG assets.`
1223
+ );
1224
+ reportOutcome();
1225
+ return geminiCaptions;
1226
+ }
1227
+ const hostConcurrency = sharp.concurrency();
1228
+ progress("design", `Rasterizing + captioning ${svgFiles.length} SVGs via vision API...`);
1229
+ const SVG_BATCH = 20;
1230
+ const SVG_RENDER_SIZE = 256;
1231
+ let svgsSkipped = 0;
1232
+ for (let i = 0; i < svgFiles.length; i += SVG_BATCH) {
1233
+ const remainingMs = options.remainingMs?.() ?? Number.POSITIVE_INFINITY;
1234
+ if (remainingMs <= 0) {
1235
+ budgetExhausted = true;
1236
+ break;
1237
+ }
1238
+ const timeoutMs = Math.max(1, Math.min(requestTimeoutMs, remainingMs));
1239
+ const batch = svgFiles.slice(i, i + SVG_BATCH);
1240
+ const rasterized = [];
1241
+ sharp.concurrency(1);
1242
+ try {
1243
+ for (const { relPath } of batch) {
1244
+ const filePath = join5(assetsDir, relPath);
1245
+ try {
1246
+ const svgSource = readFileSync2(filePath, "utf-8");
1247
+ const lightFillHits = (svgSource.match(/fill\s*=\s*["'](#fff(fff)?|white|#[ef][ef][ef]|#[ef]{6})["']/gi) || []).length;
1248
+ const darkFillHits = (svgSource.match(/fill\s*=\s*["'](#000(000)?|black|#[0-3]{6}|#[0-3]{3})["']/gi) || []).length;
1249
+ const bg = lightFillHits > darkFillHits ? { r: 32, g: 32, b: 32 } : { r: 255, g: 255, b: 255 };
1250
+ const pngBuffer = await sharp(filePath).resize({
1251
+ width: SVG_RENDER_SIZE,
1252
+ height: SVG_RENDER_SIZE,
1253
+ fit: "inside",
1254
+ withoutEnlargement: false
1255
+ }).flatten({ background: bg }).png().toBuffer();
1256
+ rasterized.push({ relPath, pngBase64: pngBuffer.toString("base64") });
1257
+ } catch {
1258
+ svgsSkipped++;
1259
+ rasterized.push({ relPath, pngBase64: null });
1260
+ }
1261
+ }
1262
+ } finally {
1263
+ sharp.concurrency(hostConcurrency);
1264
+ }
1265
+ const results = await Promise.allSettled(
1266
+ rasterized.map(async ({ relPath, pngBase64 }) => {
1267
+ if (pngBase64 === null) return { file: relPath, caption: "" };
1268
+ const caption = await captionOne({
1269
+ mimeType: "image/png",
1270
+ base64: pngBase64,
1271
+ prompt: "Describe this SVG asset rendered from a website in ONE short sentence for a video storyboard. Focus on: what shape/icon/illustration/wordmark it is, its colors, any text it contains. If you see a wordmark, READ THE LETTERS LITERALLY \u2014 do not guess a brand from context. Be factual.",
1272
+ maxTokens: 300,
1273
+ timeoutMs
1274
+ });
1275
+ return { file: relPath, caption };
1276
+ })
1277
+ );
1278
+ const counts = collectCaptionResults(results);
1279
+ timedOutCount += counts.timeouts;
1280
+ failedRequestCount += counts.failures;
1281
+ if (i + SVG_BATCH < svgFiles.length) {
1282
+ await new Promise((r) => setTimeout(r, 2e3));
1283
+ }
1284
+ progress(
1285
+ "design",
1286
+ `Captioned ${Math.min(i + SVG_BATCH, svgFiles.length)}/${svgFiles.length} SVGs...`
1287
+ );
1288
+ }
1289
+ progress("design", `${Object.keys(geminiCaptions).length} total assets captioned`);
1290
+ if (svgsSkipped > 0) {
1291
+ progress(
1292
+ "design",
1293
+ `skipped rasterizing ${svgsSkipped} SVG(s) \u2014 fell back to label-derived`
1294
+ );
1295
+ }
1296
+ }
1297
+ if (timedOutCount > 0) {
1298
+ warnings.push(
1299
+ `${providerName} vision timed out for ${timedOutCount} asset(s); captions omitted.`
1300
+ );
1301
+ }
1302
+ if (failedRequestCount > 0) {
1303
+ warnings.push(
1304
+ `${providerName} vision failed for ${failedRequestCount} asset(s); captions omitted.`
1305
+ );
1306
+ }
1307
+ } catch {
1308
+ internalError = true;
1309
+ warnings.push(`${providerName} captioning failed internally; captions omitted.`);
1310
+ }
1311
+ reportOutcome();
1312
+ return geminiCaptions;
1313
+ }
1314
+ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCaptions) {
1315
+ const captionedLines = [];
1316
+ const uncaptionedLines = [];
1317
+ const svgLines = [];
1318
+ const fontLines = [];
1319
+ const assetsPath = join5(outputDir, "assets");
1320
+ try {
1321
+ for (const file of readdirSync(assetsPath)) {
1322
+ if (file === "svgs" || file === "fonts" || file === "lottie" || file === "videos") continue;
1323
+ const filePath = join5(assetsPath, file);
1324
+ const stat = statSync(filePath);
1325
+ if (!stat.isFile()) continue;
1326
+ const sizeKb = Math.round(stat.size / 1024);
1327
+ const catalogMatch = catalogedAssets.find(
1328
+ (a) => a.url && file.includes(a.url.split("/").pop()?.split("?")[0]?.slice(0, 20) || "___")
1329
+ );
1330
+ const desc = catalogMatch?.description || catalogMatch?.notes || "";
1331
+ const heading = catalogMatch?.nearestHeading || "";
1332
+ const section = catalogMatch?.sectionClasses || "";
1333
+ const aboveFold = catalogMatch?.aboveFold ? "above fold" : "";
1334
+ const geminiCaption = geminiCaptions[file];
1335
+ const cleanName = file.replace(/\.[^.]+$/, "").replace(/[-_]/g, " ");
1336
+ const parts = [`${file} \u2014 ${sizeKb}KB`];
1337
+ if (geminiCaption) {
1338
+ parts.push(geminiCaption);
1339
+ captionedLines.push(parts.join(", "));
1340
+ } else {
1341
+ if (desc) parts.push(`"${desc.slice(0, 80)}"`);
1342
+ if (heading) parts.push(`section: "${heading.slice(0, 60)}"`);
1343
+ else if (section) parts.push(`in: ${section.split(" ").slice(0, 3).join(" ")}`);
1344
+ if (aboveFold) parts.push(aboveFold);
1345
+ if (!desc && !heading) parts.push(cleanName);
1346
+ uncaptionedLines.push(parts.join(", "));
1347
+ }
1348
+ }
1349
+ } catch {
1350
+ }
1351
+ try {
1352
+ const svgsPath = join5(assetsPath, "svgs");
1353
+ for (const file of readdirSync(svgsPath)) {
1354
+ if (!file.endsWith(".svg")) continue;
1355
+ const svgMatch = tokens.svgs.find(
1356
+ (s) => s.label && file.includes(
1357
+ s.label.toLowerCase().replace(/[^a-z0-9]/g, "-").slice(0, 15)
1358
+ )
1359
+ );
1360
+ const geminiCaption = geminiCaptions[`svgs/${file}`];
1361
+ if (geminiCaption) {
1362
+ svgLines.push(`svgs/${file} \u2014 ${geminiCaption}`);
1363
+ continue;
1364
+ }
1365
+ const label = svgMatch?.label || file.replace(".svg", "").replace(/-/g, " ");
1366
+ svgLines.push(`svgs/${file} \u2014 ${label}`);
1367
+ }
1368
+ } catch {
1369
+ }
1370
+ try {
1371
+ const fontsPath = join5(assetsPath, "fonts");
1372
+ for (const file of readdirSync(fontsPath)) {
1373
+ fontLines.push(`fonts/${file} \u2014 font file`);
1374
+ }
1375
+ } catch {
1376
+ }
1377
+ const videoLines = [];
1378
+ try {
1379
+ const manifest = JSON.parse(
1380
+ readFileSync2(join5(outputDir, "extracted", "video-manifest.json"), "utf-8")
1381
+ );
1382
+ for (const v of manifest) {
1383
+ if (!v.localPath) continue;
1384
+ const base = basename(v.localPath) || v.filename || "";
1385
+ if (!base) continue;
1386
+ const desc = (v.caption || v.heading || "").trim().replace(/\s+/g, " ").slice(0, 140) || "motion clip";
1387
+ const dimW = v.sourceWidth || v.width;
1388
+ const dimH = v.sourceHeight || v.height;
1389
+ const dims = dimW && dimH ? `, ~${dimW}\xD7${dimH}` : "";
1390
+ videoLines.push(`${base} \u2014 [video] ${desc}${dims}`);
1391
+ }
1392
+ } catch {
1393
+ }
1394
+ return [...videoLines, ...captionedLines, ...uncaptionedLines, ...svgLines, ...fontLines];
1395
+ }
1396
+
1397
+ // src/capture/coreExtractionPhase.ts
1398
+ import { join as join7 } from "path";
1399
+
1400
+ // src/capture/htmlExtractor.ts
1401
+ var DEFAULT_SETTLE_TIME = 3e3;
1402
+ async function extractHtml(page, opts = {}) {
1403
+ const settleTime = opts.settleTime ?? DEFAULT_SETTLE_TIME;
1404
+ await new Promise((r) => setTimeout(r, settleTime));
1405
+ const stylesheetUrls = await page.evaluate(`(() => {
1406
+ return Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')).map(function(l) { return l.href; });
1407
+ })()`);
1408
+ for (const href of stylesheetUrls) {
1409
+ try {
1410
+ if (isPrivateUrl(href)) continue;
1411
+ const res = await fetch(href, {
1412
+ signal: AbortSignal.timeout(1e4),
1413
+ headers: { "User-Agent": CAPTURE_USER_AGENT }
1414
+ });
1415
+ if (!res.ok) continue;
1416
+ let css = await res.text();
1417
+ css = css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
1418
+ if (url.startsWith("data:") || url.startsWith("http") || url.startsWith("//")) return match;
1419
+ try {
1420
+ return `url('${new URL(url, href).href}')`;
1421
+ } catch {
1422
+ return match;
1423
+ }
1424
+ });
1425
+ await page.addStyleTag({ content: css });
1426
+ await page.evaluate((targetHref) => {
1427
+ const links = document.querySelectorAll('link[rel="stylesheet"]');
1428
+ for (const link of links) {
1429
+ if (link.href === targetHref) {
1430
+ link.remove();
1431
+ break;
1432
+ }
1433
+ }
1434
+ }, href);
1435
+ } catch {
1436
+ }
1437
+ }
1438
+ await page.evaluate(`(() => {
1439
+ document.querySelectorAll("img[src]").forEach(function(el) {
1440
+ try {
1441
+ // getAttribute returns the raw HTML attribute (with &amp;)
1442
+ // .src returns the resolved URL (with &) \u2014 use .src for the correct value
1443
+ var resolved = el.src;
1444
+ if (resolved) el.setAttribute("src", resolved);
1445
+ } catch(e) {}
1446
+ });
1447
+ // Fix srcset attributes too (Next.js image optimization)
1448
+ document.querySelectorAll("img[srcset]").forEach(function(el) {
1449
+ try {
1450
+ var srcset = el.getAttribute("srcset") || "";
1451
+ // Decode &amp; entities in srcset
1452
+ srcset = srcset.replace(/&amp;/g, "&");
1453
+ el.setAttribute("srcset", srcset);
1454
+ } catch(e) {}
1455
+ });
1456
+ document.querySelectorAll('[style*="url("]').forEach(function(el) {
1457
+ el.style.cssText = el.style.cssText.replace(/url\\(['"]?([^'"\\)\\s]+)['"]?\\)/g, function(_, url) {
1458
+ try { return "url('" + new URL(url, location.href).href + "')"; } catch(e) { return "url('" + url + "')"; }
1459
+ });
1460
+ });
1461
+ })()`);
1462
+ await page.evaluate(`(async () => {
1463
+ var imgs = Array.from(document.querySelectorAll("img"));
1464
+ for (var i = 0; i < imgs.length; i++) {
1465
+ var img = imgs[i];
1466
+ try {
1467
+ if (!img.src || img.src.startsWith("data:")) continue;
1468
+ if (img.naturalWidth < 10 || img.naturalHeight < 10) continue;
1469
+ // Only convert cross-origin images (same-origin ones will load fine)
1470
+ var imgUrl = new URL(img.src);
1471
+ if (imgUrl.origin === location.origin) continue;
1472
+ // Try to draw to canvas \u2014 will fail if CORS blocks it
1473
+ var canvas = document.createElement("canvas");
1474
+ canvas.width = img.naturalWidth;
1475
+ canvas.height = img.naturalHeight;
1476
+ var ctx = canvas.getContext("2d");
1477
+ ctx.drawImage(img, 0, 0);
1478
+ var dataUrl = canvas.toDataURL("image/png");
1479
+ if (dataUrl.length > 100) {
1480
+ img.setAttribute("src", dataUrl);
1481
+ img.removeAttribute("srcset");
1482
+ }
1483
+ } catch(e) {
1484
+ // Canvas CORS failed \u2014 try fetch + blob as fallback
1485
+ try {
1486
+ var resp = await fetch(img.src, { mode: "cors" });
1487
+ if (resp.ok) {
1488
+ var blob = await resp.blob();
1489
+ var reader = new FileReader();
1490
+ var dataUrl2 = await new Promise(function(resolve) {
1491
+ reader.onloadend = function() { resolve(reader.result); };
1492
+ reader.readAsDataURL(blob);
1493
+ });
1494
+ if (dataUrl2 && typeof dataUrl2 === "string" && dataUrl2.length > 100) {
1495
+ img.setAttribute("src", dataUrl2);
1496
+ img.removeAttribute("srcset");
1497
+ }
1498
+ }
1499
+ } catch(e2) {
1500
+ // Both methods failed \u2014 image stays as original URL
1501
+ }
1502
+ }
1503
+ }
1504
+ })()`);
1505
+ const result = await page.evaluate(`(() => {
1506
+ // Capture styles AND scripts from head separately then combine
1507
+ // Scripts include Three.js, animation libraries that we want to preserve
1508
+ var styles = Array.from(document.head.querySelectorAll("style")).map(function(s) { return s.outerHTML; }).join("\\n");
1509
+ var scripts = Array.from(document.head.querySelectorAll("script")).map(function(s) { return s.outerHTML; }).join("\\n");
1510
+ var headHtml = styles + "\\n" + scripts;
1511
+ var bodyHtml = document.body.innerHTML;
1512
+
1513
+ var cssomRules = [];
1514
+ for (var i = 0; i < document.styleSheets.length; i++) {
1515
+ var sheet = document.styleSheets[i];
1516
+ try {
1517
+ var ownerNode = sheet.ownerNode;
1518
+ if (ownerNode && ownerNode.textContent && ownerNode.textContent.trim()) continue;
1519
+ if (sheet.href) continue;
1520
+ for (var j = 0; j < sheet.cssRules.length; j++) {
1521
+ cssomRules.push(sheet.cssRules[j].cssText);
1522
+ }
1523
+ } catch(e) {}
1524
+ }
1525
+
1526
+ var htmlEl = document.documentElement;
1527
+ var attrParts = [];
1528
+ for (var i = 0; i < htmlEl.attributes.length; i++) {
1529
+ var attr = htmlEl.attributes[i];
1530
+ if (attr.name === "lang" || attr.name === "class" || attr.name === "style" || attr.name === "dir" || attr.name.startsWith("data-")) {
1531
+ attrParts.push(attr.name + '="' + attr.value.replace(/&/g, "&amp;").replace(/"/g, "&quot;") + '"');
1532
+ }
1533
+ }
1534
+
1535
+ return {
1536
+ headHtml: headHtml,
1537
+ bodyHtml: bodyHtml,
1538
+ cssomRules: cssomRules.join("\\n"),
1539
+ htmlAttrs: attrParts.join(" "),
1540
+ viewportWidth: Math.max(window.innerWidth, document.documentElement.scrollWidth),
1541
+ viewportHeight: window.innerHeight,
1542
+ fullPageHeight: document.body.scrollHeight
1543
+ };
1544
+ })()`);
1545
+ const pageOrigin = new URL(page.url()).origin;
1546
+ result.bodyHtml = result.bodyHtml.replace(
1547
+ /(<img\b[^>]*\bsrc=")([^"]*?)(")/g,
1548
+ (_match, pre, url, post) => {
1549
+ let fixed = url.replace(/&amp;/g, "&");
1550
+ if (fixed.startsWith("/") && !fixed.startsWith("//")) {
1551
+ fixed = pageOrigin + fixed;
1552
+ }
1553
+ return pre + fixed + post;
1554
+ }
1555
+ );
1556
+ result.bodyHtml = result.bodyHtml.replace(
1557
+ /(<img\b[^>]*\bsrcset=")([^"]*?)(")/g,
1558
+ (_match, pre, urls, post) => {
1559
+ const fixed = urls.replace(/&amp;/g, "&").replace(
1560
+ /(^|,\s*)(\/[^\s,]+)/g,
1561
+ (_m, sep, path) => sep + pageOrigin + path
1562
+ );
1563
+ return pre + fixed + post;
1564
+ }
1565
+ );
1566
+ result.bodyHtml = result.bodyHtml.replace(
1567
+ /(<video\b[^>]*\bsrc=")([^"]*?)(")/g,
1568
+ (_match, pre, url, post) => {
1569
+ let fixed = url.replace(/&amp;/g, "&");
1570
+ if (fixed.startsWith("/") && !fixed.startsWith("//")) fixed = pageOrigin + fixed;
1571
+ return pre + fixed + post;
1572
+ }
1573
+ );
1574
+ result.bodyHtml = result.bodyHtml.replace(
1575
+ /(<video\b[^>]*\bposter=")([^"]*?)(")/g,
1576
+ (_match, pre, url, post) => {
1577
+ let fixed = url.replace(/&amp;/g, "&");
1578
+ if (fixed.startsWith("/") && !fixed.startsWith("//")) fixed = pageOrigin + fixed;
1579
+ return pre + fixed + post;
1580
+ }
1581
+ );
1582
+ return result;
1583
+ }
1584
+
1585
+ // src/capture/tokenExtractor.ts
1586
+ var EXTRACT_SCRIPT = `(() => {
1587
+ var isVisible = (el) => {
1588
+ var s = getComputedStyle(el);
1589
+ return s.display !== "none" && s.visibility !== "hidden" && s.opacity !== "0" && el.getBoundingClientRect().height > 0;
1590
+ };
1591
+
1592
+ // 1. CSS custom properties from :root
1593
+ var cssVariables = {};
1594
+ for (var i = 0; i < document.styleSheets.length; i++) {
1595
+ try {
1596
+ var rules = document.styleSheets[i].cssRules;
1597
+ for (var j = 0; j < rules.length; j++) {
1598
+ if (rules[j].selectorText === ":root") {
1599
+ for (var k = 0; k < rules[j].style.length; k++) {
1600
+ var prop = rules[j].style[k];
1601
+ if (prop.startsWith("--")) {
1602
+ cssVariables[prop] = rules[j].style.getPropertyValue(prop).trim();
1603
+ }
1604
+ }
1605
+ }
1606
+ }
1607
+ } catch(e) {}
1608
+ }
1609
+
1610
+ // 2. Meta
1611
+ var title = document.title || "";
1612
+ var descEl = document.querySelector('meta[name="description"]') || document.querySelector('meta[property="og:description"]');
1613
+ var description = descEl ? descEl.content : "";
1614
+ var ogImgEl = document.querySelector('meta[property="og:image"]');
1615
+ var ogImage = ogImgEl ? ogImgEl.content : undefined;
1616
+
1617
+ // 3. Fonts \u2014 enumerate loaded FontFaces + supplement with DOM sampling
1618
+ var fontMap = {};
1619
+ function ensureFont(name) {
1620
+ if (!fontMap[name]) fontMap[name] = { family: name, weights: [], variable: false, weightRange: undefined };
1621
+ return fontMap[name];
1622
+ }
1623
+ function addWeight(entry, w) {
1624
+ var n = parseInt(w, 10);
1625
+ if (!isNaN(n) && entry.weights.indexOf(n) === -1) entry.weights.push(n);
1626
+ }
1627
+ var genericFonts = ["serif","sans-serif","monospace","cursive","system-ui","ui-serif","ui-sans-serif","ui-monospace","ui-rounded","emoji","math","fangsong"];
1628
+ try {
1629
+ document.fonts.forEach(function(face) {
1630
+ var name = face.family.replace(/['"]/g, "").trim();
1631
+ if (!name || genericFonts.indexOf(name.toLowerCase()) !== -1) return;
1632
+ // Skip placeholder/fallback fonts (Framer loads hundreds of these)
1633
+ if (name.indexOf("Placeholder") !== -1 || name.indexOf("Fallback") !== -1) return;
1634
+ var entry = ensureFont(name);
1635
+ var w = (face.weight || "").trim();
1636
+ if (w.indexOf(" ") !== -1) {
1637
+ var parts = w.split(" ");
1638
+ var lo = parseInt(parts[0], 10);
1639
+ var hi = parseInt(parts[1], 10);
1640
+ if (!isNaN(lo) && !isNaN(hi)) {
1641
+ entry.variable = true;
1642
+ entry.weightRange = [lo, hi];
1643
+ }
1644
+ } else {
1645
+ addWeight(entry, w);
1646
+ }
1647
+ });
1648
+ } catch(e) {}
1649
+ // Supplement with DOM sampling
1650
+ var domSamples = Array.from(document.querySelectorAll("h1,h2,h3,h4,h5,h6,p,a,button,span,li,strong,b")).slice(0, 100);
1651
+ for (var fi = 0; fi < domSamples.length; fi++) {
1652
+ try {
1653
+ var cs = getComputedStyle(domSamples[fi]);
1654
+ var family = cs.fontFamily.split(",")[0].replace(/['"]/g, "").trim();
1655
+ if (family && genericFonts.indexOf(family.toLowerCase()) === -1) {
1656
+ var entry = ensureFont(family);
1657
+ addWeight(entry, cs.fontWeight);
1658
+ }
1659
+ } catch(e) {}
1660
+ }
1661
+
1662
+ // 4. Colors \u2014 hybrid: DOM computed styles + visual pixel sampling
1663
+ var colorSet = {};
1664
+ function addColor(c, weight) {
1665
+ if (!c || c === "rgba(0, 0, 0, 0)" || c === "transparent" || c === "inherit" || c === "initial" || c === "currentcolor") return;
1666
+ var hex = rgbToHex(c);
1667
+ if (hex) colorSet[hex] = (colorSet[hex] || 0) + (weight || 1);
1668
+ }
1669
+ function rgbToHex(color) {
1670
+ if (!color) return null;
1671
+ if (color.startsWith('#')) return (color.length === 4
1672
+ ? '#' + color[1]+color[1] + color[2]+color[2] + color[3]+color[3]
1673
+ : color).toUpperCase();
1674
+ var m = color.match(/rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)/);
1675
+ if (!m) {
1676
+ // Handle color(srgb ...) format
1677
+ var cm = color.match(/color\\(srgb\\s+([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)/);
1678
+ if (cm) {
1679
+ m = [null, Math.round(parseFloat(cm[1])*255), Math.round(parseFloat(cm[2])*255), Math.round(parseFloat(cm[3])*255)];
1680
+ } else {
1681
+ // Handle modern color functions (oklch, oklab, lch, lab, hsl, color-mix)
1682
+ // Use a 1x1 canvas to resolve ANY CSS color to RGB \u2014 this works even when
1683
+ // getComputedStyle returns the color in its original color space (Chrome 131+)
1684
+ if (/oklch|oklab|lch|lab|hsla?|color-mix|color\\(/.test(color)) {
1685
+ try {
1686
+ var cvs = document.createElement('canvas');
1687
+ cvs.width = 1; cvs.height = 1;
1688
+ var ctx2d = cvs.getContext('2d');
1689
+ if (ctx2d) {
1690
+ ctx2d.fillStyle = color;
1691
+ ctx2d.fillRect(0, 0, 1, 1);
1692
+ var px = ctx2d.getImageData(0, 0, 1, 1).data;
1693
+ if (px[3] > 0) return '#' + ((1<<24) + (px[0]<<16) + (px[1]<<8) + px[2]).toString(16).slice(1).toUpperCase();
1694
+ }
1695
+ } catch(e2) {}
1696
+ // Fallback: temp element approach
1697
+ var tmp = document.createElement('div');
1698
+ tmp.style.color = color;
1699
+ document.body.appendChild(tmp);
1700
+ var resolved = getComputedStyle(tmp).color;
1701
+ document.body.removeChild(tmp);
1702
+ if (resolved !== color) return rgbToHex(resolved);
1703
+ return null;
1704
+ }
1705
+ return null;
1706
+ }
1707
+ }
1708
+ return '#' + ((1<<24) + (parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16).slice(1).toUpperCase();
1709
+ }
1710
+
1711
+ // 4a. Sample DOM elements (text colors, borders, branded elements)
1712
+ var colorCandidates = Array.from(document.querySelectorAll(
1713
+ "body, header, nav, main, footer, section, " +
1714
+ "h1, h2, h3, h4, h5, h6, " +
1715
+ "a, button, [role='button'], " +
1716
+ "[class*='hero'], [class*='cta'], [class*='btn'], [class*='card'], " +
1717
+ "[class*='badge'], [class*='tag'], [class*='accent'], [class*='highlight']"
1718
+ )).slice(0, 200);
1719
+ for (var ci = 0; ci < colorCandidates.length; ci++) {
1720
+ try {
1721
+ var cs = getComputedStyle(colorCandidates[ci]);
1722
+ addColor(cs.backgroundColor);
1723
+ addColor(cs.color);
1724
+ addColor(cs.borderColor);
1725
+ addColor(cs.outlineColor);
1726
+ // Extract colors from gradients in background-image
1727
+ var bgImg = cs.backgroundImage;
1728
+ if (bgImg && bgImg !== 'none') {
1729
+ var gradColors = bgImg.match(/(?:#[0-9a-fA-F]{3,8}|rgba?\\([^)]+\\)|oklch\\([^)]+\\)|oklab\\([^)]+\\)|hsla?\\([^)]+\\)|lab\\([^)]+\\))/g);
1730
+ if (gradColors) gradColors.forEach(function(gc) { addColor(gc); });
1731
+ }
1732
+ // Extract colors from box-shadow
1733
+ var shadow = cs.boxShadow;
1734
+ if (shadow && shadow !== 'none') {
1735
+ var shadowColors = shadow.match(/(?:#[0-9a-fA-F]{3,8}|rgba?\\([^)]+\\))/g);
1736
+ if (shadowColors) shadowColors.forEach(function(sc) { addColor(sc); });
1737
+ }
1738
+ } catch(e) {}
1739
+ }
1740
+
1741
+ // 4b. Explicitly sample html/body backgrounds (the dominant canvas color)
1742
+ // These often define the site's light/dark character
1743
+ try {
1744
+ var htmlBg = getComputedStyle(document.documentElement).backgroundColor;
1745
+ var bodyBg = getComputedStyle(document.body).backgroundColor;
1746
+ addColor(htmlBg, 10);
1747
+ addColor(bodyBg, 10);
1748
+ // Also check the background shorthand which may contain gradients
1749
+ var bodyBgFull = getComputedStyle(document.body).background;
1750
+ var gradColors = bodyBgFull.match(/(?:#[0-9a-fA-F]{3,8}|rgba?\\([^)]+\\)|oklch\\([^)]+\\)|hsla?\\([^)]+\\))/g);
1751
+ if (gradColors) gradColors.forEach(function(gc) { addColor(gc, 8); });
1752
+ } catch(e) {}
1753
+
1754
+ // 4c. Visual pixel sampling \u2014 sample what the user actually SEES
1755
+ // Walk a grid of points across the viewport and read background + text color
1756
+ var vpW = window.innerWidth;
1757
+ var vpH = window.innerHeight;
1758
+ var gridCols = 6;
1759
+ var gridRows = 5;
1760
+ for (var gy = 0; gy < gridRows; gy++) {
1761
+ for (var gx = 0; gx < gridCols; gx++) {
1762
+ try {
1763
+ var px = Math.round((gx + 0.5) * vpW / gridCols);
1764
+ var py = Math.round((gy + 0.5) * vpH / gridRows);
1765
+ var elAt = document.elementFromPoint(px, py);
1766
+ if (elAt) {
1767
+ var elStyle = getComputedStyle(elAt);
1768
+ addColor(elStyle.color, 2);
1769
+ var bgc = elStyle.backgroundColor;
1770
+ // Walk up parents until we find a non-transparent background
1771
+ var bgWalker = elAt;
1772
+ while (bgWalker && (!bgc || bgc === "rgba(0, 0, 0, 0)" || bgc === "transparent")) {
1773
+ bgWalker = bgWalker.parentElement;
1774
+ if (bgWalker) bgc = getComputedStyle(bgWalker).backgroundColor;
1775
+ }
1776
+ addColor(bgc, 3);
1777
+ }
1778
+ } catch(e) {}
1779
+ }
1780
+ }
1781
+
1782
+ // 4c2. Broad sweep \u2014 find ANY element with a non-white/non-transparent background
1783
+ // This catches colored blocks that the grid might miss (code blocks, banners, cards)
1784
+ var allEls = document.querySelectorAll('*');
1785
+ var colorSweepCount = 0;
1786
+ for (var si = 0; si < allEls.length && colorSweepCount < 500; si++) {
1787
+ try {
1788
+ var elCs = getComputedStyle(allEls[si]);
1789
+ var elBg = elCs.backgroundColor;
1790
+ if (elBg && elBg !== "rgba(0, 0, 0, 0)" && elBg !== "transparent") {
1791
+ var hex = rgbToHex(elBg);
1792
+ if (hex && hex !== "#FFFFFF" && hex !== "#000000") {
1793
+ addColor(elBg, 1);
1794
+ }
1795
+ }
1796
+ colorSweepCount++;
1797
+ } catch(e) {}
1798
+ }
1799
+
1800
+ // 4d. Resolve CSS custom properties from :root to actual color values
1801
+ var rootStyle = getComputedStyle(document.documentElement);
1802
+ var rootProps = Object.keys(cssVariables);
1803
+ for (var ri = 0; ri < rootProps.length; ri++) {
1804
+ var val = rootStyle.getPropertyValue(rootProps[ri]).trim();
1805
+ if (val && /^(#|rgb|hsl|oklch|oklab|lch|lab|color)/.test(val)) {
1806
+ addColor(val);
1807
+ }
1808
+ }
1809
+
1810
+ // 4e. Per-color signal stats \u2014 distinguish FILL vs TEXT vs INTERACTIVE vs
1811
+ // large-AREA usage. The flat colorSet above ranks by total weight (so the
1812
+ // canvas/text dominate); these per-color signals let downstream code find the
1813
+ // BRAND color (chromatic, used on interactive/repeated fills) apart from
1814
+ // section surfaces (one big block) and link/text colors. Single pass.
1815
+ var colorStats = {};
1816
+ function statFor(hex) {
1817
+ if (!colorStats[hex]) colorStats[hex] = { count: 0, bgCount: 0, interactiveBg: 0, areaBg: 0, textCount: 0, maxArea: 0 };
1818
+ return colorStats[hex];
1819
+ }
1820
+ var statEls = Array.from(allEls).slice(0, 9000);
1821
+ for (var ti = 0; ti < statEls.length; ti++) {
1822
+ try {
1823
+ var sEl = statEls[ti];
1824
+ var sCs = getComputedStyle(sEl);
1825
+ if (sCs.display === "none" || sCs.visibility === "hidden") continue;
1826
+ var sRect = sEl.getBoundingClientRect();
1827
+ var sArea = sRect.width * sRect.height;
1828
+ var sTag = sEl.tagName.toLowerCase();
1829
+ var sRole = sEl.getAttribute("role") || "";
1830
+ var sCls = sEl.getAttribute("class") || "";
1831
+ var sInteractive = sTag === "a" || sTag === "button" ||
1832
+ sRole === "button" || sRole === "link" || sRole === "menuitem" || sRole === "tab" ||
1833
+ /\\b(btn|button|cta|primary|action)\\b/i.test(sCls);
1834
+ var sBg = sCs.backgroundColor;
1835
+ if (sBg && sBg !== "rgba(0, 0, 0, 0)" && sBg !== "transparent") {
1836
+ var bgHex = rgbToHex(sBg);
1837
+ if (bgHex) {
1838
+ var st = statFor(bgHex);
1839
+ st.count++; st.bgCount++;
1840
+ if (sInteractive) st.interactiveBg++;
1841
+ if (sArea > 50000) st.areaBg++;
1842
+ if (sArea > st.maxArea) st.maxArea = Math.round(sArea);
1843
+ }
1844
+ }
1845
+ var sColor = sCs.color;
1846
+ if (sColor && sColor !== "rgba(0, 0, 0, 0)" && sColor !== "transparent") {
1847
+ var txHex = rgbToHex(sColor);
1848
+ if (txHex) { var st2 = statFor(txHex); st2.count++; st2.textCount++; }
1849
+ }
1850
+ } catch(e) {}
1851
+ }
1852
+ var colorStatsArr = Object.keys(colorStats).map(function(h) {
1853
+ var s = colorStats[h];
1854
+ return { hex: h, count: s.count, bgCount: s.bgCount, interactiveBg: s.interactiveBg, areaBg: s.areaBg, textCount: s.textCount, maxArea: s.maxArea };
1855
+ }).filter(function(s) { return s.bgCount > 0 || s.interactiveBg > 0 || s.count >= 3; })
1856
+ .sort(function(a, b) { return (b.bgCount + b.interactiveBg * 3 + b.textCount) - (a.bgCount + a.interactiveBg * 3 + a.textCount); })
1857
+ .slice(0, 48);
1858
+
1859
+ // 5. Headings
1860
+ var headingEls = Array.from(document.querySelectorAll("h1, h2, h3, h4")).slice(0, 20);
1861
+ var headings = headingEls.filter(isVisible).map(function(h) {
1862
+ var s = getComputedStyle(h);
1863
+ return { level: parseInt(h.tagName[1]), text: (h.innerText || h.textContent || "").trim().replace(/\\s+/g, ' ').slice(0, 200), fontSize: s.fontSize, fontWeight: s.fontWeight, color: rgbToHex(s.color) || s.color };
1864
+ });
1865
+
1866
+ // 6. CTAs \u2014 match by class AND by text content patterns
1867
+ // Conservative class selectors (avoid nav links with "action" or "start" in class)
1868
+ var ctaSelectors = 'a[class*="btn"], a[class*="button"], a[class*="cta"], button[class*="primary"], button[class*="cta"], [role="button"]';
1869
+ var ctaEls = Array.from(document.querySelectorAll(ctaSelectors));
1870
+ // Filter out nav links (common false positives)
1871
+ ctaEls = ctaEls.filter(function(el) {
1872
+ return !el.closest('nav, [role="navigation"], [class*="nav"], [class*="menu"], [class*="dropdown"]');
1873
+ });
1874
+ // Also find links/buttons by text content (catches CTAs without class hints)
1875
+ // Require short text (real CTAs are concise) and exclude nav context
1876
+ var ctaTextPatterns = /^(get started|sign up|start free|try (it )?free|start (a )?trial|book a demo|request (a )?demo|contact (us|sales)|start for free|create account|register now)$/i;
1877
+ var allButtons = Array.from(document.querySelectorAll('a, button'));
1878
+ for (var bi = 0; bi < allButtons.length && ctaEls.length < 20; bi++) {
1879
+ var btnText = (allButtons[bi].textContent || "").trim();
1880
+ if (btnText.length > 30) continue;
1881
+ if (allButtons[bi].closest('nav, [role="navigation"], [class*="nav"], [class*="menu"]')) continue;
1882
+ if (ctaTextPatterns.test(btnText) && ctaEls.indexOf(allButtons[bi]) === -1) {
1883
+ ctaEls.push(allButtons[bi]);
1884
+ }
1885
+ }
1886
+ ctaEls = ctaEls.slice(0, 10);
1887
+ var ctas = ctaEls.filter(isVisible).map(function(c) { return { text: (c.textContent || "").trim().slice(0, 60), href: c.href || undefined }; }).filter(function(c) { return c.text.length > 1; });
1888
+
1889
+ // 8. SVGs
1890
+ var svgEls = Array.from(document.querySelectorAll("svg"));
1891
+ var svgs = svgEls.map(function(svg) {
1892
+ var label = svg.getAttribute("aria-label") || svg.getAttribute("title") || svg.getAttribute("alt");
1893
+ // Try harder to find a name: check class, id, parent context, inner text
1894
+ if (!label) {
1895
+ // Extract meaningful class name, skipping utility classes (tailwind, size, color)
1896
+ var svgClasses = (svg.getAttribute("class") || "").split(/\\s+/);
1897
+ var utilityPattern = /^(w-|h-|p-|m-|text-|bg-|border-|flex|grid|block|hidden|inline|absolute|relative|transition|duration|rotate|scale|opacity|group|sm:|md:|lg:|xl:)/;
1898
+ for (var ci = 0; ci < svgClasses.length; ci++) {
1899
+ var cls = svgClasses[ci];
1900
+ if (cls.length > 3 && cls.length < 40 && !utilityPattern.test(cls) && cls !== "lucide") {
1901
+ label = cls;
1902
+ break;
1903
+ }
1904
+ }
1905
+ }
1906
+ if (!label) {
1907
+ var svgId = svg.getAttribute("id") || "";
1908
+ if (svgId && svgId.length > 2 && svgId.length < 40) label = svgId;
1909
+ }
1910
+ if (!label) {
1911
+ // Check parent element for clues
1912
+ var parent = svg.closest("[class*='icon'], [class*='logo'], [class*='nav'], [class*='btn'], [class*='social']");
1913
+ if (parent) {
1914
+ var parentClass = (parent.getAttribute("class") || "").split(" ").find(function(c) { return c.length > 3 && c.length < 30; });
1915
+ if (parentClass) label = parentClass;
1916
+ }
1917
+ }
1918
+ if (!label) {
1919
+ // Check for text content inside the SVG (e.g. <text>NeetCode</text>)
1920
+ var textEl = svg.querySelector("text");
1921
+ if (textEl && textEl.textContent && textEl.textContent.trim().length > 1 && textEl.textContent.trim().length < 30) {
1922
+ label = textEl.textContent.trim();
1923
+ }
1924
+ }
1925
+ var w = svg.getAttribute("width");
1926
+ // Keep SVGs that have a label OR are at least 16px wide OR are inside a logo/brand context
1927
+ var inLogoContext = svg.closest('[class*="logo"], [class*="brand"], [class*="partner"], [class*="customer"], [class*="marquee"]') !== null;
1928
+ if (!label && !inLogoContext && (!w || parseInt(w) < 16)) return null;
1929
+ var isLogo = (label && label.toLowerCase().indexOf("logo") !== -1) ||
1930
+ svg.closest('[class*="logo"], [class*="brand"], [class*="home"], [class*="marquee"], [class*="partner"], [class*="customer"]') !== null;
1931
+ if (!isLogo) {
1932
+ var bannerEl = svg.closest('header, nav, [role="banner"]');
1933
+ if (bannerEl) {
1934
+ var firstSvg = bannerEl.querySelector('svg');
1935
+ if (firstSvg === svg) isLogo = true;
1936
+ }
1937
+ }
1938
+ if (!isLogo) {
1939
+ var anchor = svg.closest('a[href]');
1940
+ if (anchor) {
1941
+ var href = anchor.getAttribute('href') || '';
1942
+ if (href === '/' || href === '#' || href === './' ||
1943
+ /^https?:\\/\\/[^/]+\\/?$/.test(href)) {
1944
+ isLogo = true;
1945
+ }
1946
+ }
1947
+ }
1948
+ if (!isLogo) {
1949
+ var ariaLabel = svg.getAttribute('aria-label') || svg.getAttribute('title') || '';
1950
+ var titleBrand = (document.title || '').split(/[-|\u2014]/)[0].trim();
1951
+ if (titleBrand.length > 1 && titleBrand.length < 30 &&
1952
+ ariaLabel.toLowerCase().indexOf(titleBrand.toLowerCase()) !== -1) {
1953
+ isLogo = true;
1954
+ }
1955
+ }
1956
+ var rect = svg.getBoundingClientRect();
1957
+ return {
1958
+ label: label || undefined,
1959
+ viewBox: svg.getAttribute("viewBox") || undefined,
1960
+ width: Math.round(rect.width),
1961
+ height: Math.round(rect.height),
1962
+ outerHTML: svg.outerHTML.slice(0, 10000),
1963
+ isLogo: isLogo
1964
+ };
1965
+ }).filter(Boolean).slice(0, 50);
1966
+
1967
+ // 9. Sections \u2014 find large visual blocks regardless of HTML tag
1968
+ var sectionResults = [];
1969
+ // Start with semantic elements, then fall back to large direct children of body/main
1970
+ var candidates = Array.from(document.querySelectorAll(
1971
+ 'section, main > div, main > section, article, ' +
1972
+ 'body > div > div, body > main > div, body > div, ' +
1973
+ '[class*="hero"], [class*="Hero"], [class*="section"], [class*="Section"], ' +
1974
+ '[class*="container"], [class*="wrapper"], [class*="block"], ' +
1975
+ '[id*="section"], [id*="hero"], footer, [role="region"], [role="banner"]'
1976
+ ));
1977
+ // Deduplicate (a div can match multiple selectors)
1978
+ var seenEls = new Set();
1979
+ candidates = candidates.filter(function(el) {
1980
+ if (seenEls.has(el)) return false;
1981
+ seenEls.add(el);
1982
+ return true;
1983
+ });
1984
+ for (var si = 0; si < candidates.length; si++) {
1985
+ var el = candidates[si];
1986
+ var rect = el.getBoundingClientRect();
1987
+ if (rect.height < 200 || rect.width < 400 || !isVisible(el)) continue;
1988
+ // Skip page-level wrappers (a single div wrapping the entire page is not a section)
1989
+ var pageHeight = document.body.scrollHeight || document.documentElement.scrollHeight;
1990
+ if (rect.height > pageHeight * 0.8) continue;
1991
+ var y = rect.top + window.scrollY;
1992
+ var heading = el.querySelector("h1, h2, h3, h4");
1993
+ var headingText = heading ? (heading.innerText || heading.textContent || "").trim().replace(/\\s+/g, ' ').slice(0, 80) : "";
1994
+ var classes = (el.className || "").toString().toLowerCase();
1995
+ var type = "content";
1996
+ if (y < 200 || classes.indexOf("hero") !== -1) type = "hero";
1997
+ else if (el.tagName === "FOOTER" || classes.indexOf("footer") !== -1) type = "footer";
1998
+ else if (classes.indexOf("cta") !== -1) type = "cta";
1999
+ else if (classes.indexOf("logo") !== -1 || classes.indexOf("customer") !== -1) type = "logos";
2000
+ else if (classes.indexOf("testimonial") !== -1 || classes.indexOf("quote") !== -1) type = "testimonials";
2001
+ else if (classes.indexOf("feature") !== -1 || classes.indexOf("section") !== -1) type = "features";
2002
+ var selector = el.id ? "#" + el.id : el.tagName.toLowerCase();
2003
+ var sectionBg = getComputedStyle(el).backgroundColor;
2004
+ // Walk up DOM to find nearest non-transparent background (don't default to white)
2005
+ if (!sectionBg || sectionBg === "rgba(0, 0, 0, 0)" || sectionBg === "transparent") {
2006
+ var bgWalker = el.parentElement;
2007
+ while (bgWalker) {
2008
+ var parentBg = getComputedStyle(bgWalker).backgroundColor;
2009
+ if (parentBg && parentBg !== "rgba(0, 0, 0, 0)" && parentBg !== "transparent") {
2010
+ sectionBg = parentBg;
2011
+ break;
2012
+ }
2013
+ bgWalker = bgWalker.parentElement;
2014
+ }
2015
+ if (!sectionBg || sectionBg === "rgba(0, 0, 0, 0)" || sectionBg === "transparent") sectionBg = "#FFFFFF";
2016
+ }
2017
+ // Check for background-image when color is transparent/default white
2018
+ var sectionBgImage = undefined;
2019
+ var rawBgImg = getComputedStyle(el).backgroundImage;
2020
+ if (rawBgImg && rawBgImg !== "none" && rawBgImg.indexOf("url(") !== -1) {
2021
+ var start = rawBgImg.indexOf("url(") + 4;
2022
+ var end = rawBgImg.indexOf(")", start);
2023
+ if (end > start) {
2024
+ sectionBgImage = rawBgImg.slice(start, end).replace(/['"]/g, "");
2025
+ }
2026
+ }
2027
+ sectionBg = rgbToHex(sectionBg) || sectionBg;
2028
+ // Inner content for faithful page-card recreation downstream: CTAs, body
2029
+ // text, in-section media URLs (remote \u2014 joined to local paths in index.ts),
2030
+ // and a coarse layout hint. Mirrors the prior capture framework's richer
2031
+ // section model that the page-scroll-spotlight blueprint depends on.
2032
+ var absUrl = function (u) {
2033
+ try { return u ? new URL(u, location.href).href : ""; } catch (e) { return ""; }
2034
+ };
2035
+ var sectionText = (el.innerText || el.textContent || "").trim().replace(/\\s+/g, " ").slice(0, 600);
2036
+ var sectionCtas = [];
2037
+ var ctaNodes = el.querySelectorAll("a, button");
2038
+ for (var qi = 0; qi < ctaNodes.length && sectionCtas.length < 8; qi++) {
2039
+ if (!isVisible(ctaNodes[qi])) continue;
2040
+ var ctaTxt = (ctaNodes[qi].textContent || "").trim().replace(/\\s+/g, " ").slice(0, 60);
2041
+ if (ctaTxt && ctaTxt.length > 1 && sectionCtas.indexOf(ctaTxt) === -1) sectionCtas.push(ctaTxt);
2042
+ }
2043
+ var sectionAssets = [];
2044
+ var mediaNodes = el.querySelectorAll("img, video, source");
2045
+ for (var ii = 0; ii < mediaNodes.length && sectionAssets.length < 10; ii++) {
2046
+ var mn = mediaNodes[ii];
2047
+ var msrc = mn.currentSrc || mn.src || mn.getAttribute("src") || mn.getAttribute("data-src") || mn.getAttribute("poster") || "";
2048
+ var mau = absUrl(msrc);
2049
+ if (mau && !mau.startsWith("data:") && sectionAssets.indexOf(mau) === -1) sectionAssets.push(mau);
2050
+ }
2051
+ if (sectionBgImage) {
2052
+ var bau = absUrl(sectionBgImage);
2053
+ if (bau && sectionAssets.indexOf(bau) === -1) sectionAssets.unshift(bau);
2054
+ }
2055
+ var imgCount = el.querySelectorAll("img").length;
2056
+ var layout = "stacked";
2057
+ if (imgCount >= 3) layout = "grid";
2058
+ else if (el.querySelector("img, video") && headingText) layout = "split";
2059
+ else if (headingText && imgCount === 0) layout = "centered";
2060
+ var sectionEntry = {
2061
+ selector: selector, type: type,
2062
+ x: Math.round(rect.left + window.scrollX), y: Math.round(y),
2063
+ width: Math.round(rect.width), height: Math.round(rect.height),
2064
+ heading: headingText, backgroundColor: sectionBg,
2065
+ callsToAction: sectionCtas, text: sectionText, layout: layout, assetUrls: sectionAssets
2066
+ };
2067
+ if (sectionBgImage) sectionEntry.backgroundImage = sectionBgImage;
2068
+ sectionResults.push(sectionEntry);
2069
+ }
2070
+ sectionResults.sort(function(a, b) { return a.y - b.y; });
2071
+ var filtered = sectionResults.filter(function(s, i) { return i === 0 || Math.abs(s.y - sectionResults[i-1].y) > 100; });
2072
+
2073
+ // Filter cssVariables \u2014 keep only color-like values or design-relevant names
2074
+ var colorValueRe = /^(#|rgb|hsl|oklch|oklab|lch|lab|color)/i;
2075
+ var designNameRe = /(color|bg|background|border|text|font|radius|shadow)/i;
2076
+ var filteredVars = {};
2077
+ var varKeys = Object.keys(cssVariables);
2078
+ for (var vi = 0; vi < varKeys.length; vi++) {
2079
+ var varName = varKeys[vi];
2080
+ var varVal = cssVariables[varName];
2081
+ if (colorValueRe.test(varVal) || designNameRe.test(varName)) {
2082
+ filteredVars[varName] = varVal;
2083
+ }
2084
+ }
2085
+
2086
+ // Filter sections \u2014 only keep those with a non-empty heading
2087
+ var filteredSections = filtered.filter(function(s) { return s.heading && s.heading.length > 0; });
2088
+
2089
+ return {
2090
+ title: title, description: description, ogImage: ogImage,
2091
+ cssVariables: filteredVars, fonts: Object.keys(fontMap).map(function(k) { var f = fontMap[k]; f.weights.sort(function(a,b){return a-b;}); return f; }).filter(function(f) { return f.weights.length > 0 || f.variable; }).slice(0, 20), colors: Object.keys(colorSet).sort(function(a,b) { return colorSet[b] - colorSet[a]; }).slice(0, 20),
2092
+ headings: headings, ctas: ctas,
2093
+ svgs: svgs, sections: filteredSections,
2094
+ colorStats: colorStatsArr,
2095
+ page: { width: Math.round(document.documentElement.scrollWidth), height: Math.round(document.documentElement.scrollHeight), viewport: { width: window.innerWidth, height: window.innerHeight } }
2096
+ };
2097
+ })()`;
2098
+ async function extractTokens(page) {
2099
+ return page.evaluate(EXTRACT_SCRIPT);
2100
+ }
2101
+
2102
+ // src/capture/designStyleExtractor.ts
2103
+ var EXTRACT_DESIGN_STYLES_SCRIPT = `(() => {
2104
+ var isVisible = (el) => {
2105
+ var s = getComputedStyle(el);
2106
+ return s.display !== "none" && s.visibility !== "hidden" && s.opacity !== "0" && el.getBoundingClientRect().height > 0;
2107
+ };
2108
+
2109
+ function rgbToHex(color) {
2110
+ if (!color) return "";
2111
+ if (color.startsWith('#')) return color.toUpperCase();
2112
+ // capture optional alpha (group 4), allowing both comma and modern slash (rgb r g b / a) syntax.
2113
+ var m = color.match(/rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*[,/]\\s*([\\d.]+))?/);
2114
+ if (!m) return color;
2115
+ // fully-transparent fill (rgba(...,0)) \u2192 sentinel, NOT #000000 \u2014 otherwise a transparent
2116
+ // chip/tab/stat ground reads as solid black on a light-ground site.
2117
+ if (m[4] !== undefined && parseFloat(m[4]) === 0) return "transparent";
2118
+ return '#' + ((1<<24) + (parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16).slice(1).toUpperCase();
2119
+ }
2120
+
2121
+ function cleanFont(f) {
2122
+ return f.split(",")[0].replace(/['"]/g, "").trim();
2123
+ }
2124
+
2125
+ // keep only gradient background-images (drop url() sprites + "none"); gradients are a core
2126
+ // brand signal (Stripe/ElevenLabs/Snowflake mesh washes) that a flat background-color misses.
2127
+ function gradientOf(v) {
2128
+ return v && v.indexOf("gradient") >= 0 ? v.trim() : "";
2129
+ }
2130
+
2131
+ function getStyles(el) {
2132
+ var s = getComputedStyle(el);
2133
+ var bf = s.backdropFilter || s.webkitBackdropFilter || "";
2134
+ return {
2135
+ fontFamily: cleanFont(s.fontFamily),
2136
+ fontSize: s.fontSize,
2137
+ fontWeight: s.fontWeight,
2138
+ lineHeight: s.lineHeight,
2139
+ letterSpacing: s.letterSpacing,
2140
+ color: rgbToHex(s.color),
2141
+ background: rgbToHex(s.backgroundColor),
2142
+ backgroundImage: gradientOf(s.backgroundImage),
2143
+ backdropFilter: bf === "none" ? "" : bf,
2144
+ padding: s.padding,
2145
+ borderRadius: s.borderRadius,
2146
+ border: s.border,
2147
+ boxShadow: s.boxShadow === "none" ? "none" : s.boxShadow,
2148
+ height: s.height
2149
+ };
2150
+ }
2151
+
2152
+ // \u2500\u2500 1. Typography hierarchy \u2500\u2500
2153
+ // Sample each text role and deduplicate by fontSize
2154
+ var typographyMap = {};
2155
+ var roleSelectors = [
2156
+ { role: "display", sel: "h1", max: 3 },
2157
+ { role: "heading-2", sel: "h2", max: 5 },
2158
+ { role: "heading-3", sel: "h3", max: 5 },
2159
+ { role: "heading-4", sel: "h4", max: 3 },
2160
+ { role: "body", sel: "p", max: 10 },
2161
+ { role: "body-small", sel: "figcaption, .caption, [class*='caption'], [class*='subtitle'], small", max: 5 },
2162
+ { role: "label", sel: "label, [class*='label'], [class*='tag'], [class*='badge']", max: 5 },
2163
+ { role: "link", sel: "a:not([class*='btn']):not([class*='button']):not([role='button'])", max: 5 },
2164
+ { role: "code", sel: "code, pre, [class*='mono']", max: 3 }
2165
+ ];
2166
+
2167
+ for (var ri = 0; ri < roleSelectors.length; ri++) {
2168
+ var spec = roleSelectors[ri];
2169
+ var els = Array.from(document.querySelectorAll(spec.sel)).slice(0, spec.max);
2170
+ for (var ei = 0; ei < els.length; ei++) {
2171
+ if (!isVisible(els[ei])) continue;
2172
+ var s = getStyles(els[ei]);
2173
+ var key = s.fontSize + "|" + s.fontWeight + "|" + s.fontFamily;
2174
+ if (!typographyMap[key]) {
2175
+ var text = (els[ei].textContent || "").trim().replace(/\\s+/g, " ").slice(0, 60);
2176
+ typographyMap[key] = {
2177
+ role: spec.role,
2178
+ fontFamily: s.fontFamily,
2179
+ fontSize: s.fontSize,
2180
+ fontWeight: s.fontWeight,
2181
+ lineHeight: s.lineHeight,
2182
+ letterSpacing: s.letterSpacing,
2183
+ color: s.color,
2184
+ sampleText: text
2185
+ };
2186
+ }
2187
+ }
2188
+ }
2189
+
2190
+ // Sort by font size descending
2191
+ var typography = Object.values(typographyMap);
2192
+ typography.sort(function(a, b) {
2193
+ return parseFloat(b.fontSize) - parseFloat(a.fontSize);
2194
+ });
2195
+
2196
+ // Deduplicate roles \u2014 keep only the first (largest) for each role prefix
2197
+ var seenRoles = {};
2198
+ var uniqueTypo = [];
2199
+ for (var ti = 0; ti < typography.length; ti++) {
2200
+ var baseRole = typography[ti].role.replace(/-\\d+$/, "");
2201
+ if (!seenRoles[baseRole]) {
2202
+ seenRoles[baseRole] = true;
2203
+ typography[ti].role = baseRole;
2204
+ uniqueTypo.push(typography[ti]);
2205
+ } else if (baseRole === "heading") {
2206
+ // Keep multiple heading levels
2207
+ uniqueTypo.push(typography[ti]);
2208
+ }
2209
+ }
2210
+
2211
+ // \u2500\u2500 2. Buttons \u2500\u2500
2212
+ // a page's primary CTA is very often a FILLED pill in the nav/header ("Sign up", "Start for free").
2213
+ // Old code dropped everything under <nav>, losing that CTA; keep nav elements that carry a solid
2214
+ // fill (a real button), still dropping plain nav text links.
2215
+ var isFilledEl = (el) => {
2216
+ var cs = getComputedStyle(el);
2217
+ var bg = cs.backgroundColor;
2218
+ var solid = !!bg && rgbToHex(bg) !== "transparent";
2219
+ // a gradient-filled CTA (e.g. Snowflake's blue pill = background: var(--ui-background-03)) has a
2220
+ // transparent background-COLOR but a gradient background-IMAGE \u2014 count it as filled too.
2221
+ return solid || (cs.backgroundImage || "").indexOf("gradient") >= 0;
2222
+ };
2223
+ var buttonEls = Array.from(document.querySelectorAll(
2224
+ 'button, a[class*="btn"], a[class*="button"], a[role="button"], ' +
2225
+ '[class*="btn-"], [class*="button-"], [class*="cta"]'
2226
+ )).filter(function(el) {
2227
+ if (!isVisible(el)) return false;
2228
+ return el.closest('nav, [role="navigation"]') ? isFilledEl(el) : true;
2229
+ }).slice(0, 16);
2230
+
2231
+ var buttonMap = {};
2232
+ for (var bi = 0; bi < buttonEls.length; bi++) {
2233
+ var bs = getStyles(buttonEls[bi]);
2234
+ // Deduplicate by visual appearance (gradient fill kept distinct so a gradient CTA survives)
2235
+ var bKey = bs.background + "|" + bs.backgroundImage + "|" + bs.backdropFilter + "|" + bs.borderRadius + "|" + bs.border;
2236
+ if (!buttonMap[bKey]) {
2237
+ var btnText = (buttonEls[bi].textContent || "").trim().slice(0, 40);
2238
+ buttonMap[bKey] = {
2239
+ label: btnText || "button",
2240
+ background: bs.background,
2241
+ backgroundImage: bs.backgroundImage,
2242
+ backdropFilter: bs.backdropFilter,
2243
+ color: bs.color,
2244
+ padding: bs.padding,
2245
+ borderRadius: bs.borderRadius,
2246
+ border: bs.border,
2247
+ boxShadow: bs.boxShadow,
2248
+ fontSize: bs.fontSize,
2249
+ fontWeight: bs.fontWeight,
2250
+ height: bs.height
2251
+ };
2252
+ }
2253
+ }
2254
+ var buttons = Object.values(buttonMap).slice(0, 6);
2255
+
2256
+ // \u2500\u2500 3. Cards / containers \u2500\u2500
2257
+ var cardEls = Array.from(document.querySelectorAll(
2258
+ '[class*="card"], [class*="Card"], [class*="tile"], [class*="Tile"], ' +
2259
+ '[class*="panel"], [class*="Panel"], [class*="feature"], ' +
2260
+ 'article, [class*="box"]:not(select):not(input)'
2261
+ )).filter(function(el) {
2262
+ var rect = el.getBoundingClientRect();
2263
+ return isVisible(el) && rect.width > 100 && rect.height > 80;
2264
+ }).slice(0, 10);
2265
+
2266
+ var cardMap = {};
2267
+ for (var ci = 0; ci < cardEls.length; ci++) {
2268
+ var cs = getStyles(cardEls[ci]);
2269
+ // gradient fill + glass blur kept in the key so a gradient/frosted card is a distinct variant
2270
+ var cKey = cs.background + "|" + cs.backgroundImage + "|" + cs.backdropFilter + "|" + cs.borderRadius + "|" + cs.border;
2271
+ if (!cardMap[cKey]) {
2272
+ cardMap[cKey] = {
2273
+ label: "card",
2274
+ background: cs.background,
2275
+ backgroundImage: cs.backgroundImage,
2276
+ backdropFilter: cs.backdropFilter,
2277
+ color: cs.color,
2278
+ padding: cs.padding,
2279
+ borderRadius: cs.borderRadius,
2280
+ border: cs.border,
2281
+ boxShadow: cs.boxShadow,
2282
+ fontSize: cs.fontSize,
2283
+ fontWeight: cs.fontWeight,
2284
+ height: cs.height
2285
+ };
2286
+ }
2287
+ }
2288
+ var cards = Object.values(cardMap).slice(0, 4);
2289
+
2290
+ // \u2500\u2500 4. Navigation \u2500\u2500
2291
+ var navEl = document.querySelector('nav, header, [role="navigation"], [class*="navbar"], [class*="header"]');
2292
+ var nav = null;
2293
+ if (navEl && isVisible(navEl)) {
2294
+ var ns = getStyles(navEl);
2295
+ nav = {
2296
+ label: "navigation",
2297
+ background: ns.background,
2298
+ backgroundImage: ns.backgroundImage,
2299
+ backdropFilter: ns.backdropFilter,
2300
+ color: ns.color,
2301
+ padding: ns.padding,
2302
+ borderRadius: ns.borderRadius,
2303
+ border: ns.border,
2304
+ boxShadow: ns.boxShadow,
2305
+ fontSize: ns.fontSize,
2306
+ fontWeight: ns.fontWeight,
2307
+ height: ns.height
2308
+ };
2309
+ }
2310
+
2311
+ // \u2500\u2500 4b. Chips / pills / badges / tags \u2500\u2500
2312
+ // selector by class-substring, PLUS a shape fallback (small + fully-rounded + short text) so
2313
+ // hashed/utility class names (Tailwind, CSS-modules) still get caught.
2314
+ var chipEls = Array.from(document.querySelectorAll(
2315
+ '[class*="pill"], [class*="Pill"], [class*="badge"], [class*="Badge"], ' +
2316
+ '[class*="chip"], [class*="Chip"], [class*="tag"], [class*="Tag"]'
2317
+ )).filter(function(el) {
2318
+ if (!isVisible(el) || el.closest('nav, [role="navigation"]')) return false;
2319
+ var r = el.getBoundingClientRect();
2320
+ var txt = (el.textContent || "").trim();
2321
+ return r.height > 0 && r.height <= 60 && r.width <= 360 && txt.length > 0 && txt.length <= 40;
2322
+ });
2323
+ var shapeChips = Array.from(document.querySelectorAll('span, div, li, a')).slice(0, 500).filter(function(el) {
2324
+ if (!isVisible(el) || el.closest('nav, [role="navigation"]')) return false;
2325
+ var st = getComputedStyle(el);
2326
+ var r = el.getBoundingClientRect();
2327
+ var rad = parseFloat(st.borderRadius) || 0;
2328
+ var txt = (el.textContent || "").trim();
2329
+ var hasSkin = (st.backgroundColor && st.backgroundColor !== "rgba(0, 0, 0, 0)" && st.backgroundColor !== "transparent") || (parseFloat(st.borderTopWidth) || 0) > 0;
2330
+ return hasSkin && r.height > 0 && r.height <= 44 && r.width <= 260 && rad >= (r.height / 2) - 1 && txt.length > 0 && txt.length <= 24 && el.children.length <= 1;
2331
+ });
2332
+ var allChips = chipEls.concat(shapeChips);
2333
+ var chipMap = {};
2334
+ for (var chi = 0; chi < allChips.length; chi++) {
2335
+ var chs = getStyles(allChips[chi]);
2336
+ var chKey = chs.background + "|" + chs.borderRadius + "|" + chs.border;
2337
+ if (!chipMap[chKey]) {
2338
+ chipMap[chKey] = {
2339
+ label: (allChips[chi].textContent || "").trim().slice(0, 24) || "chip",
2340
+ background: chs.background, color: chs.color, padding: chs.padding, borderRadius: chs.borderRadius,
2341
+ border: chs.border, boxShadow: chs.boxShadow, fontSize: chs.fontSize, fontWeight: chs.fontWeight, height: chs.height
2342
+ };
2343
+ }
2344
+ }
2345
+ var chips = Object.values(chipMap).slice(0, 4);
2346
+
2347
+ // \u2500\u2500 4c. Stat / metric cells (a big numeral + a small label) \u2500\u2500
2348
+ var statEls = Array.from(document.querySelectorAll(
2349
+ '[class*="stat"], [class*="Stat"], [class*="metric"], [class*="Metric"], ' +
2350
+ '[class*="kpi"], [class*="KPI"], [class*="figure"], [class*="Figure"]'
2351
+ )).filter(function(el) {
2352
+ if (!isVisible(el)) return false;
2353
+ var r = el.getBoundingClientRect();
2354
+ return r.height > 0 && r.height <= 400 && r.width <= 600;
2355
+ }).slice(0, 14);
2356
+ function biggestFontChild(el) {
2357
+ var best = 0, bestEl = null, kids = el.querySelectorAll("*");
2358
+ for (var i = 0; i < kids.length; i++) {
2359
+ if (!isVisible(kids[i])) continue;
2360
+ var fz = parseFloat(getComputedStyle(kids[i]).fontSize) || 0;
2361
+ if (fz > best) { best = fz; bestEl = kids[i]; }
2362
+ }
2363
+ return bestEl;
2364
+ }
2365
+ var statMap = {};
2366
+ for (var sti = 0; sti < statEls.length; sti++) {
2367
+ var numEl = biggestFontChild(statEls[sti]) || statEls[sti];
2368
+ var numFz = parseFloat(getComputedStyle(numEl).fontSize) || 0;
2369
+ if (numFz < 28) continue; // needs a genuinely large numeral to count as a stat cell
2370
+ var cst = getStyles(statEls[sti]);
2371
+ var nst = getStyles(numEl);
2372
+ var stKey = Math.round(numFz) + "|" + cst.background;
2373
+ if (!statMap[stKey]) {
2374
+ statMap[stKey] = {
2375
+ background: cst.background, borderRadius: cst.borderRadius, border: cst.border, boxShadow: cst.boxShadow,
2376
+ numberFontSize: nst.fontSize, numberFontWeight: nst.fontWeight, numberColor: nst.color
2377
+ };
2378
+ }
2379
+ }
2380
+ var statCells = Object.values(statMap).slice(0, 3);
2381
+
2382
+ // \u2500\u2500 4d. Tabs \u2500\u2500
2383
+ var tabEls = Array.from(document.querySelectorAll(
2384
+ '[role="tab"], [class*="tab"]:not([class*="table"]):not([class*="Table"])'
2385
+ )).filter(function(el) {
2386
+ if (!isVisible(el)) return false;
2387
+ var r = el.getBoundingClientRect();
2388
+ var txt = (el.textContent || "").trim();
2389
+ return r.height > 0 && r.height <= 64 && txt.length > 0 && txt.length <= 30;
2390
+ }).slice(0, 12);
2391
+ var tabMap = {};
2392
+ for (var tbi = 0; tbi < tabEls.length; tbi++) {
2393
+ var tst = getStyles(tabEls[tbi]);
2394
+ var tKey = tst.background + "|" + tst.color + "|" + tst.border;
2395
+ if (!tabMap[tKey]) {
2396
+ tabMap[tKey] = {
2397
+ label: (tabEls[tbi].textContent || "").trim().slice(0, 24) || "tab",
2398
+ background: tst.background, color: tst.color, padding: tst.padding, borderRadius: tst.borderRadius,
2399
+ border: tst.border, boxShadow: tst.boxShadow, fontSize: tst.fontSize, fontWeight: tst.fontWeight, height: tst.height
2400
+ };
2401
+ }
2402
+ }
2403
+ var tabs = Object.values(tabMap).slice(0, 4);
2404
+
2405
+ // \u2500\u2500 5. Spacing scale \u2500\u2500
2406
+ // Collect padding and margin values from visible elements
2407
+ var spacingCounts = {};
2408
+ var spacingSamples = Array.from(document.querySelectorAll(
2409
+ "section, div, article, main, aside, header, footer, nav, " +
2410
+ "button, a, p, h1, h2, h3, h4, li, ul, ol"
2411
+ )).slice(0, 200);
2412
+
2413
+ for (var si = 0; si < spacingSamples.length; si++) {
2414
+ if (!isVisible(spacingSamples[si])) continue;
2415
+ var ss = getComputedStyle(spacingSamples[si]);
2416
+ var props = [ss.paddingTop, ss.paddingRight, ss.paddingBottom, ss.paddingLeft,
2417
+ ss.marginTop, ss.marginRight, ss.marginBottom, ss.marginLeft,
2418
+ ss.gap, ss.rowGap, ss.columnGap];
2419
+ for (var pi = 0; pi < props.length; pi++) {
2420
+ var val = parseFloat(props[pi]);
2421
+ if (val > 0 && val <= 200) {
2422
+ var rounded = Math.round(val);
2423
+ spacingCounts[rounded] = (spacingCounts[rounded] || 0) + 1;
2424
+ }
2425
+ }
2426
+ }
2427
+
2428
+ // Find the most common spacing values (at least 3 occurrences)
2429
+ var spacingEntries = Object.entries(spacingCounts)
2430
+ .filter(function(e) { return e[1] >= 3; })
2431
+ .sort(function(a, b) { return b[1] - a[1]; });
2432
+ var observedSpacing = spacingEntries.map(function(e) { return parseInt(e[0]); }).sort(function(a,b) { return a - b; });
2433
+
2434
+ // Detect base unit \u2014 GCD of the top spacing values, clamped to 4 or 8
2435
+ var baseUnit = 8;
2436
+ if (observedSpacing.length >= 3) {
2437
+ var divisible4 = observedSpacing.filter(function(v) { return v % 4 === 0; }).length;
2438
+ var divisible8 = observedSpacing.filter(function(v) { return v % 8 === 0; }).length;
2439
+ baseUnit = (divisible4 > divisible8 * 1.5) ? 4 : 8;
2440
+ }
2441
+
2442
+ // \u2500\u2500 6. Border radius scale \u2500\u2500
2443
+ var radiusCounts = {};
2444
+ var radiusSamples = Array.from(document.querySelectorAll(
2445
+ "button, a, [class*='card'], [class*='btn'], input, select, textarea, " +
2446
+ "[class*='badge'], [class*='tag'], [class*='chip'], img, video"
2447
+ )).slice(0, 100);
2448
+
2449
+ for (var rsi = 0; rsi < radiusSamples.length; rsi++) {
2450
+ if (!isVisible(radiusSamples[rsi])) continue;
2451
+ var br = getComputedStyle(radiusSamples[rsi]).borderRadius;
2452
+ if (br && br !== "0px") {
2453
+ radiusCounts[br] = (radiusCounts[br] || 0) + 1;
2454
+ }
2455
+ }
2456
+
2457
+ var radius = Object.entries(radiusCounts)
2458
+ .filter(function(e) { return e[1] >= 2; })
2459
+ .sort(function(a, b) { return parseFloat(a[0]) - parseFloat(b[0]); })
2460
+ .map(function(e) { return e[0]; });
2461
+
2462
+ // \u2500\u2500 7. Box shadows \u2500\u2500
2463
+ var shadowCounts = {};
2464
+ // Keep semantic samples even when they are small or appear late in a dense page.
2465
+ var semanticShadowSamples = new Set(Array.from(document.querySelectorAll(
2466
+ "[class*='card'], [class*='Card'], button, [class*='btn'], " +
2467
+ "[class*='dropdown'], [class*='modal'], [class*='popover'], " +
2468
+ "nav, header, [class*='panel'], article"
2469
+ )).slice(0, 100));
2470
+ // Plain/utility-class boxes also carry elevation. Bound the extra style reads
2471
+ // and deduplicate elements that already matched a semantic selector.
2472
+ var shadowSamples = new Set([
2473
+ ...semanticShadowSamples,
2474
+ ...Array.from(document.querySelectorAll("div, section, aside, li, a, figure, main")).slice(0, 800)
2475
+ ]);
2476
+
2477
+ for (var shEl of shadowSamples) {
2478
+ if (!isVisible(shEl)) continue;
2479
+ if (!semanticShadowSamples.has(shEl)) {
2480
+ var shRect = shEl.getBoundingClientRect();
2481
+ if (shRect.width * shRect.height < 1600) continue;
2482
+ if (shRect.width > window.innerWidth * 0.95 && shRect.height > window.innerHeight * 0.9) continue;
2483
+ }
2484
+ var shVal = getComputedStyle(shEl).boxShadow;
2485
+ if (shVal && shVal !== "none") {
2486
+ shadowCounts[shVal] = (shadowCounts[shVal] || 0) + 1;
2487
+ }
2488
+ }
2489
+
2490
+ var shadows = Object.entries(shadowCounts)
2491
+ .sort(function(a, b) { return b[1] - a[1]; })
2492
+ .slice(0, 5)
2493
+ .map(function(e) { return { value: e[0], count: e[1] }; });
2494
+
2495
+ // \u2500\u2500 8. Dominant gradient / mesh backgrounds \u2500\u2500
2496
+ // A site's signature color wash (Stripe/ElevenLabs/Snowflake) lives in gradient background-images
2497
+ // on large blocks \u2014 often on a pseudo-element (::before glow orbs) rather than the block itself.
2498
+ // Weight each distinct gradient by the total on-screen area it covers; return the top few.
2499
+ var gradientArea = {};
2500
+ var gradSamples = Array.from(document.querySelectorAll(
2501
+ "body, main, section, header, div, [class*='hero'], [class*='gradient'], [class*='bg'], [class*='background']"
2502
+ )).slice(0, 400);
2503
+
2504
+ // max stop chroma (max\u2212min RGB) of a gradient: a vivid brand wash scores high, a neutral
2505
+ // white/cream scrim ~0. Used to rank washes so a small vivid gradient beats a big grey scrim.
2506
+ function gradientChroma(g) {
2507
+ var max = 0, re = /rgba?\\(\\s*(\\d+)[,\\s]+(\\d+)[,\\s]+(\\d+)/g, m;
2508
+ while ((m = re.exec(g))) {
2509
+ var r = +m[1], gr = +m[2], b = +m[3];
2510
+ var c = Math.max(r, gr, b) - Math.min(r, gr, b);
2511
+ if (c > max) max = c;
2512
+ }
2513
+ return max;
2514
+ }
2515
+
2516
+ function addGradient(val, area) {
2517
+ var g = gradientOf(val);
2518
+ if (!g || area < 20000) return; // ignore tiny decorative gradients
2519
+ var norm = g.replace(/\\s+/g, " ");
2520
+ gradientArea[norm] = (gradientArea[norm] || 0) + area;
2521
+ }
2522
+
2523
+ for (var gi = 0; gi < gradSamples.length; gi++) {
2524
+ var gel = gradSamples[gi];
2525
+ if (!isVisible(gel)) continue;
2526
+ var grect = gel.getBoundingClientRect();
2527
+ var garea = grect.width * grect.height;
2528
+ if (garea < 20000) continue;
2529
+ addGradient(getComputedStyle(gel).backgroundImage, garea);
2530
+ addGradient(getComputedStyle(gel, "::before").backgroundImage, garea);
2531
+ addGradient(getComputedStyle(gel, "::after").backgroundImage, garea);
2532
+ }
2533
+
2534
+ // rank by chroma-weighted area so the brand's signature color wash outranks a larger neutral scrim
2535
+ var backgrounds = Object.entries(gradientArea)
2536
+ .map(function(e) { return { value: e[0], area: Math.round(e[1]), score: e[1] * (1 + 2 * gradientChroma(e[0]) / 255) }; })
2537
+ .sort(function(a, b) { return b.score - a.score; })
2538
+ .slice(0, 6)
2539
+ .map(function(e) { return { value: e.value, area: e.area }; });
2540
+
2541
+ // \u2500\u2500 9. Frosted-glass panels (backdrop-filter) \u2500\u2500
2542
+ // A defining material on modern hero UIs (HeyGen's prompt box, Stripe's floating chrome): a
2543
+ // translucent surface with a backdrop blur. Capture the RAW fill (rgba/gradient \u2014 alpha preserved,
2544
+ // unlike rgbToHex) + the blur, ranked by area. This is what lets a frame render a real frosted card.
2545
+ var glassSamples = Array.from(document.querySelectorAll(
2546
+ "div, section, header, nav, aside, [class*='card'], [class*='panel'], [class*='glass'], [class*='blur'], [class*='modal'], [class*='overlay'], [class*='input']"
2547
+ )).slice(0, 400);
2548
+ var glassByKey = {};
2549
+ for (var qi = 0; qi < glassSamples.length; qi++) {
2550
+ var qel = glassSamples[qi];
2551
+ if (!isVisible(qel)) continue;
2552
+ var qs = getComputedStyle(qel);
2553
+ var bf = qs.backdropFilter || qs.webkitBackdropFilter || "";
2554
+ if (!bf || bf === "none" || bf.indexOf("blur") < 0) continue;
2555
+ var qrect = qel.getBoundingClientRect();
2556
+ var qarea = qrect.width * qrect.height;
2557
+ if (qarea < 8000) continue; // ignore tiny blurred chips
2558
+ // raw fill with alpha intact: prefer a translucent gradient, else the rgba background-color
2559
+ var gi2 = qs.backgroundImage;
2560
+ var rawFill = gi2 && gi2.indexOf("gradient") >= 0 ? gi2.replace(/\\s+/g, " ").trim() : qs.backgroundColor;
2561
+ var key = bf + "|" + rawFill + "|" + qs.borderRadius;
2562
+ if (!glassByKey[key]) {
2563
+ glassByKey[key] = {
2564
+ backdropFilter: bf,
2565
+ background: rawFill,
2566
+ border: qs.border,
2567
+ borderRadius: qs.borderRadius,
2568
+ boxShadow: qs.boxShadow === "none" ? "" : qs.boxShadow,
2569
+ area: 0
2570
+ };
2571
+ }
2572
+ glassByKey[key].area += qarea;
2573
+ }
2574
+ var glass = Object.values(glassByKey)
2575
+ .sort(function(a, b) { return b.area - a.area; })
2576
+ .slice(0, 3)
2577
+ .map(function(g) { return { backdropFilter: g.backdropFilter, background: g.background, border: g.border, borderRadius: g.borderRadius, boxShadow: g.boxShadow, area: Math.round(g.area) }; });
2578
+
2579
+ return {
2580
+ typography: uniqueTypo,
2581
+ spacing: { observed: observedSpacing.slice(0, 15), baseUnit: baseUnit },
2582
+ radius: radius,
2583
+ shadows: shadows,
2584
+ buttons: buttons,
2585
+ cards: cards,
2586
+ nav: nav,
2587
+ chips: chips,
2588
+ statCells: statCells,
2589
+ tabs: tabs,
2590
+ backgrounds: backgrounds,
2591
+ glass: glass
2592
+ };
2593
+ })()`;
2594
+ async function extractDesignStyles(page) {
2595
+ return page.evaluate(EXTRACT_DESIGN_STYLES_SCRIPT);
2596
+ }
2597
+
2598
+ // src/capture/mediaCapture.ts
2599
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
2600
+ import { join as join6, extname } from "path";
2601
+
2602
+ // src/capture/lottiePreviewRequests.ts
2603
+ var LOTTIE_RUNTIME_URL = "https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js";
2604
+ function allowedLottieRequest(url, resourceType) {
2605
+ if (url === LOTTIE_RUNTIME_URL) return true;
2606
+ if (!["image", "font", "stylesheet"].includes(resourceType)) return false;
2607
+ if (url.startsWith("data:") || url.startsWith("blob:")) return true;
2608
+ return !isPrivateUrl(url);
2609
+ }
2610
+ async function guardLottiePreviewRequests(page) {
2611
+ await page.setRequestInterception(true);
2612
+ page.on("request", (request) => {
2613
+ if (request.isInterceptResolutionHandled()) return;
2614
+ const action = allowedLottieRequest(request.url(), request.resourceType()) ? request.continue() : request.abort("blockedbyclient");
2615
+ void action.catch(() => {
2616
+ });
2617
+ });
2618
+ }
2619
+
2620
+ // src/capture/mediaCapture.ts
2621
+ function liveRemainingMs(budget, fallbackMs) {
2622
+ return budget.remainingMs?.() ?? fallbackMs;
2623
+ }
2624
+ async function saveLottieAnimations(discoveredLotties, lottieDir, outputDir, budget = {}) {
2625
+ ensureCaptureDirSync(outputDir, lottieDir);
2626
+ const byteBudget = budget.byteBudget ?? createCaptureDownloadBudget();
2627
+ let savedCount = 0;
2628
+ const savedHashes = /* @__PURE__ */ new Set();
2629
+ for (let li = 0; li < discoveredLotties.length && li < 10; li++) {
2630
+ if (liveRemainingMs(budget, 1e4) <= 0) break;
2631
+ const lottieItem = discoveredLotties[li];
2632
+ try {
2633
+ let jsonData;
2634
+ if (lottieItem.data) {
2635
+ jsonData = JSON.stringify(lottieItem.data);
2636
+ const size = Buffer.byteLength(jsonData);
2637
+ if (size > MAX_LOTTIE_BYTES) continue;
2638
+ if (lottieItem.dataBudget !== byteBudget) {
2639
+ if (size > byteBudget.remainingBytes) continue;
2640
+ byteBudget.remainingBytes -= size;
2641
+ }
2642
+ } else if (lottieItem.url) {
2643
+ const requestTimeoutMs = Math.min(1e4, liveRemainingMs(budget, 1e4));
2644
+ if (requestTimeoutMs <= 0) break;
2645
+ const res = await safeFetch(lottieItem.url, {
2646
+ signal: AbortSignal.timeout(requestTimeoutMs),
2647
+ headers: { "User-Agent": CAPTURE_USER_AGENT }
2648
+ });
2649
+ if (!res || !res.ok) continue;
2650
+ const buf = await readBoundedResponse(res, MAX_LOTTIE_BYTES, byteBudget);
2651
+ if (!buf) continue;
2652
+ jsonData = new URL(lottieItem.url).pathname.endsWith(".lottie") ? readLottieArchive(buf) ?? void 0 : buf.toString("utf8");
2653
+ }
2654
+ if (jsonData) {
2655
+ const hash = jsonData.slice(0, 200);
2656
+ if (savedHashes.has(hash)) continue;
2657
+ savedHashes.add(hash);
2658
+ if (!validLottieJson(jsonData)) continue;
2659
+ writeCaptureFileSync(join6(lottieDir, `animation-${savedCount}.json`), jsonData, "utf-8");
2660
+ savedCount++;
2661
+ }
2662
+ } catch {
2663
+ }
2664
+ }
2665
+ return savedCount;
2666
+ }
2667
+ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir, budget = {}) {
2668
+ const manifest = [];
2669
+ const previewDir = join6(lottieDir, "previews");
2670
+ ensureCaptureDirSync(outputDir, previewDir);
2671
+ for (const file of readdirSync2(lottieDir)) {
2672
+ if (!file.endsWith(".json")) continue;
2673
+ if (liveRemainingMs(budget, 1) <= 0) break;
2674
+ try {
2675
+ const raw = JSON.parse(readFileSync3(join6(lottieDir, file), "utf-8"));
2676
+ const fr = raw.fr || 30;
2677
+ const dur = ((raw.op || 0) - (raw.ip || 0)) / fr;
2678
+ const previewName = file.replace(".json", "-preview.png");
2679
+ let preview;
2680
+ const fileSize = statSync2(join6(lottieDir, file)).size;
2681
+ if (fileSize > 2e6) continue;
2682
+ let previewPage;
2683
+ try {
2684
+ if (liveRemainingMs(budget, 1) <= 0) break;
2685
+ previewPage = await chromeBrowser.newPage();
2686
+ if (liveRemainingMs(budget, 1) <= 0) break;
2687
+ await guardLottiePreviewRequests(previewPage);
2688
+ await previewPage.setViewport({ width: 400, height: 400 });
2689
+ const animData = JSON.parse(readFileSync3(join6(lottieDir, file), "utf-8"));
2690
+ const midFrame = Math.floor(((raw.op || 0) - (raw.ip || 0)) * 0.3);
2691
+ await previewPage.setContent(
2692
+ `<!DOCTYPE html>
2693
+ <html><head>
2694
+ <script src="${LOTTIE_RUNTIME_URL}"></script>
2695
+ <style>*{margin:0;padding:0;background:transparent}#c{width:400px;height:400px}</style>
2696
+ </head><body><div id="c"></div></body></html>`,
2697
+ { waitUntil: "load", timeout: 1e4 }
2698
+ );
2699
+ await previewPage.evaluate(
2700
+ (data, frame) => {
2701
+ const a = window.lottie.loadAnimation({
2702
+ container: document.getElementById("c"),
2703
+ renderer: "svg",
2704
+ loop: false,
2705
+ autoplay: false,
2706
+ animationData: data
2707
+ });
2708
+ a.addEventListener("DOMLoaded", () => {
2709
+ a.goToAndStop(frame, true);
2710
+ window.__READY = true;
2711
+ });
2712
+ },
2713
+ animData,
2714
+ midFrame
2715
+ );
2716
+ await previewPage.waitForFunction(() => window.__READY === true, { timeout: 5e3 }).catch(() => {
2717
+ });
2718
+ if (liveRemainingMs(budget, 1) > 0) {
2719
+ const shot = await previewPage.screenshot({ type: "png", omitBackground: true });
2720
+ writeCaptureFileSync(join6(previewDir, previewName), shot);
2721
+ preview = `assets/lottie/previews/${previewName}`;
2722
+ }
2723
+ } catch {
2724
+ } finally {
2725
+ await previewPage?.close().catch(() => {
2726
+ });
2727
+ }
2728
+ manifest.push({
2729
+ file: `assets/lottie/${file}`,
2730
+ ...preview ? { preview } : {},
2731
+ name: raw.nm || file,
2732
+ width: raw.w || 0,
2733
+ height: raw.h || 0,
2734
+ duration: Math.round(dur * 10) / 10,
2735
+ frameRate: fr,
2736
+ layers: (raw.layers || []).length
2737
+ });
2738
+ } catch {
2739
+ }
2740
+ }
2741
+ if (manifest.length > 0) {
2742
+ writeCaptureFileSync(
2743
+ join6(outputDir, "extracted", "lottie-manifest.json"),
2744
+ JSON.stringify(manifest, null, 2),
2745
+ "utf-8"
2746
+ );
2747
+ }
2748
+ }
2749
+ var MAX_VIDEO_BYTES = 75 * 1024 * 1024;
2750
+ var DOWNLOADABLE_VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".webm", ".mov", ".m4v"]);
2751
+ var VIDEO_DOWNLOAD_TIMEOUT_MS = 12e4;
2752
+ function remainingVideoDownloadTimeoutMs(budgetStartedAt, budgetMs, now = Date.now()) {
2753
+ return Math.max(0, Math.min(VIDEO_DOWNLOAD_TIMEOUT_MS, budgetMs - (now - budgetStartedAt)));
2754
+ }
2755
+ async function downloadVideoBody(srcUrl, filename, videosDir, timeoutMs) {
2756
+ if (isPrivateUrl(srcUrl)) return null;
2757
+ let ext = "";
2758
+ try {
2759
+ ext = extname(new URL(srcUrl).pathname).toLowerCase();
2760
+ } catch {
2761
+ return null;
2762
+ }
2763
+ if (!DOWNLOADABLE_VIDEO_EXTS.has(ext)) return null;
2764
+ try {
2765
+ const res = await safeFetch(srcUrl, {
2766
+ signal: AbortSignal.timeout(timeoutMs),
2767
+ // bounded by both per-request and aggregate capture budgets
2768
+ headers: { "User-Agent": CAPTURE_USER_AGENT }
2769
+ });
2770
+ if (!res || !res.ok || !res.body) return null;
2771
+ const ct = (res.headers.get("content-type") || "").toLowerCase();
2772
+ if (ct && !ct.startsWith("video/") && !ct.includes("octet-stream")) return null;
2773
+ const declared = Number(res.headers.get("content-length") || 0);
2774
+ if (declared && declared > MAX_VIDEO_BYTES) return null;
2775
+ const chunks = [];
2776
+ let total = 0;
2777
+ for await (const chunk of res.body) {
2778
+ total += chunk.length;
2779
+ if (total > MAX_VIDEO_BYTES) return null;
2780
+ chunks.push(Buffer.from(chunk));
2781
+ }
2782
+ if (total < 1024) return null;
2783
+ const safe = /\.[a-z0-9]+$/i.test(filename) ? filename.replace(/[^\w.-]/g, "_") : `video${ext}`;
2784
+ writeCaptureFileSync(join6(videosDir, safe), Buffer.concat(chunks));
2785
+ return `assets/videos/${safe}`;
2786
+ } catch {
2787
+ return null;
2788
+ }
2789
+ }
2790
+ var VIDEO_SCAN_EXPR = `(() => {
2791
+ var videos = Array.from(document.querySelectorAll('video'));
2792
+ return videos.map(function(v) {
2793
+ var src = v.src || v.currentSrc || (v.querySelector('source') ? v.querySelector('source').src : '');
2794
+ if (!src || !src.startsWith('http')) return null;
2795
+ var rect = v.getBoundingClientRect();
2796
+ if (rect.width < 10 || rect.height < 10) return null;
2797
+ var heading = '';
2798
+ var el = v;
2799
+ for (var i = 0; i < 8; i++) {
2800
+ el = el.parentElement;
2801
+ if (!el) break;
2802
+ var h = el.querySelector('h1,h2,h3,h4');
2803
+ if (h) { heading = h.textContent.trim().slice(0, 100); break; }
2804
+ }
2805
+ var caption = '';
2806
+ el = v;
2807
+ for (var j = 0; j < 5; j++) {
2808
+ el = el.parentElement;
2809
+ if (!el) break;
2810
+ var p = el.querySelector('p,figcaption,[class*="caption"],[class*="desc"]');
2811
+ if (p) { caption = p.textContent.trim().slice(0, 200); break; }
2812
+ }
2813
+ var ariaLabel = v.getAttribute('aria-label') || v.getAttribute('title') || '';
2814
+ var wrapper = v.parentElement;
2815
+ if (!ariaLabel && wrapper) ariaLabel = wrapper.getAttribute('aria-label') || '';
2816
+ return {
2817
+ src: src,
2818
+ // width/height are the DOM display box (what the page laid the element out
2819
+ // at); sourceWidth/Height are the clip's intrinsic resolution. Size planners
2820
+ // off the source dims, not the display box (a 1920x1080 clip can display at
2821
+ // 904x613). 0 when metadata has not loaded yet.
2822
+ width: Math.round(rect.width),
2823
+ height: Math.round(rect.height),
2824
+ sourceWidth: v.videoWidth || 0,
2825
+ sourceHeight: v.videoHeight || 0,
2826
+ top: Math.round(rect.top),
2827
+ left: Math.round(rect.left),
2828
+ heading: heading,
2829
+ caption: caption,
2830
+ ariaLabel: ariaLabel,
2831
+ filename: src.split('/').pop().split('?')[0],
2832
+ };
2833
+ }).filter(Boolean);
2834
+ })()`;
2835
+ async function scanVideoDom(page) {
2836
+ return await page.evaluate(VIDEO_SCAN_EXPR);
2837
+ }
2838
+ async function sampleVideoDom(page, budgetMs, netSet, budget) {
2839
+ const seen = /* @__PURE__ */ new Map();
2840
+ const start = Date.now();
2841
+ let stale = 0;
2842
+ while (Date.now() - start < budgetMs && stale < 3 && liveRemainingMs(budget, 1) > 0) {
2843
+ let grew = false;
2844
+ const netBefore = netSet.size;
2845
+ for (const d of await scanVideoDom(page)) {
2846
+ if (!seen.has(d.src)) {
2847
+ seen.set(d.src, d);
2848
+ grew = true;
2849
+ }
2850
+ }
2851
+ if (netSet.size > netBefore) grew = true;
2852
+ stale = grew ? 0 : stale + 1;
2853
+ const waitMs = Math.min(
2854
+ 2e3,
2855
+ Math.max(0, budgetMs - (Date.now() - start)),
2856
+ Math.max(0, liveRemainingMs(budget, 2e3))
2857
+ );
2858
+ if (waitMs > 0) await new Promise((r) => setTimeout(r, waitMs));
2859
+ }
2860
+ return [...seen.values()];
2861
+ }
2862
+ async function captureVideoManifest(page, outputDir, progress, opts) {
2863
+ const netSet = opts?.networkVideoUrls ?? /* @__PURE__ */ new Set();
2864
+ const sampleMs = opts?.sampleMs ?? 0;
2865
+ const downloadBudgetMs = opts?.downloadBudgetMs ?? 18e4;
2866
+ const initial = await scanVideoDom(page);
2867
+ const domVideos = initial.length > 0 && sampleMs > 0 ? await sampleVideoDom(page, sampleMs, netSet, opts ?? {}) : initial;
2868
+ const fileKey = (s) => (s.split("/").pop() || s).split("?")[0];
2869
+ const byKey = /* @__PURE__ */ new Map();
2870
+ for (const d of domVideos) {
2871
+ const k = d.filename || fileKey(d.src);
2872
+ if (!byKey.has(k)) byKey.set(k, { ...d, rich: true });
2873
+ }
2874
+ for (const url of netSet) {
2875
+ if (!url.startsWith("http")) continue;
2876
+ const k = fileKey(url);
2877
+ if (!byKey.has(k)) {
2878
+ byKey.set(k, {
2879
+ src: url,
2880
+ filename: k,
2881
+ width: 0,
2882
+ height: 0,
2883
+ sourceWidth: 0,
2884
+ sourceHeight: 0,
2885
+ top: 0,
2886
+ left: 0,
2887
+ heading: "",
2888
+ caption: "",
2889
+ ariaLabel: "",
2890
+ rich: false
2891
+ });
2892
+ }
2893
+ }
2894
+ const merged = [...byKey.values()];
2895
+ if (merged.length === 0) return;
2896
+ const videoManifestDir = join6(outputDir, "assets", "videos");
2897
+ ensureCaptureDirSync(outputDir, videoManifestDir);
2898
+ const previewDir = join6(videoManifestDir, "previews");
2899
+ ensureCaptureDirSync(outputDir, previewDir);
2900
+ const videoManifest = [];
2901
+ const dlStart = Date.now();
2902
+ for (let vi = 0; vi < merged.length && vi < 20; vi++) {
2903
+ const iterationRemainingMs = liveRemainingMs(
2904
+ opts ?? {},
2905
+ remainingVideoDownloadTimeoutMs(dlStart, downloadBudgetMs)
2906
+ );
2907
+ if (iterationRemainingMs <= 0) break;
2908
+ const v = merged[vi];
2909
+ let preview;
2910
+ if (v.rich) {
2911
+ const previewName = `video-${vi}-preview.png`;
2912
+ try {
2913
+ await page.evaluate(`window.scrollTo(0, ${Math.max(0, v.top - 100)})`);
2914
+ await new Promise((r) => setTimeout(r, 300));
2915
+ const rect = await page.evaluate((fn) => {
2916
+ const vid = [...document.querySelectorAll("video")].find(
2917
+ (x) => (x.src || x.currentSrc || "").includes(fn)
2918
+ );
2919
+ if (!vid) return null;
2920
+ vid.currentTime = 0.1;
2921
+ return vid.getBoundingClientRect().toJSON();
2922
+ }, v.filename);
2923
+ if (rect && rect.width >= 10) {
2924
+ await new Promise((r) => setTimeout(r, 200));
2925
+ if (liveRemainingMs(opts ?? {}, 1) > 0) {
2926
+ const shot = await page.screenshot({
2927
+ type: "png",
2928
+ clip: {
2929
+ x: Math.max(0, rect.x),
2930
+ y: Math.max(0, rect.y),
2931
+ width: Math.min(rect.width, 1920),
2932
+ height: Math.min(rect.height, 1080)
2933
+ }
2934
+ });
2935
+ writeCaptureFileSync(join6(previewDir, previewName), shot);
2936
+ preview = `assets/videos/previews/${previewName}`;
2937
+ }
2938
+ }
2939
+ } catch {
2940
+ }
2941
+ }
2942
+ const downloadTimeoutMs = Math.min(
2943
+ remainingVideoDownloadTimeoutMs(dlStart, downloadBudgetMs),
2944
+ liveRemainingMs(opts ?? {}, VIDEO_DOWNLOAD_TIMEOUT_MS)
2945
+ );
2946
+ const savedPath = downloadTimeoutMs > 0 ? await downloadVideoBody(v.src, v.filename, videoManifestDir, downloadTimeoutMs) : null;
2947
+ if (!preview && !savedPath) continue;
2948
+ videoManifest.push({
2949
+ index: vi,
2950
+ url: v.src,
2951
+ filename: v.filename,
2952
+ width: v.width,
2953
+ height: v.height,
2954
+ sourceWidth: v.sourceWidth,
2955
+ sourceHeight: v.sourceHeight,
2956
+ heading: v.heading,
2957
+ caption: v.caption,
2958
+ ariaLabel: v.ariaLabel,
2959
+ ...preview ? { preview } : {},
2960
+ ...savedPath ? { localPath: savedPath } : {}
2961
+ });
2962
+ }
2963
+ if (videoManifest.length > 0) {
2964
+ writeCaptureFileSync(
2965
+ join6(outputDir, "extracted", "video-manifest.json"),
2966
+ JSON.stringify(videoManifest, null, 2),
2967
+ "utf-8"
2968
+ );
2969
+ const downloaded = videoManifest.filter((v) => v.localPath).length;
2970
+ const previews = videoManifest.filter((v) => v.preview).length;
2971
+ progress(
2972
+ "design",
2973
+ `${videoManifest.length} video(s) discovered` + (previews ? `, ${previews} preview(s)` : "") + (downloaded ? `, ${downloaded} body downloaded` : "")
2974
+ );
2975
+ }
2976
+ }
2977
+
2978
+ // src/capture/filterExtractedScripts.ts
2979
+ var FRAMEWORK_SRC_PATTERNS = [
2980
+ /_next\/static\/chunks\/(main|framework|webpack|pages\/)/,
2981
+ /_next\/static\/chunks\/app\//,
2982
+ /_buildManifest\.js/,
2983
+ /_ssgManifest\.js/
2984
+ ];
2985
+ var NEXT_BOOTSTRAP_MARKERS = [
2986
+ "__next_f",
2987
+ "self.__next_f",
2988
+ "__NEXT_LOADED_PAGES__",
2989
+ "_N_E",
2990
+ "__NEXT_P"
2991
+ ];
2992
+ function removeMatchingScripts(source, shouldRemove) {
2993
+ const rootId = "__hf_root";
2994
+ const { document: document2 } = parseHTML(`<div id="${rootId}">${source}</div>`);
2995
+ for (const script of document2.querySelectorAll("script")) {
2996
+ if (script.id === "__NEXT_DATA__" || shouldRemove(script)) script.remove();
2997
+ }
2998
+ return document2.getElementById(rootId)?.innerHTML ?? "";
2999
+ }
3000
+ function filterExtractedScripts(bodyHtml, headHtml) {
3001
+ const body = removeMatchingScripts(
3002
+ bodyHtml,
3003
+ (script) => NEXT_BOOTSTRAP_MARKERS.some((marker) => script.textContent?.includes(marker))
3004
+ );
3005
+ const head = removeMatchingScripts(headHtml, (script) => {
3006
+ const src = script.getAttribute("src") ?? "";
3007
+ return FRAMEWORK_SRC_PATTERNS.some((pattern) => pattern.test(src));
3008
+ });
3009
+ return { bodyHtml: body, headHtml: head };
3010
+ }
3011
+
3012
+ // src/capture/coreExtractionPhase.ts
3013
+ async function runCoreExtraction(input) {
3014
+ let {
3015
+ page1,
3016
+ chromeBrowser,
3017
+ cdp,
3018
+ cdpAnims,
3019
+ state,
3020
+ outputDir,
3021
+ warnings,
3022
+ progress,
3023
+ remainingMs,
3024
+ pageContentCheck,
3025
+ contentCheckTimedOut,
3026
+ discoveredLotties,
3027
+ lottieDiscovery,
3028
+ discoveredVideoUrls,
3029
+ animationCatalog,
3030
+ capturedShaders,
3031
+ catalogedAssets,
3032
+ detectedLibraries,
3033
+ visibleTextContent,
3034
+ faviconLinks,
3035
+ tokens,
3036
+ extracted,
3037
+ screenshots,
3038
+ downloadByteBudget,
3039
+ canWrite
3040
+ } = input;
3041
+ const runLazyAndLottie = async () => {
3042
+ if (!contentCheckTimedOut && pageContentCheck.textLength < 100) {
3043
+ const reason = "Page has very little text content (" + pageContentCheck.textLength + " chars) \u2014 may be blocked or a client-rendered SPA that needs more time";
3044
+ warnings.push(reason);
3045
+ progress("warn", reason);
3046
+ }
3047
+ const lazyLoadBudgetMs = Math.min(15e3, remainingMs());
3048
+ const lazyScroll = await lazyScrollForCapture(page1, lazyLoadBudgetMs, {
3049
+ onWarning: (message) => {
3050
+ warnings.push(message);
3051
+ progress("warn", message);
3052
+ }
3053
+ });
3054
+ if (lazyScroll.timedOut && !lazyScroll.degraded) {
3055
+ const message = `lazy-scroll stopped after ${lazyScroll.steps} steps (budget ${lazyLoadBudgetMs}ms)`;
3056
+ warnings.push(message);
3057
+ progress("warn", message);
3058
+ }
3059
+ await new Promise((r) => setTimeout(r, 300));
3060
+ };
3061
+ await runLazyAndLottie();
3062
+ const runAnimationDiscovery = async () => {
3063
+ const scanDomLotties = async () => {
3064
+ try {
3065
+ const domLotties = await page1.evaluate(`(() => {
3066
+ var urls = [];
3067
+ document.querySelectorAll('dotlottie-wc, lottie-player, dotlottie-player').forEach(function(el) {
3068
+ var src = el.getAttribute('src');
3069
+ if (src) urls.push(src);
3070
+ });
3071
+ // Also check lottie-web registered animations
3072
+ if (window.lottie && window.lottie.getRegisteredAnimations) {
3073
+ window.lottie.getRegisteredAnimations().forEach(function(anim) {
3074
+ if (anim.path) urls.push(anim.path);
3075
+ });
3076
+ }
3077
+ return urls;
3078
+ })()`);
3079
+ if (Array.isArray(domLotties)) {
3080
+ for (const lottieUrl of domLotties) {
3081
+ if (typeof lottieUrl === "string" && !discoveredLotties.some((l) => l.url === lottieUrl)) {
3082
+ discoveredLotties.push({ url: lottieUrl });
3083
+ }
3084
+ }
3085
+ }
3086
+ } catch {
3087
+ }
3088
+ };
3089
+ await scanDomLotties();
3090
+ const collectNetworkLotties = async () => {
3091
+ for (const found of await lottieDiscovery.run(downloadByteBudget, remainingMs)) {
3092
+ const existing = discoveredLotties.findIndex(
3093
+ (item) => item.url === found.url
3094
+ );
3095
+ if (existing < 0) discoveredLotties.push(found);
3096
+ else discoveredLotties[existing] = found;
3097
+ }
3098
+ };
3099
+ await collectNetworkLotties();
3100
+ const saveDiscoveredLotties = async () => {
3101
+ if (discoveredLotties.length > 0 && remainingMs() > 0) {
3102
+ const lottieDir = join7(outputDir, "assets", "lottie");
3103
+ if (!canWrite()) return;
3104
+ const lottieBudget = { remainingMs, byteBudget: downloadByteBudget };
3105
+ const savedCount = await saveLottieAnimations(
3106
+ discoveredLotties,
3107
+ lottieDir,
3108
+ outputDir,
3109
+ lottieBudget
3110
+ );
3111
+ if (savedCount > 0 && remainingMs() > 0 && canWrite()) {
3112
+ await renderLottiePreviews(chromeBrowser, lottieDir, outputDir, lottieBudget);
3113
+ progress("lottie", `${savedCount} Lottie animation(s) saved`);
3114
+ }
3115
+ }
3116
+ };
3117
+ await saveDiscoveredLotties();
3118
+ };
3119
+ await runAnimationDiscovery();
3120
+ const runShaderCapture = async () => {
3121
+ try {
3122
+ const shaders = await page1.evaluate(`window.__capturedShaders || []`);
3123
+ if (Array.isArray(shaders) && shaders.length > 0) {
3124
+ const seen = /* @__PURE__ */ new Set();
3125
+ const unique = shaders.filter((s) => {
3126
+ if (seen.has(s.source)) return false;
3127
+ seen.add(s.source);
3128
+ return true;
3129
+ });
3130
+ capturedShaders = unique;
3131
+ if (canWrite()) {
3132
+ writeCaptureFileSync(
3133
+ join7(outputDir, "extracted", "shaders.json"),
3134
+ JSON.stringify(unique, null, 2),
3135
+ "utf-8"
3136
+ );
3137
+ }
3138
+ progress("shaders", `${unique.length} WebGL shader(s) captured`);
3139
+ }
3140
+ } catch {
3141
+ }
3142
+ };
3143
+ await runShaderCapture();
3144
+ const runTokenExtraction = async () => {
3145
+ progress("tokens", "Extracting design tokens...");
3146
+ tokens = await extractTokens(page1);
3147
+ state.tokens = tokens;
3148
+ if (canWrite()) {
3149
+ writeCaptureFileSync(
3150
+ join7(outputDir, "extracted", "tokens.json"),
3151
+ serializeTokensForCapture(tokens),
3152
+ "utf-8"
3153
+ );
3154
+ }
3155
+ progress("style", "Extracting design styles...");
3156
+ try {
3157
+ const designStyles = await extractDesignStyles(page1);
3158
+ state.designStyles = designStyles;
3159
+ if (canWrite()) {
3160
+ writeCaptureFileSync(
3161
+ join7(outputDir, "extracted", "design-styles.json"),
3162
+ JSON.stringify(designStyles, null, 2),
3163
+ "utf-8"
3164
+ );
3165
+ }
3166
+ progress(
3167
+ "tokens",
3168
+ `${designStyles.typography.length} typography roles, ${designStyles.buttons.length} button styles, ${designStyles.shadows.length} shadow values extracted`
3169
+ );
3170
+ } catch (err) {
3171
+ const errMsg = err instanceof Error ? `${err.message}
3172
+ ${err.stack}` : normalizeErrorMessage(err);
3173
+ console.error(` \u26A0 Design style extraction failed: ${errMsg}`);
3174
+ warnings.push(`Design style extraction failed: ${errMsg}`);
3175
+ }
3176
+ };
3177
+ await runTokenExtraction();
3178
+ const runAnimationCapture = async () => {
3179
+ progress("animations", "Cataloging animations...");
3180
+ try {
3181
+ const animationOutcome = await collectAnimationCatalog(page1, cdpAnims, cdp, {
3182
+ scrollBudgetMs: Math.min(8e3, remainingMs()),
3183
+ evaluateBudgetMs: Math.min(15e3, remainingMs())
3184
+ });
3185
+ animationCatalog = animationOutcome.catalog;
3186
+ if (animationOutcome.timedOut) {
3187
+ const message = "animation catalog evaluate timed out; continuing without animation catalog";
3188
+ warnings.push(message);
3189
+ progress("warn", message);
3190
+ }
3191
+ } catch (err) {
3192
+ if (!isDegradableEvaluateTimeoutError(err)) {
3193
+ throw err;
3194
+ }
3195
+ const message = "animation catalog evaluate timed out; continuing without animation catalog";
3196
+ warnings.push(message);
3197
+ progress("warn", message);
3198
+ try {
3199
+ await cdp.send("Animation.disable");
3200
+ } catch {
3201
+ }
3202
+ }
3203
+ progress("screenshots", "Capturing scroll screenshots...");
3204
+ const { captureScrollScreenshots } = await import("./screenshotCapture-ECBTLCU6.js");
3205
+ try {
3206
+ if (!canWrite()) return;
3207
+ screenshots = await captureScrollScreenshots(page1, outputDir, { remainingMs });
3208
+ state.screenshots = screenshots;
3209
+ progress("screenshots", `${screenshots.length} scroll screenshots captured`);
3210
+ } catch (err) {
3211
+ if (!isDegradableEvaluateTimeoutError(err)) {
3212
+ throw err;
3213
+ }
3214
+ const message = "scroll screenshots timed out; continuing without screenshots";
3215
+ warnings.push(message);
3216
+ progress("warn", message);
3217
+ }
3218
+ };
3219
+ await runAnimationCapture();
3220
+ const runHtmlExtraction = async () => {
3221
+ progress("design", "Cataloging assets...");
3222
+ try {
3223
+ const { catalogAssets } = await import("./assetCataloger-VBLPHX24.js");
3224
+ catalogedAssets = await catalogAssets(page1);
3225
+ progress("design", `${catalogedAssets.length} assets cataloged`);
3226
+ if (catalogedAssets.length === 0) {
3227
+ warnings.push(
3228
+ "Asset catalog is empty \u2014 no images will be downloaded. The page may use non-standard image loading."
3229
+ );
3230
+ }
3231
+ } catch (err) {
3232
+ warnings.push(`Asset cataloging failed (no images will be downloaded): ${err}`);
3233
+ }
3234
+ progress("extract", "Extracting HTML & CSS...");
3235
+ extracted = await extractHtml(page1, { settleTime: 1e3 });
3236
+ state.extracted = extracted;
3237
+ extracted.bodyHtml = extracted.bodyHtml.replace(/\s*data-reactroot="[^"]*"/g, "").replace(/\s*data-reactroot/g, "");
3238
+ const filteredScripts = filterExtractedScripts(extracted.bodyHtml, extracted.headHtml);
3239
+ extracted.bodyHtml = filteredScripts.bodyHtml;
3240
+ extracted.headHtml = filteredScripts.headHtml;
3241
+ try {
3242
+ const videoBudgetMs = remainingMs();
3243
+ if (videoBudgetMs > 0 && canWrite()) {
3244
+ await captureVideoManifest(page1, outputDir, progress, {
3245
+ networkVideoUrls: discoveredVideoUrls,
3246
+ // Layer 1 (live Set, read after sampling)
3247
+ sampleMs: Math.min(12e3, videoBudgetMs),
3248
+ // Layer 2: poll DOM within the shared budget
3249
+ downloadBudgetMs: videoBudgetMs,
3250
+ remainingMs
3251
+ });
3252
+ }
3253
+ } catch (err) {
3254
+ if (err instanceof CaptureDirRefusedError)
3255
+ warnings.push(`Video manifest skipped: ${err.message}`);
3256
+ }
3257
+ detectedLibraries = await detectLibraries(page1, capturedShaders);
3258
+ visibleTextContent = await extractVisibleText(page1);
3259
+ faviconLinks = await page1.evaluate(`(() => {
3260
+ var iconEls = Array.from(document.querySelectorAll('link[rel*="icon"], link[rel="apple-touch-icon"]'));
3261
+ return iconEls.map(function(l) {
3262
+ return {
3263
+ rel: l.rel,
3264
+ href: l.href,
3265
+ sizes: l.getAttribute('sizes'),
3266
+ type: l.getAttribute('type'),
3267
+ };
3268
+ });
3269
+ })()`);
3270
+ await page1.close();
3271
+ };
3272
+ await runHtmlExtraction();
3273
+ return {
3274
+ animationCatalog,
3275
+ capturedShaders,
3276
+ catalogedAssets,
3277
+ detectedLibraries,
3278
+ visibleTextContent,
3279
+ faviconLinks,
3280
+ tokens,
3281
+ extracted,
3282
+ screenshots
3283
+ };
3284
+ }
3285
+
3286
+ // src/capture/postExtractionPhase.ts
3287
+ import { existsSync as existsSync4 } from "fs";
3288
+ import { join as join9 } from "path";
3289
+
3290
+ // src/capture/fontMetadataExtractor.ts
3291
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
3292
+ import { join as join8 } from "path";
3293
+ import * as fontkit from "fontkit";
3294
+ function isFontCollection(value) {
3295
+ return value.type === "TTC" || value.type === "DFont";
3296
+ }
3297
+ function extractFontMetadata(fontsDir, outputPath) {
3298
+ const files = [];
3299
+ const unidentified = [];
3300
+ if (existsSync3(fontsDir)) {
3301
+ const fontFiles = readdirSync3(fontsDir).filter((f) => /\.(woff2?|ttf|otf)$/i.test(f));
3302
+ for (const filename of fontFiles) {
3303
+ const fullPath = join8(fontsDir, filename);
3304
+ const meta = readSingleFont(fullPath, filename);
3305
+ if (meta.identified) {
3306
+ files.push(meta);
3307
+ } else {
3308
+ files.push(meta);
3309
+ unidentified.push(filename);
3310
+ }
3311
+ }
3312
+ }
3313
+ const families = aggregateFamilies(files);
3314
+ const manifest = {
3315
+ files,
3316
+ families,
3317
+ unidentified,
3318
+ meta: {
3319
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3320
+ // Record just the tool name; the version moves with the dep and would
3321
+ // otherwise drift from a hardcoded string on every fontkit bump.
3322
+ tool: "fontkit"
3323
+ }
3324
+ };
3325
+ writeCaptureFileSync(outputPath, JSON.stringify(manifest, null, 2), "utf-8");
3326
+ return manifest;
3327
+ }
3328
+ function readSingleFont(fullPath, filename) {
3329
+ const empty = {
3330
+ file: filename,
3331
+ family: "",
3332
+ rawFamily: "",
3333
+ subfamily: "",
3334
+ postscript: "",
3335
+ weight: 0,
3336
+ style: "normal",
3337
+ variationAxes: [],
3338
+ identified: false,
3339
+ isIcon: false
3340
+ };
3341
+ try {
3342
+ const buf = readFileSync4(fullPath);
3343
+ const created = fontkit.create(buf);
3344
+ const font = isFontCollection(created) ? created.fonts[0] : created;
3345
+ if (!font) return empty;
3346
+ const rawFamily = (font.familyName || "").trim();
3347
+ const subfamily = (font.subfamilyName || "").trim();
3348
+ const postscript = (font.postscriptName || "").trim();
3349
+ const fsSelection = font["OS/2"]?.fsSelection;
3350
+ const italicBit = Boolean(fsSelection?.italic || fsSelection?.oblique);
3351
+ const style = italicBit || /italic|oblique/i.test(subfamily) ? "italic" : "normal";
3352
+ const variationAxes = font.variationAxes ? Object.keys(font.variationAxes) : [];
3353
+ if (!rawFamily && !postscript) return empty;
3354
+ const familyForCanonicalization = rawFamily || deriveFamilyFromPostscript(postscript);
3355
+ const { canonical, inferredWeight } = canonicalizeFamily(familyForCanonicalization);
3356
+ const weight = font["OS/2"]?.usWeightClass ?? inferredWeight ?? inferWeightFromSubfamily(subfamily);
3357
+ return {
3358
+ file: filename,
3359
+ family: canonical || familyForCanonicalization,
3360
+ rawFamily: familyForCanonicalization,
3361
+ subfamily,
3362
+ postscript,
3363
+ weight,
3364
+ style,
3365
+ variationAxes,
3366
+ identified: true,
3367
+ isIcon: detectIconFont(font)
3368
+ };
3369
+ } catch {
3370
+ return empty;
3371
+ }
3372
+ }
3373
+ function detectIconFont(font) {
3374
+ const f = font;
3375
+ try {
3376
+ return isIconCharacterSet(Array.isArray(f.characterSet) ? f.characterSet : []);
3377
+ } catch {
3378
+ return false;
3379
+ }
3380
+ }
3381
+ function isIconCharacterSet(characterSet) {
3382
+ if (!characterSet.length) return false;
3383
+ const isLatinLetter = (cp) => cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122;
3384
+ if (characterSet.filter(isLatinLetter).length >= 26) return false;
3385
+ const inPua = (cp) => cp >= 57344 && cp <= 63743 || cp >= 983040 && cp <= 1114109;
3386
+ return characterSet.filter(inPua).length / characterSet.length > 0.5;
3387
+ }
3388
+ function aggregateFamilies(files) {
3389
+ const byFamily = /* @__PURE__ */ new Map();
3390
+ for (const f of files) {
3391
+ if (!f.family) continue;
3392
+ let entry = byFamily.get(f.family);
3393
+ if (!entry) {
3394
+ entry = { family: f.family, weights: [], variable: false, fileCount: 0, files: [] };
3395
+ byFamily.set(f.family, entry);
3396
+ }
3397
+ entry.fileCount++;
3398
+ entry.files.push(f.file);
3399
+ if (f.variationAxes.length > 0) entry.variable = true;
3400
+ if (f.weight && !entry.weights.includes(f.weight)) entry.weights.push(f.weight);
3401
+ }
3402
+ for (const entry of byFamily.values()) {
3403
+ entry.weights.sort((a, b) => a - b);
3404
+ entry.files.sort();
3405
+ }
3406
+ return Array.from(byFamily.values()).sort((a, b) => a.family.localeCompare(b.family));
3407
+ }
3408
+ function deriveFamilyFromPostscript(postscript) {
3409
+ if (!postscript) return "";
3410
+ const dashIdx = postscript.indexOf("-");
3411
+ return (dashIdx > 0 ? postscript.slice(0, dashIdx) : postscript).trim();
3412
+ }
3413
+ function inferWeightFromSubfamily(subfamily) {
3414
+ const s = subfamily.toLowerCase().replace(/[\s-]+/g, "");
3415
+ if (s.includes("thin")) return 100;
3416
+ if (s.includes("extralight") || s.includes("ultralight")) return 200;
3417
+ if (s.includes("light")) return 300;
3418
+ if (s.includes("medium")) return 500;
3419
+ if (s.includes("semibold") || s.includes("demibold")) return 600;
3420
+ if (s.includes("extrabold") || s.includes("ultrabold")) return 800;
3421
+ if (s.includes("black") || s.includes("heavy")) return 900;
3422
+ if (s.includes("bold")) return 700;
3423
+ return 400;
3424
+ }
3425
+ var WEIGHT_TOKEN_TO_VALUE = {
3426
+ Thin: 100,
3427
+ Hairline: 100,
3428
+ ExtraLight: 200,
3429
+ UltraLight: 200,
3430
+ Light: 300,
3431
+ Book: 400,
3432
+ Regular: 400,
3433
+ Normal: 400,
3434
+ Medium: 500,
3435
+ SemiBold: 600,
3436
+ DemiBold: 600,
3437
+ Bold: 700,
3438
+ ExtraBold: 800,
3439
+ UltraBold: 800,
3440
+ Black: 900,
3441
+ Heavy: 900,
3442
+ ExtraBlack: 950,
3443
+ UltraBlack: 950
3444
+ };
3445
+ var WEIGHT_TOKEN_RE = new RegExp(`\\s+(${Object.keys(WEIGHT_TOKEN_TO_VALUE).join("|")})$`, "i");
3446
+ function canonicalizeFamily(family) {
3447
+ if (!family) return { canonical: family, inferredWeight: null };
3448
+ let result = family.trim();
3449
+ result = result.replace(/\s+(Italic|Oblique)$/i, "").trim();
3450
+ result = result.replace(
3451
+ /\s+(Semi|Extra|Ultra|Demi)\s+(Bold|Black|Light)$/i,
3452
+ (_, prefix, suffix) => ` ${capitalize(prefix)}${capitalize(suffix)}`
3453
+ );
3454
+ const match = result.match(WEIGHT_TOKEN_RE);
3455
+ if (match && match[1]) {
3456
+ const matchedKey = Object.keys(WEIGHT_TOKEN_TO_VALUE).find(
3457
+ (k) => k.toLowerCase() === match[1].toLowerCase()
3458
+ );
3459
+ const inferredWeight = matchedKey ? WEIGHT_TOKEN_TO_VALUE[matchedKey] : null;
3460
+ result = result.slice(0, result.length - match[0].length).trim();
3461
+ return { canonical: result, inferredWeight };
3462
+ }
3463
+ return { canonical: result, inferredWeight: null };
3464
+ }
3465
+ function capitalize(s) {
3466
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1).toLowerCase();
3467
+ }
3468
+
3469
+ // src/capture/postExtractionPhase.ts
3470
+ function hasVisionCredentials(skipVision) {
3471
+ return Boolean(
3472
+ !skipVision && (process.env.OPENROUTER_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || process.env.HYPERFRAMES_VERTEX_PROJECT_ID && process.env.HYPERFRAMES_VERTEX_SERVICE_ACCOUNT)
3473
+ );
3474
+ }
3475
+ function writeAssetDescriptionsFile(outputDir, lines, header) {
3476
+ writeCaptureFileSync(
3477
+ join9(outputDir, "extracted", "asset-descriptions.md"),
3478
+ header + lines.map((line) => `- ${line}`).join("\n") + "\n",
3479
+ "utf-8"
3480
+ );
3481
+ }
3482
+ async function runPostExtraction(input) {
3483
+ let {
3484
+ state,
3485
+ outputDir,
3486
+ warnings,
3487
+ progress,
3488
+ remainingMs,
3489
+ phase,
3490
+ animationCatalog,
3491
+ catalogedAssets,
3492
+ visibleTextContent,
3493
+ faviconLinks,
3494
+ tokens,
3495
+ extracted,
3496
+ skipAssets,
3497
+ skipVision,
3498
+ downloadByteBudget,
3499
+ assets,
3500
+ dropped,
3501
+ fontDrops,
3502
+ canWrite
3503
+ } = input;
3504
+ const runFontExtraction = async () => {
3505
+ if (!canWrite()) return;
3506
+ phase("fonts", "started");
3507
+ const fontPass = await downloadAndRewriteFonts(extracted.headHtml, outputDir, {
3508
+ remainingMs,
3509
+ byteBudget: downloadByteBudget
3510
+ });
3511
+ fontDrops = fontPass.drops;
3512
+ extracted.headHtml = fontPass.css;
3513
+ phase(
3514
+ "fonts",
3515
+ remainingMs() > 0 ? "completed" : "degraded",
3516
+ remainingMs() > 0 ? void 0 : "budget-exhausted"
3517
+ );
3518
+ try {
3519
+ const fontsManifest = extractFontMetadata(
3520
+ join9(outputDir, "assets", "fonts"),
3521
+ join9(outputDir, "extracted", "fonts-manifest.json")
3522
+ );
3523
+ if (fontsManifest.families.length > 0) {
3524
+ const summary = fontsManifest.families.map((f) => `${f.family}${f.variable ? " (variable)" : ""} \xD7 ${f.fileCount}`).join(", ");
3525
+ diag.notice(`Font metadata extracted: ${summary}`);
3526
+ if (fontsManifest.unidentified.length > 0) {
3527
+ diag.warn(
3528
+ ` ${fontsManifest.unidentified.length} font file(s) could not be identified \u2014 DESIGN.md should flag these explicitly.`
3529
+ );
3530
+ }
3531
+ }
3532
+ } catch (err) {
3533
+ diag.warn("Font metadata extraction failed (non-fatal):", normalizeErrorMessage(err));
3534
+ }
3535
+ };
3536
+ await runFontExtraction();
3537
+ const runAnimationCatalog = async () => {
3538
+ if (animationCatalog) {
3539
+ const uniqueAnimNames = /* @__PURE__ */ new Set();
3540
+ for (const d of animationCatalog.cssDeclarations || []) {
3541
+ if (d.animation?.name) uniqueAnimNames.add(d.animation.name);
3542
+ }
3543
+ const representativeAnims = (animationCatalog.webAnimations || []).filter((a) => a.keyframes && a.keyframes.length > 0).slice(0, 10);
3544
+ const leanCatalog = {
3545
+ summary: animationCatalog.summary,
3546
+ namedAnimations: Array.from(uniqueAnimNames),
3547
+ scrollTriggeredElements: (animationCatalog.scrollTargets || []).length,
3548
+ representativeAnimations: representativeAnims
3549
+ };
3550
+ if (canWrite()) {
3551
+ writeCaptureFileSync(
3552
+ join9(outputDir, "extracted", "animations.json"),
3553
+ JSON.stringify(leanCatalog, null, 2),
3554
+ "utf-8"
3555
+ );
3556
+ }
3557
+ }
3558
+ };
3559
+ await runAnimationCatalog();
3560
+ const runAssetDownload = async () => {
3561
+ let assetDrops = noDrops();
3562
+ const runAssetFetch = async () => {
3563
+ if (!skipAssets && canWrite()) {
3564
+ phase("assets", "started");
3565
+ progress("assets", "Downloading assets...");
3566
+ const assetPass = await downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks, {
3567
+ remainingMs,
3568
+ byteBudget: downloadByteBudget
3569
+ });
3570
+ assets = assetPass.assets;
3571
+ assetDrops = assetPass.drops;
3572
+ state.assets = assets;
3573
+ if (canWrite()) {
3574
+ writeCaptureFileSync(
3575
+ join9(outputDir, "extracted", "icons-manifest.json"),
3576
+ JSON.stringify(assetPass.icons, null, 2),
3577
+ "utf-8"
3578
+ );
3579
+ }
3580
+ phase(
3581
+ "assets",
3582
+ remainingMs() > 0 ? "completed" : "degraded",
3583
+ remainingMs() > 0 ? void 0 : "budget-exhausted"
3584
+ );
3585
+ } else {
3586
+ phase("assets", "degraded", "disabled");
3587
+ }
3588
+ };
3589
+ await runAssetFetch();
3590
+ const recordAssetDrops = () => {
3591
+ dropped = mergeDrops(fontDrops, assetDrops);
3592
+ state.dropped = dropped;
3593
+ const droppedTotal = totalDrops(dropped);
3594
+ if (droppedTotal > 0) {
3595
+ const breakdown = Object.entries(dropped).filter(([, n]) => typeof n === "number" && n > 0).map(([reason, n]) => `${n} ${reason}`).join(", ");
3596
+ warnings.push(
3597
+ `${droppedTotal} referenced asset(s) are not in this capture (${breakdown}). A thin capture with no drops is a thin page; this one was truncated.`
3598
+ );
3599
+ }
3600
+ };
3601
+ recordAssetDrops();
3602
+ const rewriteTokenAssets = () => {
3603
+ const base = (u) => u.split(/[#?]/)[0] ?? u;
3604
+ const localByUrl = /* @__PURE__ */ new Map();
3605
+ const collectLocalAssetPaths = () => {
3606
+ for (const a of assets) {
3607
+ if (!a.url || !a.localPath) continue;
3608
+ localByUrl.set(a.url, a.localPath);
3609
+ localByUrl.set(base(a.url), a.localPath);
3610
+ }
3611
+ };
3612
+ collectLocalAssetPaths();
3613
+ const applyLocalAssetPaths = () => {
3614
+ for (const sec of tokens.sections || []) {
3615
+ const local = [];
3616
+ for (const u of sec.assetUrls || []) {
3617
+ const hit = localByUrl.get(u) || localByUrl.get(base(u));
3618
+ if (hit && !local.includes(hit)) local.push(hit);
3619
+ }
3620
+ if (local.length) sec.assets = local;
3621
+ }
3622
+ };
3623
+ applyLocalAssetPaths();
3624
+ if (assets.length && Array.isArray(tokens.sections)) {
3625
+ if (canWrite()) {
3626
+ writeCaptureFileSync(
3627
+ join9(outputDir, "extracted", "tokens.json"),
3628
+ serializeTokensForCapture(tokens),
3629
+ "utf-8"
3630
+ );
3631
+ }
3632
+ }
3633
+ };
3634
+ await rewriteTokenAssets();
3635
+ };
3636
+ await runAssetDownload();
3637
+ try {
3638
+ const pageHtml = `<!doctype html>
3639
+ <html ${extracted.htmlAttrs || ""}>
3640
+ <head>
3641
+ ${extracted.headHtml}
3642
+ </head>
3643
+ <body>
3644
+ ${extracted.bodyHtml}
3645
+ </body>
3646
+ </html>
3647
+ `;
3648
+ state.pageHtml = pageHtml;
3649
+ if (canWrite())
3650
+ writeCaptureFileSync(join9(outputDir, "extracted", "page.html"), pageHtml, "utf-8");
3651
+ } catch (err) {
3652
+ warnings.push(`page.html write failed: ${err}`);
3653
+ }
3654
+ if (visibleTextContent) {
3655
+ if (canWrite()) {
3656
+ writeCaptureFileSync(
3657
+ join9(outputDir, "extracted", "visible-text.txt"),
3658
+ visibleTextContent,
3659
+ "utf-8"
3660
+ );
3661
+ }
3662
+ }
3663
+ let geminiCaptions = {};
3664
+ const runVision = async () => {
3665
+ if (skipVision) {
3666
+ phase("vision", "degraded", "disabled");
3667
+ } else if (remainingMs() <= 0 || !canWrite()) {
3668
+ warnings.push(
3669
+ "Capture budget exhausted before vision captioning; catalog descriptions were preserved."
3670
+ );
3671
+ phase("vision", "degraded", "budget-exhausted");
3672
+ } else {
3673
+ phase("vision", "started");
3674
+ let visionOutcome = {
3675
+ timedOutRequests: 0,
3676
+ failedRequests: 0,
3677
+ budgetExhausted: false
3678
+ };
3679
+ geminiCaptions = await captionImagesWithGemini(outputDir, progress, warnings, {
3680
+ remainingMs,
3681
+ onOutcome: (outcome) => {
3682
+ visionOutcome = outcome;
3683
+ }
3684
+ });
3685
+ const completion = resolveVisionPhaseCompletion(visionOutcome, remainingMs());
3686
+ phase(
3687
+ "vision",
3688
+ completion.status,
3689
+ completion.status === "degraded" ? completion.reason : void 0
3690
+ );
3691
+ }
3692
+ };
3693
+ await runVision();
3694
+ const runAssetDescriptions = () => {
3695
+ progress("design", "Generating asset descriptions...");
3696
+ try {
3697
+ const lines = generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCaptions);
3698
+ if (lines.length === 0 || !canWrite()) return;
3699
+ const hasVisionKey = hasVisionCredentials(skipVision);
3700
+ const header = hasVisionKey ? "# Asset Descriptions\n\nOne line per file. Read this instead of opening every image individually.\n\nTo find a specific brand or icon, **grep this file for the brand name in the description text** (e.g. `grep -i 'autodesk' asset-descriptions.md`). The Gemini Vision captions identify what's actually in each file \u2014 that's the agent's selector.\n\nThe `logo-<hash>.svg` filename prefix is a cheap structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). It is NOT a content claim \u2014 many `logo-*` files are nav icons or decorative shapes. Trust the captions, not the filename prefix.\n\n" : "# Asset Descriptions\n\n\u26A0\uFE0F No vision credentials \u2014 descriptions below are catalog-derived (alt text, headings, section context, filename) instead of Vision-generated. To get richer Vision descriptions on the next capture, set GEMINI_API_KEY (or GOOGLE_API_KEY), or HYPERFRAMES_VERTEX_PROJECT_ID plus HYPERFRAMES_VERTEX_SERVICE_ACCOUNT for Vertex service-account auth, and re-run.\n\nThe `logo-<hash>.svg` filename prefix is a structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). To pick the actual brand logo without Vision, open the `logo-*` candidates in a previewer or rasterize them with `sharp` before referencing \u2014 composing a fake logo ships off-brand in the final video.\n\n";
3701
+ writeAssetDescriptionsFile(outputDir, lines, header);
3702
+ progress(
3703
+ "design",
3704
+ `${lines.length} asset descriptions written${hasVisionKey ? "" : " (no vision provider \u2014 catalog-fallback mode)"}`
3705
+ );
3706
+ } catch {
3707
+ }
3708
+ };
3709
+ runAssetDescriptions();
3710
+ progress("design", "DESIGN.md will be created by your AI agent");
3711
+ const runContactSheets = async () => {
3712
+ if (remainingMs() > 0 && canWrite()) {
3713
+ phase("contact-sheets", "started");
3714
+ try {
3715
+ const { createScrollContactSheet, createAssetContactSheet, createSvgContactSheet } = await import("./contactSheet-27SRU7A4.js");
3716
+ const contactSheetBudget = { remainingMs };
3717
+ const runScrollContactSheet = async () => {
3718
+ const scrollSheets = await createScrollContactSheet(
3719
+ join9(outputDir, "screenshots"),
3720
+ join9(outputDir, "screenshots", "contact-sheet.jpg"),
3721
+ contactSheetBudget
3722
+ );
3723
+ if (scrollSheets.length > 0)
3724
+ progress(
3725
+ "design",
3726
+ `Screenshot contact sheet generated (${scrollSheets.length} page${scrollSheets.length > 1 ? "s" : ""})`
3727
+ );
3728
+ };
3729
+ await runScrollContactSheet();
3730
+ const runAssetContactSheet = async () => {
3731
+ const assetsImgDir = join9(outputDir, "assets");
3732
+ if (existsSync4(assetsImgDir)) {
3733
+ const assetSheets = await createAssetContactSheet(
3734
+ assetsImgDir,
3735
+ join9(outputDir, "assets", "contact-sheet.jpg"),
3736
+ contactSheetBudget
3737
+ );
3738
+ if (assetSheets.length > 0)
3739
+ progress(
3740
+ "design",
3741
+ `Asset contact sheet generated (${assetSheets.length} page${assetSheets.length > 1 ? "s" : ""})`
3742
+ );
3743
+ }
3744
+ };
3745
+ await runAssetContactSheet();
3746
+ const runSvgContactSheet = async () => {
3747
+ const svgsDir = join9(outputDir, "assets", "svgs");
3748
+ const assetsRootDir = join9(outputDir, "assets");
3749
+ const hasSvgsDir = existsSync4(svgsDir);
3750
+ if (hasSvgsDir) ensureCaptureDirSync(outputDir, svgsDir);
3751
+ const svgOutputPath = hasSvgsDir ? join9(outputDir, "assets", "svgs", "contact-sheet.jpg") : join9(outputDir, "assets", "contact-sheet-svgs.jpg");
3752
+ const svgSheets = await createSvgContactSheet(
3753
+ svgsDir,
3754
+ svgOutputPath,
3755
+ assetsRootDir,
3756
+ contactSheetBudget
3757
+ );
3758
+ if (svgSheets.length > 0)
3759
+ progress(
3760
+ "design",
3761
+ `SVG contact sheet generated (${svgSheets.length} page${svgSheets.length > 1 ? "s" : ""})`
3762
+ );
3763
+ };
3764
+ await runSvgContactSheet();
3765
+ } catch (err) {
3766
+ if (err instanceof CaptureDirRefusedError)
3767
+ warnings.push(`SVG contact sheet skipped: ${err.message}`);
3768
+ }
3769
+ phase(
3770
+ "contact-sheets",
3771
+ remainingMs() > 0 ? "completed" : "degraded",
3772
+ remainingMs() > 0 ? void 0 : "budget-exhausted"
3773
+ );
3774
+ } else {
3775
+ warnings.push(
3776
+ "Capture budget exhausted before contact sheets; source images were preserved."
3777
+ );
3778
+ phase("contact-sheets", "degraded", "budget-exhausted");
3779
+ }
3780
+ };
3781
+ await runContactSheets();
3782
+ return { assets, dropped, fontDrops, extracted, tokens, animationCatalog };
3783
+ }
3784
+
3785
+ // src/capture/captureAttempt.ts
3786
+ var DEFAULT_POST_NAVIGATION_BUDGET_MS = 12e4;
3787
+ async function captureWebsiteAttempt(opts, onProgress, disableWebgl, watchdog, state) {
3788
+ const {
3789
+ url,
3790
+ outputDir,
3791
+ viewportWidth = 1920,
3792
+ viewportHeight = 1080,
3793
+ timeout = 12e4,
3794
+ settleTime = 3e3,
3795
+ maxScreenshots: _maxScreenshots = 24,
3796
+ skipAssets = false,
3797
+ skipVision = false,
3798
+ postNavigationBudgetMs = DEFAULT_POST_NAVIGATION_BUDGET_MS,
3799
+ onPhase
3800
+ } = opts;
3801
+ const downloadByteBudget = createCaptureDownloadBudget();
3802
+ const warnings = [...state.warnings];
3803
+ state.warnings = warnings;
3804
+ const progress = (stage, detail) => {
3805
+ onProgress?.(stage, detail);
3806
+ };
3807
+ const budgetMs = Number.isFinite(postNavigationBudgetMs) && postNavigationBudgetMs > 0 ? postNavigationBudgetMs : DEFAULT_POST_NAVIGATION_BUDGET_MS;
3808
+ let postNavigationDeadline;
3809
+ const remainingMs = () => postNavigationDeadline === void 0 ? budgetMs : Math.max(0, postNavigationDeadline - Date.now());
3810
+ const canWrite = () => !watchdog.expired();
3811
+ let lastPhase = {
3812
+ schema: CAPTURE_PHASE_SCHEMA,
3813
+ phase: "browser",
3814
+ status: "started",
3815
+ remainingMs: null
3816
+ };
3817
+ const phase = (name, status, reason) => {
3818
+ if (watchdog.expired()) return;
3819
+ const remaining = postNavigationDeadline === void 0 ? null : remainingMs();
3820
+ lastPhase = reason ? {
3821
+ schema: CAPTURE_PHASE_SCHEMA,
3822
+ phase: name,
3823
+ status,
3824
+ remainingMs: remaining,
3825
+ reason
3826
+ } : { schema: CAPTURE_PHASE_SCHEMA, phase: name, status, remainingMs: remaining };
3827
+ onPhase?.(lastPhase);
3828
+ };
3829
+ phase("browser", "started");
3830
+ loadEnvFile(outputDir);
3831
+ ensureCaptureDirSync(outputDir, join10(outputDir, "extracted"));
3832
+ ensureCaptureDirSync(outputDir, join10(outputDir, "screenshots"));
3833
+ ensureCaptureDirSync(outputDir, join10(outputDir, "assets"));
3834
+ progress("browser", "Launching headless Chrome...");
3835
+ const { ensureBrowser } = await import("./manager-CT3D67CG.js");
3836
+ const browser = await ensureBrowser();
3837
+ const puppeteer = await import("puppeteer-core");
3838
+ const chromeBrowser = await launchManagedBrowser(puppeteer.default, {
3839
+ headless: true,
3840
+ executablePath: browser.executablePath,
3841
+ protocolTimeout: captureProtocolTimeoutMs(timeout, budgetMs),
3842
+ args: captureBrowserArgs(disableWebgl, viewportWidth, viewportHeight)
3843
+ });
3844
+ watchdog.registerBrowser(chromeBrowser);
3845
+ let animationCatalog;
3846
+ let tokens = state.tokens;
3847
+ let extracted = state.extracted;
3848
+ let screenshots = [];
3849
+ let catalogedAssets = [];
3850
+ let capturedShaders;
3851
+ let visibleTextContent = "";
3852
+ let faviconLinks = [];
3853
+ let detectedLibraries = [];
3854
+ let assets = [];
3855
+ let dropped = noDrops();
3856
+ let fontDrops = noDrops();
3857
+ let httpStatus = null;
3858
+ let pageContentCheck = {
3859
+ textLength: 0,
3860
+ title: "",
3861
+ hasChallengeElement: false,
3862
+ bodyChildCount: Number.POSITIVE_INFINITY
3863
+ };
3864
+ let contentCheckTimedOut = false;
3865
+ try {
3866
+ phase("browser", "completed");
3867
+ phase("navigation", "started");
3868
+ progress("animations", "Cataloging animations (full JS)...");
3869
+ const page1 = await chromeBrowser.newPage();
3870
+ await page1.setViewport({ width: viewportWidth, height: viewportHeight });
3871
+ await page1.setUserAgent(CAPTURE_USER_AGENT);
3872
+ await setupAnimationCapture(page1);
3873
+ const { cdp, animations: cdpAnims } = await startCdpAnimationCapture(page1);
3874
+ await page1.evaluateOnNewDocument(`
3875
+ var origGetContext = HTMLCanvasElement.prototype.getContext;
3876
+ window.__capturedShaders = [];
3877
+ HTMLCanvasElement.prototype.getContext = function(type, attrs) {
3878
+ var ctx = origGetContext.call(this, type, attrs);
3879
+ if (ctx && (type === 'webgl' || type === 'webgl2' || type === 'experimental-webgl')) {
3880
+ window.__hfWebglSeen = true;
3881
+ if (ctx.shaderSource && !ctx.__hfHooked) {
3882
+ var origShaderSource = ctx.shaderSource.bind(ctx);
3883
+ ctx.shaderSource = function(shader, source) {
3884
+ try {
3885
+ var shaderType = ctx.getShaderParameter(shader, ctx.SHADER_TYPE);
3886
+ window.__capturedShaders.push({
3887
+ type: shaderType === ctx.VERTEX_SHADER ? 'vertex' : 'fragment',
3888
+ source: source.slice(0, 5000)
3889
+ });
3890
+ } catch(e) {}
3891
+ return origShaderSource(shader, source);
3892
+ };
3893
+ ctx.__hfHooked = true;
3894
+ }
3895
+ }
3896
+ return ctx;
3897
+ };
3898
+ `);
3899
+ const discoveredLotties = [];
3900
+ const lottieDiscovery = new LottieDiscovery();
3901
+ const discoveredVideoUrls = /* @__PURE__ */ new Set();
3902
+ page1.on("response", (response) => {
3903
+ try {
3904
+ const responseUrl = response.url();
3905
+ if (/\.(mp4|webm|mov|m4v)(\?|#|$)/i.test(responseUrl)) {
3906
+ discoveredVideoUrls.add(responseUrl);
3907
+ }
3908
+ lottieDiscovery.collect(response);
3909
+ } catch {
3910
+ }
3911
+ });
3912
+ const navigationResult = await runNavigationChecks({
3913
+ page1,
3914
+ url,
3915
+ timeout,
3916
+ settleTime,
3917
+ budgetMs,
3918
+ warnings,
3919
+ progress,
3920
+ remainingMs,
3921
+ pageContentCheck,
3922
+ contentCheckTimedOut,
3923
+ postNavigationDeadline,
3924
+ outputDir,
3925
+ phase,
3926
+ httpStatus,
3927
+ canWrite
3928
+ });
3929
+ ({ pageContentCheck, contentCheckTimedOut, httpStatus, postNavigationDeadline } = navigationResult);
3930
+ const coreResult = await runCoreExtraction({
3931
+ page1,
3932
+ chromeBrowser,
3933
+ cdp,
3934
+ cdpAnims,
3935
+ state,
3936
+ outputDir,
3937
+ warnings,
3938
+ progress,
3939
+ remainingMs,
3940
+ pageContentCheck,
3941
+ contentCheckTimedOut,
3942
+ discoveredLotties,
3943
+ lottieDiscovery,
3944
+ discoveredVideoUrls,
3945
+ animationCatalog,
3946
+ capturedShaders,
3947
+ catalogedAssets,
3948
+ detectedLibraries,
3949
+ visibleTextContent,
3950
+ faviconLinks,
3951
+ tokens,
3952
+ extracted,
3953
+ screenshots,
3954
+ downloadByteBudget,
3955
+ canWrite
3956
+ });
3957
+ ({
3958
+ animationCatalog,
3959
+ capturedShaders,
3960
+ catalogedAssets,
3961
+ detectedLibraries,
3962
+ visibleTextContent,
3963
+ faviconLinks,
3964
+ tokens,
3965
+ extracted,
3966
+ screenshots
3967
+ } = coreResult);
3968
+ phase("core-extraction", "completed");
3969
+ const postResult = await runPostExtraction({
3970
+ state,
3971
+ outputDir,
3972
+ warnings,
3973
+ progress,
3974
+ remainingMs,
3975
+ phase,
3976
+ animationCatalog,
3977
+ catalogedAssets,
3978
+ visibleTextContent,
3979
+ faviconLinks,
3980
+ tokens,
3981
+ extracted,
3982
+ skipAssets,
3983
+ skipVision,
3984
+ downloadByteBudget,
3985
+ assets,
3986
+ dropped,
3987
+ fontDrops,
3988
+ canWrite
3989
+ });
3990
+ ({ assets, dropped, fontDrops, extracted, tokens, animationCatalog } = postResult);
3991
+ phase("scaffold", "started");
3992
+ if (!watchdog.expired()) {
3993
+ await generateProjectScaffold(
3994
+ outputDir,
3995
+ url,
3996
+ tokens,
3997
+ animationCatalog,
3998
+ screenshots.length > 0,
3999
+ discoveredLotties.length > 0,
4000
+ existsSync5(join10(outputDir, "extracted", "shaders.json")),
4001
+ catalogedAssets,
4002
+ progress,
4003
+ warnings,
4004
+ detectedLibraries
4005
+ );
4006
+ }
4007
+ phase("scaffold", "completed");
4008
+ progress("done", "Capture complete");
4009
+ phase("complete", "completed");
4010
+ return {
4011
+ ok: true,
4012
+ projectDir: outputDir,
4013
+ url,
4014
+ httpStatus,
4015
+ title: tokens.title,
4016
+ extracted,
4017
+ screenshots,
4018
+ tokens,
4019
+ assets,
4020
+ dropped,
4021
+ animationCatalog,
4022
+ warnings,
4023
+ lastPhase
4024
+ };
4025
+ } finally {
4026
+ watchdog.unregisterBrowser(chromeBrowser);
4027
+ await chromeBrowser.close();
4028
+ }
4029
+ }
4030
+
4031
+ // src/capture/index.ts
4032
+ async function captureWebsite(opts, onProgress) {
4033
+ const watchdog = createCaptureWatchdog(opts.captureDeadlineMs);
4034
+ const state = createPartialCaptureState(opts);
4035
+ const attempt = captureWebsiteAttempt(opts, onProgress, false, watchdog, state);
4036
+ try {
4037
+ const first = await runWithWatchdog(attempt, watchdog.promise);
4038
+ if (first.kind === "deadline") return deadlineResult(opts, state);
4039
+ if (first.kind === "error") throw first.error;
4040
+ return first.result;
4041
+ } catch (err) {
4042
+ if (!(err instanceof NavigationDeadlineError) || watchdog.expired() || !err.webglObserved) {
4043
+ throw err;
4044
+ }
4045
+ onProgress?.("warn", "Navigation timed out; retrying once with WebGL disabled");
4046
+ state.warnings.push("Navigation timed out; retrying once with WebGL disabled");
4047
+ opts.onPhase?.({
4048
+ schema: CAPTURE_PHASE_SCHEMA,
4049
+ phase: "navigation",
4050
+ status: "degraded",
4051
+ remainingMs: null,
4052
+ reason: "webgl-disabled-retry"
4053
+ });
4054
+ const retry = captureWebsiteAttempt(opts, onProgress, true, watchdog, state);
4055
+ const second = await runWithWatchdog(retry, watchdog.promise);
4056
+ if (second.kind === "deadline") return deadlineResult(opts, state);
4057
+ if (second.kind === "error") throw second.error;
4058
+ return {
4059
+ ...second.result,
4060
+ warnings: [.../* @__PURE__ */ new Set([...state.warnings, ...second.result.warnings])]
4061
+ };
4062
+ } finally {
4063
+ watchdog.dispose();
4064
+ }
4065
+ }
4066
+ function deadlineResult(opts, state) {
4067
+ const lastPhase = {
4068
+ schema: CAPTURE_PHASE_SCHEMA,
4069
+ phase: "complete",
4070
+ status: "degraded",
4071
+ remainingMs: null,
4072
+ reason: "deadline"
4073
+ };
4074
+ opts.onPhase?.(lastPhase);
4075
+ return writePartialCaptureBundle(opts, state, lastPhase);
4076
+ }
4077
+ export {
4078
+ captureWebsite
4079
+ };