voxflow 1.15.0 → 1.15.2

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 (453) hide show
  1. package/README.md +34 -0
  2. package/bin/voxflow.js +27 -0
  3. package/dist/index.js +1 -1
  4. package/dist/remotion-bundle/02a2fb2eb80bc7bf.woff2 +0 -0
  5. package/dist/remotion-bundle/052ca5351e5e06ba.woff2 +0 -0
  6. package/dist/remotion-bundle/05853dd28f4019cb.woff2 +0 -0
  7. package/dist/remotion-bundle/072ead3737f7c0d0.woff2 +0 -0
  8. package/dist/remotion-bundle/07d4248613c86a2e.woff2 +0 -0
  9. package/dist/remotion-bundle/0884a5c2d1d2d99b.woff2 +0 -0
  10. package/dist/remotion-bundle/0b0e185b2752095e.woff2 +0 -0
  11. package/dist/remotion-bundle/0e66c11bde067d91.woff2 +0 -0
  12. package/dist/remotion-bundle/0f7794cfba2c5d21.woff2 +0 -0
  13. package/dist/remotion-bundle/0fdbae5a4365783a.woff2 +0 -0
  14. package/dist/remotion-bundle/112.bundle.js +11 -0
  15. package/dist/remotion-bundle/112.bundle.js.map +1 -0
  16. package/dist/remotion-bundle/113.bundle.js +11 -0
  17. package/dist/remotion-bundle/113.bundle.js.map +1 -0
  18. package/dist/remotion-bundle/119cae0c4c16f7ed.woff2 +0 -0
  19. package/dist/remotion-bundle/14725f649fd1e78c.woff2 +0 -0
  20. package/dist/remotion-bundle/14abe9e3f95f7888.woff2 +0 -0
  21. package/dist/remotion-bundle/163.bundle.js +14678 -0
  22. package/dist/remotion-bundle/163.bundle.js.map +1 -0
  23. package/dist/remotion-bundle/1808c54072bf6d14.woff2 +0 -0
  24. package/dist/remotion-bundle/18948bec3e3012fe.woff2 +0 -0
  25. package/dist/remotion-bundle/1a661c60d0fc84fc.woff2 +0 -0
  26. package/dist/remotion-bundle/1af94941e1bc7e1e.woff2 +0 -0
  27. package/dist/remotion-bundle/1bee0219595f606c.woff2 +0 -0
  28. package/dist/remotion-bundle/1bfd5da7ce9d4ec4.woff2 +0 -0
  29. package/dist/remotion-bundle/1c158d56f1884f3f.woff2 +0 -0
  30. package/dist/remotion-bundle/1cf5e88e667610eb.woff2 +0 -0
  31. package/dist/remotion-bundle/1d431bd10f53c481.woff2 +0 -0
  32. package/dist/remotion-bundle/1d701a81a7670db2.woff2 +0 -0
  33. package/dist/remotion-bundle/1da0fecad4240f16.woff2 +0 -0
  34. package/dist/remotion-bundle/1ed14d3d0c5c63fe.woff2 +0 -0
  35. package/dist/remotion-bundle/1edfecf40e586f53.woff2 +0 -0
  36. package/dist/remotion-bundle/1f479711bc34b054.woff +0 -0
  37. package/dist/remotion-bundle/1f86e54a0ff5fcd1.woff2 +0 -0
  38. package/dist/remotion-bundle/2043ea87d9aabd11.woff2 +0 -0
  39. package/dist/remotion-bundle/20563c39ee8a0e40.woff2 +0 -0
  40. package/dist/remotion-bundle/20c231590fd12c44.woff2 +0 -0
  41. package/dist/remotion-bundle/20ce61713f754c07.woff2 +0 -0
  42. package/dist/remotion-bundle/21eb9306fce24bb1.woff2 +0 -0
  43. package/dist/remotion-bundle/244bf71c0cc851af.woff2 +0 -0
  44. package/dist/remotion-bundle/274d4cfc02bffbcb.woff2 +0 -0
  45. package/dist/remotion-bundle/275.bundle.js +21 -0
  46. package/dist/remotion-bundle/275.bundle.js.map +1 -0
  47. package/dist/remotion-bundle/2958f540b39513dc.woff2 +0 -0
  48. package/dist/remotion-bundle/2a168b98fd97722e.woff2 +0 -0
  49. package/dist/remotion-bundle/2d1f6373937ab55f.woff2 +0 -0
  50. package/dist/remotion-bundle/2d213ae47ff6daa9.woff2 +0 -0
  51. package/dist/remotion-bundle/2e4b1f04fcd05047.woff2 +0 -0
  52. package/dist/remotion-bundle/304170d98f4c4563.woff2 +0 -0
  53. package/dist/remotion-bundle/30d02e136e7a5642.woff2 +0 -0
  54. package/dist/remotion-bundle/3135562b52a714cd.woff2 +0 -0
  55. package/dist/remotion-bundle/313713af2c8144e9.woff2 +0 -0
  56. package/dist/remotion-bundle/325fa4108d2285b9.woff2 +0 -0
  57. package/dist/remotion-bundle/338e927ed3345e0c.woff2 +0 -0
  58. package/dist/remotion-bundle/35fc6b190365bc17.woff2 +0 -0
  59. package/dist/remotion-bundle/37a51f1122d4efc5.woff2 +0 -0
  60. package/dist/remotion-bundle/39a4d63e02736f5e.woff2 +0 -0
  61. package/dist/remotion-bundle/3a00e0d62dfc4171.woff2 +0 -0
  62. package/dist/remotion-bundle/3a6955e6561affe1.woff2 +0 -0
  63. package/dist/remotion-bundle/3c573945aef49b89.woff2 +0 -0
  64. package/dist/remotion-bundle/3cdbfbfa23b516a5.woff2 +0 -0
  65. package/dist/remotion-bundle/3e42f85a9e64ca8a.woff2 +0 -0
  66. package/dist/remotion-bundle/3e83eaf1ec859415.woff2 +0 -0
  67. package/dist/remotion-bundle/3f3c8c90de1250ee.woff2 +0 -0
  68. package/dist/remotion-bundle/434.bundle.js +205 -0
  69. package/dist/remotion-bundle/434.bundle.js.map +1 -0
  70. package/dist/remotion-bundle/44ffc6ca4d781692.woff2 +0 -0
  71. package/dist/remotion-bundle/4670d9c4580b09eb.woff2 +0 -0
  72. package/dist/remotion-bundle/479756881b302824.woff2 +0 -0
  73. package/dist/remotion-bundle/481b82134bfa9c82.woff2 +0 -0
  74. package/dist/remotion-bundle/48d27029626f4328.woff2 +0 -0
  75. package/dist/remotion-bundle/49b7b2a30329c511.woff2 +0 -0
  76. package/dist/remotion-bundle/4c8b25a1a9337045.woff2 +0 -0
  77. package/dist/remotion-bundle/4cba14788ca9259b.woff2 +0 -0
  78. package/dist/remotion-bundle/4cd6c589c004a6a7.woff2 +0 -0
  79. package/dist/remotion-bundle/4cd8d79c1021608d.woff2 +0 -0
  80. package/dist/remotion-bundle/4d8fa99b3f00f9f0.woff2 +0 -0
  81. package/dist/remotion-bundle/4e7805a643f86d53.woff2 +0 -0
  82. package/dist/remotion-bundle/4ff91be454542e3f.woff2 +0 -0
  83. package/dist/remotion-bundle/504cbcba1f63591b.woff2 +0 -0
  84. package/dist/remotion-bundle/5202d792e5791d6c.woff2 +0 -0
  85. package/dist/remotion-bundle/534db5ad4770cc1d.woff2 +0 -0
  86. package/dist/remotion-bundle/53b9568eb85f866b.woff2 +0 -0
  87. package/dist/remotion-bundle/543ad386ca171de9.woff2 +0 -0
  88. package/dist/remotion-bundle/54798e55bbf7976e.woff2 +0 -0
  89. package/dist/remotion-bundle/580.bundle.js +11 -0
  90. package/dist/remotion-bundle/580.bundle.js.map +1 -0
  91. package/dist/remotion-bundle/58d174d1193af6d1.woff2 +0 -0
  92. package/dist/remotion-bundle/591d29ff3ff53c80.woff2 +0 -0
  93. package/dist/remotion-bundle/5c28c4f4824383c6.woff2 +0 -0
  94. package/dist/remotion-bundle/5da9740d2ce894c8.woff2 +0 -0
  95. package/dist/remotion-bundle/6197735364642360.woff2 +0 -0
  96. package/dist/remotion-bundle/6265a4335724080f.woff2 +0 -0
  97. package/dist/remotion-bundle/633f5e4f6394daa7.woff2 +0 -0
  98. package/dist/remotion-bundle/637d95ace6a69c49.woff2 +0 -0
  99. package/dist/remotion-bundle/648e04a04dacff8f.woff2 +0 -0
  100. package/dist/remotion-bundle/64a6e83045a008b2.woff2 +0 -0
  101. package/dist/remotion-bundle/651.bundle.js +11 -0
  102. package/dist/remotion-bundle/651.bundle.js.map +1 -0
  103. package/dist/remotion-bundle/65e2a988c070facc.woff2 +0 -0
  104. package/dist/remotion-bundle/66a2f6ce5cc69105.woff2 +0 -0
  105. package/dist/remotion-bundle/690.bundle.js +3479 -0
  106. package/dist/remotion-bundle/690.bundle.js.map +1 -0
  107. package/dist/remotion-bundle/690ff55252ca715d.woff2 +0 -0
  108. package/dist/remotion-bundle/6a01a1cff49314fc.woff2 +0 -0
  109. package/dist/remotion-bundle/6cbc32670982986c.woff2 +0 -0
  110. package/dist/remotion-bundle/6d3cc42ae547f454.woff2 +0 -0
  111. package/dist/remotion-bundle/6d8f4cfa1ddc0830.woff2 +0 -0
  112. package/dist/remotion-bundle/6e4d7c6ae65e2dc3.woff2 +0 -0
  113. package/dist/remotion-bundle/6e86418bbcefb2e8.woff2 +0 -0
  114. package/dist/remotion-bundle/6ee02884b29cf7fb.woff2 +0 -0
  115. package/dist/remotion-bundle/6f436a74c9e3252c.woff2 +0 -0
  116. package/dist/remotion-bundle/78c8022f1657618b.woff2 +0 -0
  117. package/dist/remotion-bundle/7c5444169792bca4.woff2 +0 -0
  118. package/dist/remotion-bundle/7c86bddd9d997212.woff2 +0 -0
  119. package/dist/remotion-bundle/7e1284684767f584.woff2 +0 -0
  120. package/dist/remotion-bundle/7e81c17522d182b2.woff2 +0 -0
  121. package/dist/remotion-bundle/7eb87be198f7858c.woff2 +0 -0
  122. package/dist/remotion-bundle/8060c928f948aab5.woff2 +0 -0
  123. package/dist/remotion-bundle/80bc9dfbea2b35ae.woff2 +0 -0
  124. package/dist/remotion-bundle/811b83f69963bb48.woff2 +0 -0
  125. package/dist/remotion-bundle/813.bundle.js +117511 -0
  126. package/dist/remotion-bundle/813.bundle.js.map +1 -0
  127. package/dist/remotion-bundle/84df492e349f82e9.woff2 +0 -0
  128. package/dist/remotion-bundle/8501bfd73eb36f2b.woff2 +0 -0
  129. package/dist/remotion-bundle/854236a8376093fe.woff2 +0 -0
  130. package/dist/remotion-bundle/8571d74529082753.woff2 +0 -0
  131. package/dist/remotion-bundle/860bf44f8e6f4b5d.woff2 +0 -0
  132. package/dist/remotion-bundle/879.bundle.js +64 -0
  133. package/dist/remotion-bundle/879.bundle.js.map +1 -0
  134. package/dist/remotion-bundle/887dd482f848d56f.woff2 +0 -0
  135. package/dist/remotion-bundle/89b2132e85fbbb5a.woff2 +0 -0
  136. package/dist/remotion-bundle/8ba60d6c306010c2.woff2 +0 -0
  137. package/dist/remotion-bundle/8c7c4dadea897806.woff2 +0 -0
  138. package/dist/remotion-bundle/8c943f9999706f61.woff2 +0 -0
  139. package/dist/remotion-bundle/8f2a718c90575cc9.woff2 +0 -0
  140. package/dist/remotion-bundle/906b6edb3e1772c9.woff2 +0 -0
  141. package/dist/remotion-bundle/930ff9daccdf14eb.woff2 +0 -0
  142. package/dist/remotion-bundle/934db2f1c403c4d0.woff2 +0 -0
  143. package/dist/remotion-bundle/938.bundle.js +451 -0
  144. package/dist/remotion-bundle/938.bundle.js.map +1 -0
  145. package/dist/remotion-bundle/967.bundle.js +4462 -0
  146. package/dist/remotion-bundle/967.bundle.js.map +1 -0
  147. package/dist/remotion-bundle/9684a1093d3c02ce.woff2 +0 -0
  148. package/dist/remotion-bundle/973dcd0faa6116cc.woff2 +0 -0
  149. package/dist/remotion-bundle/9745400694e76cd8.woff2 +0 -0
  150. package/dist/remotion-bundle/999ef957bed3bdca.woff2 +0 -0
  151. package/dist/remotion-bundle/99a3d67c8b0f43e3.woff2 +0 -0
  152. package/dist/remotion-bundle/a0586c3e03127283.woff2 +0 -0
  153. package/dist/remotion-bundle/a0eb654fdae46269.woff2 +0 -0
  154. package/dist/remotion-bundle/a20e35d3b08f7994.woff2 +0 -0
  155. package/dist/remotion-bundle/a2dcaced7c8c25ab.woff2 +0 -0
  156. package/dist/remotion-bundle/a79255a972a2681a.woff2 +0 -0
  157. package/dist/remotion-bundle/a804b352cb9fec1a.woff2 +0 -0
  158. package/dist/remotion-bundle/aae7117164e1eabc.woff2 +0 -0
  159. package/dist/remotion-bundle/affd121385d0442d.woff2 +0 -0
  160. package/dist/remotion-bundle/b19a6083987ee0d7.woff2 +0 -0
  161. package/dist/remotion-bundle/b1b2bd04d8637981.woff2 +0 -0
  162. package/dist/remotion-bundle/b2c07f341486be87.woff2 +0 -0
  163. package/dist/remotion-bundle/b33d8f82e575c4ce.woff2 +0 -0
  164. package/dist/remotion-bundle/b366c0bed35ef491.woff2 +0 -0
  165. package/dist/remotion-bundle/b41e857ec1b85642.woff2 +0 -0
  166. package/dist/remotion-bundle/b420bb34ccf23e7f.woff2 +0 -0
  167. package/dist/remotion-bundle/b4f7bf4efb0c0ccf.woff2 +0 -0
  168. package/dist/remotion-bundle/b60fe5eca03cff93.woff2 +0 -0
  169. package/dist/remotion-bundle/b6bd31a336e64bce.woff2 +0 -0
  170. package/dist/remotion-bundle/b6d2befba3dfefeb.woff2 +0 -0
  171. package/dist/remotion-bundle/b75f39ab06c43bf4.woff2 +0 -0
  172. package/dist/remotion-bundle/b77880e8c413d4fd.woff2 +0 -0
  173. package/dist/remotion-bundle/b7e38ec441e4a77a.woff2 +0 -0
  174. package/dist/remotion-bundle/b83baa383ff0bf2b.woff2 +0 -0
  175. package/dist/remotion-bundle/b9ad7b6c0a11450a.woff2 +0 -0
  176. package/dist/remotion-bundle/baf84486e8ae3aaf.woff2 +0 -0
  177. package/dist/remotion-bundle/bc047b1f6869cffa.woff2 +0 -0
  178. package/dist/remotion-bundle/bf4f3ac6e93f33aa.woff2 +0 -0
  179. package/dist/remotion-bundle/bf6835ffec5897a2.woff2 +0 -0
  180. package/dist/remotion-bundle/bf8885f581eb1724.woff2 +0 -0
  181. package/dist/remotion-bundle/bundle.js +83376 -0
  182. package/dist/remotion-bundle/bundle.js.map +1 -0
  183. package/dist/remotion-bundle/c03f046bccd789d0.woff2 +0 -0
  184. package/dist/remotion-bundle/c0bb1f8962b73bc3.woff2 +0 -0
  185. package/dist/remotion-bundle/c1003f9a7db6e1cf.woff2 +0 -0
  186. package/dist/remotion-bundle/c15d83fb1e199515.woff2 +0 -0
  187. package/dist/remotion-bundle/c28e7e5d310f73ef.woff2 +0 -0
  188. package/dist/remotion-bundle/c2b840274db78aea.woff2 +0 -0
  189. package/dist/remotion-bundle/c3000e3299d4e45f.woff2 +0 -0
  190. package/dist/remotion-bundle/c83ce886e5288510.woff2 +0 -0
  191. package/dist/remotion-bundle/c87a5a64d4ac0918.woff2 +0 -0
  192. package/dist/remotion-bundle/c8a7e0d049e965fa.woff2 +0 -0
  193. package/dist/remotion-bundle/c949a35d3a3b1faf.woff2 +0 -0
  194. package/dist/remotion-bundle/c9618c9b9ac2bc78.woff2 +0 -0
  195. package/dist/remotion-bundle/ca3add3b84152d5b.woff2 +0 -0
  196. package/dist/remotion-bundle/cad9dd036408d707.woff2 +0 -0
  197. package/dist/remotion-bundle/cbb24916619df439.woff2 +0 -0
  198. package/dist/remotion-bundle/cc054f0b5514e177.woff2 +0 -0
  199. package/dist/remotion-bundle/ccc248ed9312bc71.woff2 +0 -0
  200. package/dist/remotion-bundle/cd9d623aa07af925.woff2 +0 -0
  201. package/dist/remotion-bundle/ce2ba7a321bd1247.woff2 +0 -0
  202. package/dist/remotion-bundle/cf72455f79a29b14.woff2 +0 -0
  203. package/dist/remotion-bundle/d267cbfefab452ac.woff2 +0 -0
  204. package/dist/remotion-bundle/d435cff46a64955f.woff +0 -0
  205. package/dist/remotion-bundle/d494d07f67e363f6.woff2 +0 -0
  206. package/dist/remotion-bundle/d7aa0cc1fa47bf38.woff2 +0 -0
  207. package/dist/remotion-bundle/d7c5ca93d885160a.woff2 +0 -0
  208. package/dist/remotion-bundle/d855d3e252db74e2.woff2 +0 -0
  209. package/dist/remotion-bundle/d8f13d47f02f82c2.woff2 +0 -0
  210. package/dist/remotion-bundle/d9567cce2ee11019.woff2 +0 -0
  211. package/dist/remotion-bundle/db8d4456fc75dd86.woff +0 -0
  212. package/dist/remotion-bundle/dc274628378c47ee.woff2 +0 -0
  213. package/dist/remotion-bundle/dc3e06947bb69903.woff2 +0 -0
  214. package/dist/remotion-bundle/dd67040ac3b6d523.woff2 +0 -0
  215. package/dist/remotion-bundle/e0b04bd488f953f4.woff2 +0 -0
  216. package/dist/remotion-bundle/e2a572ff95089370.woff2 +0 -0
  217. package/dist/remotion-bundle/e2e18a86b1c2b0cc.woff2 +0 -0
  218. package/dist/remotion-bundle/e3a78ee2fc9c6931.woff2 +0 -0
  219. package/dist/remotion-bundle/e654c9d547605a9f.woff2 +0 -0
  220. package/dist/remotion-bundle/e67a3a64c129927c.woff2 +0 -0
  221. package/dist/remotion-bundle/e6be28b4203cd6ce.woff2 +0 -0
  222. package/dist/remotion-bundle/e841907ad9b0a191.woff +0 -0
  223. package/dist/remotion-bundle/e889d1541c69fffa.woff2 +0 -0
  224. package/dist/remotion-bundle/e88ef8c76373a9e2.woff2 +0 -0
  225. package/dist/remotion-bundle/e9c72f4bc37defef.woff2 +0 -0
  226. package/dist/remotion-bundle/e9e35f863403a255.woff2 +0 -0
  227. package/dist/remotion-bundle/eb23b37b009375da.woff2 +0 -0
  228. package/dist/remotion-bundle/ee1342b741625721.woff2 +0 -0
  229. package/dist/remotion-bundle/f07da88543a57ec9.woff2 +0 -0
  230. package/dist/remotion-bundle/f522982115306f8a.woff2 +0 -0
  231. package/dist/remotion-bundle/f8449bd864e6d8bc.woff2 +0 -0
  232. package/dist/remotion-bundle/f906dd5bd95ff9ab.woff2 +0 -0
  233. package/dist/remotion-bundle/f9e9e9413e3c38bb.woff2 +0 -0
  234. package/dist/remotion-bundle/fa5a5b16280994a8.woff2 +0 -0
  235. package/dist/remotion-bundle/favicon.ico +0 -0
  236. package/dist/remotion-bundle/fb19c0517725599b.woff2 +0 -0
  237. package/dist/remotion-bundle/fcaf24232f684b9b.woff2 +0 -0
  238. package/dist/remotion-bundle/fe09e084a3eea8cf.woff2 +0 -0
  239. package/dist/remotion-bundle/ff38d5317df7345a.woff2 +0 -0
  240. package/dist/remotion-bundle/ffe7ea1ea08f455a.woff2 +0 -0
  241. package/dist/remotion-bundle/index.html +49 -0
  242. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/0.mp3 +0 -0
  243. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/1.mp3 +0 -0
  244. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/2.mp3 +0 -0
  245. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/3.mp3 +0 -0
  246. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/0.mp3 +0 -0
  247. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/1.mp3 +0 -0
  248. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/2.mp3 +0 -0
  249. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/3.mp3 +0 -0
  250. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/0.mp3 +0 -0
  251. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/1.mp3 +0 -0
  252. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/2.mp3 +0 -0
  253. package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/3.mp3 +0 -0
  254. package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/0.mp3 +0 -0
  255. package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/1.mp3 +0 -0
  256. package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/2.mp3 +0 -0
  257. package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/3.mp3 +0 -0
  258. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/0.mp3 +0 -0
  259. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/1.mp3 +0 -0
  260. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/2.mp3 +0 -0
  261. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/3.mp3 +0 -0
  262. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/0.mp3 +0 -0
  263. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/1.mp3 +0 -0
  264. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/2.mp3 +0 -0
  265. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/3.mp3 +0 -0
  266. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/0.mp3 +0 -0
  267. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/1.mp3 +0 -0
  268. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/2.mp3 +0 -0
  269. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/3.mp3 +0 -0
  270. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/0.mp3 +0 -0
  271. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/1.mp3 +0 -0
  272. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/2.mp3 +0 -0
  273. package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/3.mp3 +0 -0
  274. package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/0.mp3 +0 -0
  275. package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/1.mp3 +0 -0
  276. package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/2.mp3 +0 -0
  277. package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/3.mp3 +0 -0
  278. package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/0.mp3 +0 -0
  279. package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/1.mp3 +0 -0
  280. package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/2.mp3 +0 -0
  281. package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/3.mp3 +0 -0
  282. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/0.mp3 +0 -0
  283. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/1.mp3 +0 -0
  284. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/2.mp3 +0 -0
  285. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/3.mp3 +0 -0
  286. package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/4.mp3 +0 -0
  287. package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/0.mp3 +0 -0
  288. package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/1.mp3 +0 -0
  289. package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/2.mp3 +0 -0
  290. package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/3.mp3 +0 -0
  291. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/0.mp3 +0 -0
  292. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/1.mp3 +0 -0
  293. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/2.mp3 +0 -0
  294. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/3.mp3 +0 -0
  295. package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/4.mp3 +0 -0
  296. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/0.mp3 +0 -0
  297. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/1.mp3 +0 -0
  298. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/2.mp3 +0 -0
  299. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/3.mp3 +0 -0
  300. package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/4.mp3 +0 -0
  301. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/0.mp3 +0 -0
  302. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/1.mp3 +0 -0
  303. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/2.mp3 +0 -0
  304. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/3.mp3 +0 -0
  305. package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/4.mp3 +0 -0
  306. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/0.mp3 +0 -0
  307. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/1.mp3 +0 -0
  308. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/2.mp3 +0 -0
  309. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/3.mp3 +0 -0
  310. package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/4.mp3 +0 -0
  311. package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/0.mp3 +0 -0
  312. package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/1.mp3 +0 -0
  313. package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/2.mp3 +0 -0
  314. package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/3.mp3 +0 -0
  315. package/dist/remotion-bundle/public/paper-slide-experiments/product-update/0.mp3 +0 -0
  316. package/dist/remotion-bundle/public/paper-slide-experiments/product-update/1.mp3 +0 -0
  317. package/dist/remotion-bundle/public/paper-slide-experiments/product-update/2.mp3 +0 -0
  318. package/dist/remotion-bundle/public/paper-slide-experiments/product-update/3.mp3 +0 -0
  319. package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/0.mp3 +0 -0
  320. package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/1.mp3 +0 -0
  321. package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/2.mp3 +0 -0
  322. package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/3.mp3 +0 -0
  323. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/0.mp3 +0 -0
  324. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/1.mp3 +0 -0
  325. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/2.mp3 +0 -0
  326. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/3.mp3 +0 -0
  327. package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/4.mp3 +0 -0
  328. package/dist/remotion-bundle/public/voiceover/ai-life/card-0.mp3 +0 -0
  329. package/dist/remotion-bundle/public/voiceover/ai-life/card-1.mp3 +0 -0
  330. package/dist/remotion-bundle/public/voiceover/ai-life/card-2.mp3 +0 -0
  331. package/dist/remotion-bundle/public/voiceover/ai-life/card-3.mp3 +0 -0
  332. package/dist/remotion-bundle/public/voiceover/ai-life/card-4.mp3 +0 -0
  333. package/dist/remotion-bundle/public/voiceover/ai-life/card-5.mp3 +0 -0
  334. package/dist/remotion-bundle/public/voiceover/coffee-science/card-0.mp3 +0 -0
  335. package/dist/remotion-bundle/public/voiceover/coffee-science/card-1.mp3 +0 -0
  336. package/dist/remotion-bundle/public/voiceover/coffee-science/card-2.mp3 +0 -0
  337. package/dist/remotion-bundle/public/voiceover/coffee-science/card-3.mp3 +0 -0
  338. package/dist/remotion-bundle/public/voiceover/coffee-science/card-4.mp3 +0 -0
  339. package/dist/remotion-bundle/public/voiceover/coffee-science/card-5.mp3 +0 -0
  340. package/dist/remotion-bundle/public/voiceover/coffee-science/card-6.mp3 +0 -0
  341. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-0.mp3 +0 -0
  342. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-1.mp3 +0 -0
  343. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-2.mp3 +0 -0
  344. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-3.mp3 +0 -0
  345. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-4.mp3 +0 -0
  346. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-5.mp3 +0 -0
  347. package/dist/remotion-bundle/public/voiceover/reading-secrets/card-6.mp3 +0 -0
  348. package/dist/remotion-bundle/public/voiceover/remote-work/card-0.mp3 +0 -0
  349. package/dist/remotion-bundle/public/voiceover/remote-work/card-1.mp3 +0 -0
  350. package/dist/remotion-bundle/public/voiceover/remote-work/card-2.mp3 +0 -0
  351. package/dist/remotion-bundle/public/voiceover/remote-work/card-3.mp3 +0 -0
  352. package/dist/remotion-bundle/public/voiceover/remote-work/card-4.mp3 +0 -0
  353. package/dist/remotion-bundle/public/voiceover/remote-work/card-5.mp3 +0 -0
  354. package/dist/remotion-bundle/source-map-helper.wasm +0 -0
  355. package/lib/cli.js +270 -0
  356. package/lib/commands/_registry.js +48 -0
  357. package/lib/commands/add.js +242 -0
  358. package/lib/commands/asr/azure-transcribe.js +336 -0
  359. package/lib/commands/asr/cloud-transcribe.js +384 -0
  360. package/lib/commands/asr/helpers.js +76 -0
  361. package/lib/commands/asr/index.js +236 -0
  362. package/lib/commands/asr/local-transcribe.js +125 -0
  363. package/lib/commands/asr-jobs.js +257 -0
  364. package/lib/commands/asr.js +11 -0
  365. package/lib/commands/auth-cmds.js +358 -0
  366. package/lib/commands/dub.js +542 -0
  367. package/lib/commands/explain.js +512 -0
  368. package/lib/commands/feedback.js +152 -0
  369. package/lib/commands/image.js +207 -0
  370. package/lib/commands/mcp-key.js +166 -0
  371. package/lib/commands/narrate.js +639 -0
  372. package/lib/commands/picstory-templates.js +276 -0
  373. package/lib/commands/picstory.js +547 -0
  374. package/lib/commands/podcast/dialogue.js +109 -0
  375. package/lib/commands/podcast/generate.js +127 -0
  376. package/lib/commands/podcast/index.js +561 -0
  377. package/lib/commands/podcast/synthesize.js +188 -0
  378. package/lib/commands/podcast.js +11 -0
  379. package/lib/commands/present.js +519 -0
  380. package/lib/commands/publish.js +415 -0
  381. package/lib/commands/skills.js +473 -0
  382. package/lib/commands/slice-render.js +282 -0
  383. package/lib/commands/slice-stage.js +264 -0
  384. package/lib/commands/slice.js +346 -0
  385. package/lib/commands/slides/constants.js +108 -0
  386. package/lib/commands/slides/html-renderer.js +338 -0
  387. package/lib/commands/slides/index.js +345 -0
  388. package/lib/commands/slides.js +11 -0
  389. package/lib/commands/story.js +302 -0
  390. package/lib/commands/summarize.js +532 -0
  391. package/lib/commands/synthesize.js +261 -0
  392. package/lib/commands/translate.js +593 -0
  393. package/lib/commands/upgrade.js +249 -0
  394. package/lib/commands/video-translate.js +577 -0
  395. package/lib/commands/voices.js +292 -0
  396. package/lib/core/agent-env.js +104 -0
  397. package/lib/core/args.js +107 -0
  398. package/lib/core/asr-client.js +448 -0
  399. package/lib/core/asr-jobs-client.js +126 -0
  400. package/lib/core/asr-jobs-store.js +105 -0
  401. package/lib/core/asr-r2-upload.js +181 -0
  402. package/lib/core/asr-upload.js +132 -0
  403. package/lib/core/audio-extract.js +150 -0
  404. package/lib/core/audio.js +219 -0
  405. package/lib/core/auth.js +880 -0
  406. package/lib/core/config.js +197 -0
  407. package/lib/core/feedback.js +64 -0
  408. package/lib/core/ffmpeg.js +476 -0
  409. package/lib/core/http.js +188 -0
  410. package/lib/core/image-client.js +55 -0
  411. package/lib/core/intent-params.js +11 -0
  412. package/lib/core/llm-client.js +76 -0
  413. package/lib/core/logger.js +208 -0
  414. package/lib/core/mic-recorder.js +182 -0
  415. package/lib/core/pause-markers.js +94 -0
  416. package/lib/core/podcast-pacing.js +118 -0
  417. package/lib/core/spinner.js +33 -0
  418. package/lib/core/srt.js +394 -0
  419. package/lib/core/telemetry.js +100 -0
  420. package/lib/core/timeline.js +92 -0
  421. package/lib/core/tts-synthesizer.js +70 -0
  422. package/lib/core/update-check.js +185 -0
  423. package/lib/core/url-download.js +148 -0
  424. package/lib/core/whisper-local.js +279 -0
  425. package/lib/internal/deck-validator.js +488 -0
  426. package/lib/internal/slice-themes.json +370 -0
  427. package/lib/stage-core/cloud-render.js +170 -0
  428. package/lib/stage-core/deck-format.js +133 -0
  429. package/lib/stage-core/edit-prompt.js +104 -0
  430. package/lib/stage-core/event-bus.js +31 -0
  431. package/lib/stage-core/port.js +46 -0
  432. package/lib/stage-core/server.js +352 -0
  433. package/lib/stage-core/snapshot-store.js +198 -0
  434. package/lib/stage-core/watcher.js +106 -0
  435. package/lib/stage-ui/slice/template.js +1672 -0
  436. package/package.json +9 -4
  437. package/skills/.claude-plugin/marketplace.json +22 -0
  438. package/skills/.claude-plugin/plugin.json +25 -0
  439. package/skills/LICENSE +21 -0
  440. package/skills/README.md +120 -0
  441. package/skills/hub/SKILL.md +317 -0
  442. package/skills/podcast/SKILL.md +146 -0
  443. package/skills/slice/SKILL.md +205 -0
  444. package/skills/slice/agents/openai.yaml +4 -0
  445. package/skills/slice/references/deck-schema.md +183 -0
  446. package/skills/slice/references/example-decks.md +108 -0
  447. package/skills/slice/references/themes.md +172 -0
  448. package/skills/transcribe/SKILL.md +473 -0
  449. package/skills/video/SKILL.md +261 -0
  450. package/skills/voxflow-slice/SKILL.md +271 -0
  451. package/skills/voxflow-slice/examples/article.md +13 -0
  452. package/skills/voxflow-slice/examples/expected-deck.json +39 -0
  453. package/skills/voxflow-slice/examples/validate.mjs +46 -0
@@ -0,0 +1,370 @@
1
+ {
2
+ "$comment": "Single source of truth for Slice visual themes. Consumed by backend (deck-validator / composition-core schemas / render-worker pipeline / slicing prompt), CLI (slice + slice-stage commands), and apps/console (SLICE_THEMES UI metadata). Adding a theme: append an entry here. The Remotion <Composition>/<Still> in video-present/src/Root.tsx + matching CSS blocks in apps/console/app/apps/slice/three-col.css still need authoring, but ID + metadata + dispatch live here. The completeness test (apps/console/lib/slice/__tests__/theme-registry-coverage.test.ts) fails build if you forget either. Optional `variants[]` describes palette swaps within a theme; each variant has its own id/label/accent for UI chips. Default variant = variants[0] or theme-level palette when absent.",
3
+ "default": "paper-slide",
4
+ "themes": [
5
+ {
6
+ "id": "paper-slide",
7
+ "label": "纸面",
8
+ "blurb": "泛黄纸纹 · 衬线红字 · 手绘印章",
9
+ "accent": "#7a1f15",
10
+ "platforms": ["抖音", "视频号", "小红书"],
11
+ "remotion": { "deck": "PaperSlideDeck", "cover": "PaperSlide-Cover" },
12
+ "variants": [
13
+ { "id": "classic", "label": "经典朱红", "accent": "#7a1f15" },
14
+ { "id": "forest", "label": "深林墨绿", "accent": "#1e4d33" },
15
+ { "id": "ocean", "label": "深海靛蓝", "accent": "#1a3a5c" }
16
+ ]
17
+ },
18
+ {
19
+ "id": "editorial-mag",
20
+ "label": "编辑刊",
21
+ "blurb": "米白页面 · 衬线斜体 · 杂志感留白",
22
+ "accent": "#6b1a1a",
23
+ "platforms": ["知乎", "公众号", "Linkedin"],
24
+ "remotion": { "deck": "EditorialMagDeck", "cover": "EditorialMag-Cover" },
25
+ "variants": [
26
+ { "id": "cream", "label": "经典米白", "accent": "#6b1a1a" },
27
+ { "id": "sepia", "label": "棕褐古典", "accent": "#9a4b1a" },
28
+ { "id": "cold-grey", "label": "冷调灰蓝", "accent": "#1b4f5c" }
29
+ ]
30
+ },
31
+ {
32
+ "id": "bold-poster",
33
+ "label": "大字海报",
34
+ "blurb": "左侧 accent · 黑体粗字 · 数字大块",
35
+ "accent": "#FF3B30",
36
+ "platforms": ["X", "Threads", "Linkedin"],
37
+ "remotion": { "deck": "BoldPosterDeck", "cover": "BoldPoster-Cover" },
38
+ "variants": [
39
+ { "id": "vermilion", "label": "经典朱红", "accent": "#FF3B30" },
40
+ { "id": "electric", "label": "电光蓝", "accent": "#0066ff" },
41
+ { "id": "forest", "label": "深林绿", "accent": "#1f8a3a" }
42
+ ]
43
+ },
44
+ {
45
+ "id": "notion-card",
46
+ "label": "Notion 卡",
47
+ "blurb": "纯白底 · 暖灰墨 · 蓝色 accent · 页面图标",
48
+ "accent": "#0066ff",
49
+ "platforms": ["公众号", "飞书", "知识星球"],
50
+ "remotion": { "deck": "NotionCardDeck", "cover": "NotionCard-Cover" },
51
+ "variants": [
52
+ { "id": "signal", "label": "信号蓝", "accent": "#0066ff" },
53
+ { "id": "peach", "label": "暖橘桃", "accent": "#ec8854" },
54
+ { "id": "sage", "label": "宁静鼠尾", "accent": "#5e9b7a" }
55
+ ]
56
+ },
57
+ {
58
+ "id": "brutalist",
59
+ "label": "粗野",
60
+ "blurb": "黑白纯色 · 粗边框 · NO.NN 标签 · raw 排版",
61
+ "accent": "#000000",
62
+ "platforms": ["X", "Mastodon", "播客"],
63
+ "remotion": { "deck": "BrutalistDeck", "cover": "Brutalist-Cover" }
64
+ },
65
+ {
66
+ "id": "glass-dark",
67
+ "label": "玻璃夜",
68
+ "blurb": "深色渐变 · 紫色发光 · 玻璃拟态 · 短视频感",
69
+ "accent": "#a855f7",
70
+ "platforms": ["抖音", "视频号", "TikTok"],
71
+ "remotion": { "deck": "GlassDarkDeck", "cover": "GlassDark-Cover" },
72
+ "variants": [
73
+ { "id": "violet", "label": "电光紫", "accent": "#a855f7" },
74
+ { "id": "ember", "label": "暖琥珀", "accent": "#ff9844" },
75
+ { "id": "aqua", "label": "霓虹青", "accent": "#5fd6e0" }
76
+ ]
77
+ },
78
+ {
79
+ "id": "broadsheet",
80
+ "label": "财经刊",
81
+ "blurb": "FT 三文鱼底 · 重磅衬线 · 首字下沉 · DATELINE 红标",
82
+ "accent": "#0c2e5b",
83
+ "platforms": ["LinkedIn", "知乎", "雪球"],
84
+ "remotion": { "deck": "BroadsheetDeck", "cover": "Broadsheet-Cover" },
85
+ "variants": [
86
+ { "id": "salmon", "label": "FT 三文鱼", "accent": "#0c2e5b" },
87
+ { "id": "cream", "label": "周末刊奶白", "accent": "#1e3a5c" },
88
+ { "id": "editorial-grey", "label": "现代冷灰", "accent": "#0a1428" }
89
+ ]
90
+ },
91
+ {
92
+ "id": "blueprint",
93
+ "label": "蓝晒图",
94
+ "blurb": "青底 + 白色网格 · 橙色尺寸标 · 工程图纸感",
95
+ "accent": "#ffb74a",
96
+ "platforms": ["少数派", "知乎", "GitHub"],
97
+ "remotion": { "deck": "BlueprintDeck", "cover": "Blueprint-Cover" },
98
+ "variants": [
99
+ { "id": "cyanotype", "label": "经典蓝晒", "accent": "#ffb74a" },
100
+ { "id": "redline", "label": "评审红线", "accent": "#e74c3c" },
101
+ { "id": "nightshift", "label": "夜班加深", "accent": "#ffd97a" }
102
+ ]
103
+ },
104
+ {
105
+ "id": "daisy-pastel",
106
+ "label": "雏菊",
107
+ "blurb": "奶油底 + 手绘雏菊 + 星星 · 粉嫩可爱",
108
+ "accent": "#e58a9c",
109
+ "platforms": ["小红书", "微博", "即刻"],
110
+ "remotion": { "deck": "DaisyPastelDeck", "cover": "DaisyPastel-Cover" },
111
+ "variants": [
112
+ { "id": "petal", "label": "粉嫩花瓣", "accent": "#e58a9c" },
113
+ { "id": "meadow", "label": "草地鼠尾草", "accent": "#7ea876" },
114
+ { "id": "sunset", "label": "夕阳珊瑚", "accent": "#e8957a" }
115
+ ]
116
+ },
117
+ {
118
+ "id": "showa-catalog",
119
+ "label": "昭和目录",
120
+ "blurb": "70s city-pop · 彩色圆堆 + 彩虹斜条 + 太阳印章",
121
+ "accent": "#d6437b",
122
+ "platforms": ["小红书", "B 站", "播客"],
123
+ "remotion": { "deck": "ShowaCatalogDeck", "cover": "ShowaCatalog-Cover" },
124
+ "variants": [
125
+ { "id": "pop", "label": "城市流行", "accent": "#d6437b" },
126
+ { "id": "pastel", "label": "indie zine 软调", "accent": "#e0a4b8" },
127
+ { "id": "monochrome", "label": "复古单色", "accent": "#a04030" }
128
+ ]
129
+ },
130
+ {
131
+ "id": "photo-feature",
132
+ "label": "摄影刊",
133
+ "blurb": "全屏摄影 + 渐变压底 + 重磅衬线 · 旅行游记感",
134
+ "accent": "#e8b455",
135
+ "platforms": ["小红书", "知乎", "微博"],
136
+ "remotion": { "deck": "PhotoFeatureDeck", "cover": "PhotoFeature-Cover" },
137
+ "variants": [
138
+ { "id": "warm-amber", "label": "暖琥珀夕照", "accent": "#e8b455" },
139
+ { "id": "cool-blue", "label": "冷调暮色", "accent": "#7ecbe8" },
140
+ { "id": "monochrome", "label": "黑白纪实", "accent": "#cccccc" }
141
+ ]
142
+ },
143
+ {
144
+ "id": "atmospheric",
145
+ "label": "深夜刊",
146
+ "blurb": "黑底 + 一束暖光 + 衬线粉红 italic · 深夜散文",
147
+ "accent": "#dd5878",
148
+ "platforms": ["微博", "即刻", "播客"],
149
+ "remotion": { "deck": "AtmosphericDeck", "cover": "Atmospheric-Cover" }
150
+ },
151
+ {
152
+ "id": "art-mag",
153
+ "label": "艺术杂志",
154
+ "blurb": "颗粒感色块 + 大字衬线 · 几何球 · 艺术画廊感",
155
+ "accent": "#dd5e72",
156
+ "platforms": ["小红书", "播客", "即刻"],
157
+ "remotion": { "deck": "ArtMagDeck", "cover": "ArtMag-Cover" },
158
+ "variants": [
159
+ { "id": "vivid", "label": "高彩编辑", "accent": "#dd5e72" },
160
+ { "id": "muted", "label": "莫兰迪低饱和", "accent": "#c08488" },
161
+ { "id": "bauhaus", "label": "包豪斯三原色", "accent": "#c8242c" }
162
+ ]
163
+ },
164
+ {
165
+ "id": "tome-noir",
166
+ "label": "夜灯",
167
+ "blurb": "纯黑 + humanist sans · 60/40 非对称 · 暖光晕",
168
+ "accent": "#ffb38a",
169
+ "platforms": ["X", "LinkedIn", "即刻"],
170
+ "remotion": { "deck": "TomeNoirDeck", "cover": "TomeNoir-Cover" },
171
+ "variants": [
172
+ { "id": "noir", "label": "暖琥珀", "accent": "#ffb38a" },
173
+ { "id": "bronze", "label": "古铜", "accent": "#c0744a" },
174
+ { "id": "platinum", "label": "冷银", "accent": "#a8b0bd" }
175
+ ]
176
+ },
177
+ {
178
+ "id": "flomo-mute",
179
+ "label": "纯白",
180
+ "blurb": "#fafafa Muji 纸面 · IBM Plex + 苹方 · 绿色细线 · 零装饰",
181
+ "accent": "#4ea870",
182
+ "platforms": ["小红书", "即刻", "微博"],
183
+ "remotion": { "deck": "FlomoMuteDeck", "cover": "FlomoMute-Cover" },
184
+ "variants": [
185
+ { "id": "muji", "label": "原木绿", "accent": "#4ea870" },
186
+ { "id": "graphite", "label": "石墨灰", "accent": "#6b7785" },
187
+ { "id": "linen", "label": "亚麻米", "accent": "#c08a3a" }
188
+ ]
189
+ },
190
+ {
191
+ "id": "substack-drop",
192
+ "label": "专栏",
193
+ "blurb": "米色衬线 · 首字下沉 · 章节分隔 · 阅读时间 chip",
194
+ "accent": "#ff6719",
195
+ "platforms": ["公众号", "知乎", "少数派"],
196
+ "remotion": { "deck": "SubstackDropDeck", "cover": "SubstackDrop-Cover" },
197
+ "variants": [
198
+ { "id": "signal", "label": "信号橙", "accent": "#ff6719" },
199
+ { "id": "eclipse", "label": "深红夜", "accent": "#8a1f2c" },
200
+ { "id": "tidewater", "label": "潮汐青", "accent": "#0a5d5f" }
201
+ ]
202
+ },
203
+ {
204
+ "id": "ink-scroll",
205
+ "label": "卷轴",
206
+ "blurb": "米黄宣纸 · 楷体竖排 · 印章红 · 水墨边缘",
207
+ "accent": "#c0392b",
208
+ "platforms": ["小红书", "微博", "B 站国创区"],
209
+ "remotion": { "deck": "InkScrollDeck", "cover": "InkScroll-Cover" },
210
+ "variants": [
211
+ { "id": "seal-red", "label": "朱印红", "accent": "#c0392b" },
212
+ { "id": "seal-blue", "label": "青衣靛", "accent": "#1f3a6b" },
213
+ { "id": "seal-jade", "label": "翡翠绿", "accent": "#2a6b4a" }
214
+ ]
215
+ },
216
+ {
217
+ "id": "podcast-clip",
218
+ "label": "播客剪辑",
219
+ "blurb": "封面渐变 + 圆形封面 + 大字幕 · 波形 / 时间码底栏",
220
+ "accent": "#ffba78",
221
+ "platforms": ["小宇宙", "即刻", "抖音"],
222
+ "remotion": { "deck": "PodcastClipDeck", "cover": "PodcastClip-Cover" }
223
+ },
224
+ {
225
+ "id": "ink-wash",
226
+ "label": "新中式",
227
+ "blurb": "宣纸 + 思源宋体 · 横排单字 · 笔触 · 朱印 · 留白",
228
+ "accent": "#b3261d",
229
+ "platforms": ["知乎长文", "小红书", "公众号"],
230
+ "remotion": { "deck": "InkWashDeck", "cover": "InkWash-Cover" },
231
+ "variants": [
232
+ { "id": "vermilion", "label": "朱印红", "accent": "#b3261d" },
233
+ { "id": "azure", "label": "青花蓝", "accent": "#1d4a7a" },
234
+ { "id": "bronze", "label": "古铜褐", "accent": "#8a5a26" }
235
+ ]
236
+ },
237
+ {
238
+ "id": "morandi-calm",
239
+ "label": "莫兰迪",
240
+ "blurb": "低饱和粉褐 + 苹方 Light · 色块分区 · 零阴影零渐变",
241
+ "accent": "#9c5a4a",
242
+ "platforms": ["小红书", "公众号", "即刻"],
243
+ "remotion": { "deck": "MorandiCalmDeck", "cover": "MorandiCalm-Cover" }
244
+ },
245
+ {
246
+ "id": "douyin-data",
247
+ "label": "数据卡",
248
+ "blurb": "纯黑底 + 抖音品红 + 湖蓝 · RGB 色差 · 大数字 60%",
249
+ "accent": "#FE2C55",
250
+ "platforms": ["抖音", "视频号", "小红书"],
251
+ "remotion": { "deck": "DouyinDataDeck", "cover": "DouyinData-Cover" }
252
+ },
253
+ {
254
+ "id": "highlighter-note",
255
+ "label": "学习笔记",
256
+ "blurb": "奶油格纸 + 印刷体黑字 + Caveat 手写 · 黄色荧光笔 + 红色批注",
257
+ "accent": "#FFE63B",
258
+ "platforms": ["小红书", "B 站", "知乎"],
259
+ "remotion": { "deck": "HighlighterNoteDeck", "cover": "HighlighterNote-Cover" },
260
+ "variants": [
261
+ { "id": "yellow", "label": "经典荧光黄", "accent": "#FFE63B" },
262
+ { "id": "pink", "label": "粉色荧光", "accent": "#ff6ba0" },
263
+ { "id": "mint", "label": "薄荷蓝笔", "accent": "#5fc88a" }
264
+ ]
265
+ },
266
+ {
267
+ "id": "memphis-design",
268
+ "label": "Memphis 设计",
269
+ "blurb": "奶油 + 原色块 · 波浪线 + 锯齿 + 实心圆 · 1986 后现代",
270
+ "accent": "#ff4d6d",
271
+ "platforms": ["即刻", "小红书", "播客"],
272
+ "remotion": { "deck": "MemphisDesignDeck", "cover": "MemphisDesign-Cover" }
273
+ },
274
+ {
275
+ "id": "bauhaus-grid",
276
+ "label": "Bauhaus 包豪斯",
277
+ "blurb": "奶油纸 + 红黄蓝原色 + 圆方三角 · 1923 包豪斯几何构图",
278
+ "accent": "#d62828",
279
+ "platforms": ["LinkedIn", "设计博客", "工业设计", "知乎"],
280
+ "remotion": { "deck": "BauhausGridDeck", "cover": "BauhausGrid-Cover" }
281
+ },
282
+ {
283
+ "id": "riso-print",
284
+ "label": "双色印刷",
285
+ "blurb": "荧光粉 + 钴蓝错版 · 网点 + 噪点 · Risograph 独立印刷",
286
+ "accent": "#ff48a7",
287
+ "platforms": ["独立开发", "设计 newsletter", "音乐厂牌"],
288
+ "remotion": { "deck": "RisoPrintDeck", "cover": "RisoPrint-Cover" }
289
+ },
290
+ {
291
+ "id": "chrome-y2k",
292
+ "label": "千禧液态金属",
293
+ "blurb": "镀铬全息 + 像素栅 + 双径向辉光 · Y2K Bratz/iPod 质感",
294
+ "accent": "#8e6dff",
295
+ "platforms": ["小红书 girlie", "抖音 Z 世代", "TikTok"],
296
+ "remotion": { "deck": "ChromeY2kDeck", "cover": "ChromeY2k-Cover" }
297
+ },
298
+ {
299
+ "id": "botanical-press",
300
+ "label": "植物图谱",
301
+ "blurb": "羊皮纸 + Cormorant 斜体 + 拉丁学名 · 18 世纪植物标本页",
302
+ "accent": "#5a6d2c",
303
+ "platforms": ["茶道", "园艺", "慢生活"],
304
+ "remotion": { "deck": "BotanicalPressDeck", "cover": "BotanicalPress-Cover" },
305
+ "variants": [
306
+ { "id": "sage", "label": "鼠尾草绿", "accent": "#5a6d2c" },
307
+ { "id": "copper", "label": "铜版褐", "accent": "#a06340" },
308
+ { "id": "indigo", "label": "古典靛蓝", "accent": "#2c5078" }
309
+ ]
310
+ },
311
+ {
312
+ "id": "art-nouveau",
313
+ "label": "新艺术",
314
+ "blurb": "Mucha 曲线 + 烫金 + 装饰边框 · 1900 巴黎沙龙海报",
315
+ "accent": "#b07d2d",
316
+ "platforms": ["艺术评论", "美术志", "插画家"],
317
+ "remotion": { "deck": "ArtNouveauDeck", "cover": "ArtNouveau-Cover" }
318
+ },
319
+ {
320
+ "id": "tabloid-print",
321
+ "label": "小报头条",
322
+ "blurb": "红色 BREAKING + Anton 巨字 + 半调 + 双栏 · 八卦小报封面",
323
+ "accent": "#d6261a",
324
+ "platforms": ["行业八卦", "争议事件", "独立专栏"],
325
+ "remotion": { "deck": "TabloidPrintDeck", "cover": "TabloidPrint-Cover" }
326
+ },
327
+ {
328
+ "id": "arcade-pixel",
329
+ "label": "街机像素",
330
+ "blurb": "CRT 扫描线 + 8-bit 像素 + RGB 色差 · 80s 街机标题屏",
331
+ "accent": "#c5f54c",
332
+ "platforms": ["独立游戏", "像素艺术", "90s 复古"],
333
+ "remotion": { "deck": "ArcadePixelDeck", "cover": "ArcadePixel-Cover" }
334
+ },
335
+ {
336
+ "id": "hand-lettered",
337
+ "label": "黑板手写",
338
+ "blurb": "黑板绿底 + 粉笔白字 + Caveat 手写 · 咖啡馆菜单板",
339
+ "accent": "#f7d34a",
340
+ "platforms": ["咖啡馆菜单", "教师 notebook", "生活感"],
341
+ "remotion": { "deck": "HandLetteredDeck", "cover": "HandLettered-Cover" },
342
+ "variants": [
343
+ { "id": "chalkboard", "label": "黑板绿", "accent": "#f7d34a" },
344
+ { "id": "slate", "label": "石板灰", "accent": "#f0c84a" },
345
+ { "id": "kraft", "label": "牛皮纸棕", "accent": "#f5c060" }
346
+ ]
347
+ },
348
+ {
349
+ "id": "stamp-collector",
350
+ "label": "邮票集",
351
+ "blurb": "齿孔 + 邮戳 + 面额 · 复古集邮册标本页",
352
+ "accent": "#1f4e6b",
353
+ "platforms": ["旅行游记", "收藏品", "怀旧专栏"],
354
+ "remotion": { "deck": "StampCollectorDeck", "cover": "StampCollector-Cover" },
355
+ "variants": [
356
+ { "id": "navy", "label": "邮政蓝", "accent": "#1f4e6b" },
357
+ { "id": "sepia", "label": "棕褐版", "accent": "#7a4a26" },
358
+ { "id": "forest", "label": "森林绿", "accent": "#2a5a40" }
359
+ ]
360
+ },
361
+ {
362
+ "id": "tropical-postcard",
363
+ "label": "热带明信片",
364
+ "blurb": "落日渐变 + 椰影 + 浪潮线 · 60s 旅游海报明信片",
365
+ "accent": "#f04e9a",
366
+ "platforms": ["旅行游记", "夏日散文", "小红书 vlog"],
367
+ "remotion": { "deck": "TropicalPostcardDeck", "cover": "TropicalPostcard-Cover" }
368
+ }
369
+ ]
370
+ }
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Stage → backend bridge for the cloud Slice renderer.
5
+ *
6
+ * Background: stage runs on localhost with no auth. The backend cloud-render
7
+ * endpoint (`POST /api/paper-slide/render`) requires a JWT and costs 500
8
+ * quota per call. To let users go from "deck I'm happy with" → "mp4 in my
9
+ * hands" without leaving stage, we proxy three calls through the local stage
10
+ * server:
11
+ *
12
+ * submit(deck) → POST /api/paper-slide/render { jobId }
13
+ * status(jobId) → GET /api/render/:jobId { status, video_url? }
14
+ * quota() → GET /api/user/quota { remaining, monthly }
15
+ *
16
+ * Auth: we read the same `~/.config/voxflow/token.json` cache the CLI uses.
17
+ * No interactive refresh — if the token is missing or expired, callers get a
18
+ * machine-readable `not_logged_in` code and the UI tells the user to run
19
+ * `voxflow login`. Auto-refresh would mean potentially launching a browser,
20
+ * which is the wrong move when the user already has stage open.
21
+ *
22
+ * Design choice — proxy everything: never expose the JWT to the page. The
23
+ * UI talks to localhost:5180/api/render-mp4 etc.; this module is the only
24
+ * thing that ever sees the access token. Same posture as the rest of the
25
+ * stage server (no third-party tabs can read it via the Origin allowlist).
26
+ */
27
+
28
+ const http = require('http');
29
+ const https = require('https');
30
+
31
+ const { API_BASE } = require('../core/config');
32
+ const { readCachedToken } = require('../core/auth');
33
+
34
+ const DEFAULT_TIMEOUT_MS = 30_000;
35
+
36
+ function requestJson(targetUrl, opts = {}, body = null, timeoutMs = DEFAULT_TIMEOUT_MS) {
37
+ return new Promise((resolve, reject) => {
38
+ const u = new URL(targetUrl);
39
+ const mod = u.protocol === 'https:' ? https : http;
40
+ const headers = Object.assign({}, opts.headers || {});
41
+ let payload = null;
42
+ if (body !== null && body !== undefined) {
43
+ payload = Buffer.from(JSON.stringify(body), 'utf8');
44
+ headers['Content-Type'] = 'application/json';
45
+ headers['Content-Length'] = payload.length;
46
+ }
47
+ const req = mod.request({
48
+ hostname: u.hostname,
49
+ port: u.port || (u.protocol === 'https:' ? 443 : 80),
50
+ path: u.pathname + u.search,
51
+ method: opts.method || 'GET',
52
+ headers,
53
+ }, (res) => {
54
+ const chunks = [];
55
+ res.on('data', (c) => chunks.push(c));
56
+ res.on('end', () => {
57
+ const raw = Buffer.concat(chunks).toString('utf8');
58
+ let parsed = null;
59
+ try { parsed = JSON.parse(raw); } catch { /* keep as null on non-JSON */ }
60
+ resolve({ status: res.statusCode, data: parsed, raw });
61
+ });
62
+ });
63
+ req.on('error', reject);
64
+ req.setTimeout(timeoutMs, () => { req.destroy(new Error(`upstream timeout after ${timeoutMs}ms`)); });
65
+ if (payload) req.write(payload);
66
+ req.end();
67
+ });
68
+ }
69
+
70
+ /**
71
+ * @param {object} [opts]
72
+ * @param {string} [opts.apiBase=API_BASE]
73
+ * @param {() => ({access_token:string}|null)} [opts.tokenLoader] For tests.
74
+ * @returns {{
75
+ * submit: (deck:object) => Promise<{code:string, jobId?:string, message?:string, ...}>,
76
+ * status: (jobId:string) => Promise<{code:string, status?:string, videoUrl?:string|null, ...}>,
77
+ * quota: () => Promise<{code:string, remaining?:number, monthly?:number, ...}>,
78
+ * }}
79
+ */
80
+ function createCloudRenderClient(opts = {}) {
81
+ const apiBase = (opts.apiBase || API_BASE).replace(/\/$/, '');
82
+ const tokenLoader = typeof opts.tokenLoader === 'function' ? opts.tokenLoader : readCachedToken;
83
+
84
+ function authHeaders() {
85
+ const cached = tokenLoader();
86
+ if (!cached || !cached.access_token) return null;
87
+ return { Authorization: 'Bearer ' + cached.access_token };
88
+ }
89
+
90
+ async function submit(deck) {
91
+ if (!deck || !Array.isArray(deck.cards)) {
92
+ return { code: 'invalid_deck', message: 'deck.cards must be an array' };
93
+ }
94
+ const headers = authHeaders();
95
+ if (!headers) return { code: 'not_logged_in', message: 'Run `voxflow login` first.' };
96
+ let r;
97
+ try {
98
+ r = await requestJson(`${apiBase}/api/paper-slide/render`, { method: 'POST', headers }, { deck });
99
+ } catch (err) {
100
+ return { code: 'network_error', message: err.message || String(err) };
101
+ }
102
+ if (r.status === 401) return { code: 'not_logged_in', message: 'Token expired — run `voxflow login`.' };
103
+ if (r.status === 402 || r.status === 429) {
104
+ return { code: 'quota_exceeded', message: r.data?.message || 'Insufficient quota' };
105
+ }
106
+ if (r.status >= 400 || !r.data) {
107
+ return { code: r.data?.code || 'render_submit_failed', message: r.data?.message || `HTTP ${r.status}` };
108
+ }
109
+ const jobId = r.data.data?.jobId || r.data.jobId;
110
+ if (!jobId) return { code: 'render_submit_failed', message: 'no jobId in response' };
111
+ return { code: 'success', jobId };
112
+ }
113
+
114
+ async function status(jobId) {
115
+ if (typeof jobId !== 'string' || jobId.length === 0) {
116
+ return { code: 'invalid_id', message: 'jobId required' };
117
+ }
118
+ const headers = authHeaders();
119
+ if (!headers) return { code: 'not_logged_in', message: 'Run `voxflow login` first.' };
120
+ let r;
121
+ try {
122
+ r = await requestJson(`${apiBase}/api/render/${encodeURIComponent(jobId)}`, { method: 'GET', headers });
123
+ } catch (err) {
124
+ return { code: 'network_error', message: err.message || String(err) };
125
+ }
126
+ if (r.status === 401) return { code: 'not_logged_in' };
127
+ if (r.status === 404) return { code: 'job_not_found' };
128
+ if (r.status >= 400 || !r.data) {
129
+ return { code: r.data?.code || 'status_failed', message: r.data?.message || `HTTP ${r.status}` };
130
+ }
131
+ const job = r.data.data || r.data;
132
+ return {
133
+ code: 'success',
134
+ status: job.status,
135
+ videoUrl: job.video_url || job.videoUrl || null,
136
+ progress: job.progress,
137
+ };
138
+ }
139
+
140
+ async function quota() {
141
+ const headers = authHeaders();
142
+ if (!headers) return { code: 'not_logged_in' };
143
+ let r;
144
+ try {
145
+ r = await requestJson(`${apiBase}/api/user/quota`, { method: 'GET', headers });
146
+ } catch (err) {
147
+ return { code: 'network_error', message: err.message };
148
+ }
149
+ if (r.status === 401) return { code: 'not_logged_in' };
150
+ if (r.status >= 400 || !r.data) {
151
+ return { code: 'quota_lookup_failed', message: `HTTP ${r.status}` };
152
+ }
153
+ const q = r.data.data || r.data;
154
+ return {
155
+ code: 'success',
156
+ remaining: typeof q.remaining === 'number' ? q.remaining : null,
157
+ monthly: typeof q.monthly === 'number' ? q.monthly : null,
158
+ bonus: typeof q.bonus === 'number' ? q.bonus : null,
159
+ };
160
+ }
161
+
162
+ return { submit, status, quota };
163
+ }
164
+
165
+ // The render quota cost is hardcoded backend-side (500 per cloud-render).
166
+ // We expose it here so the UI can show "this costs N quota" without an
167
+ // extra round-trip — keep this in sync if the backend ladder changes.
168
+ const CLOUD_RENDER_COST = 500;
169
+
170
+ module.exports = { createCloudRenderClient, CLOUD_RENDER_COST };
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Pure deck formatters used by the Stage UI (browser-side via stringification)
5
+ * AND by node:test units. Keep self-contained — no closures, no requires —
6
+ * because `Function.prototype.toString()` ships the source verbatim into the
7
+ * UI HTML and the browser then `eval`s the body inside an IIFE.
8
+ *
9
+ * Inputs are deliberately defensive about unknown card shapes (slice deck
10
+ * cards have varied kinds: title / body / outro; not every card has every
11
+ * field). Outputs are plain strings — clipboard-ready, no HTML escaping
12
+ * (browsers handle that on paste).
13
+ */
14
+
15
+ /**
16
+ * Convert a single Slice card to a plain-text snippet — the kind a user
17
+ * would paste into X / Twitter / 即刻 / 公众号 caption / 微博:
18
+ *
19
+ * {title}
20
+ * {caption (if distinct from title)}
21
+ *
22
+ * {narration}
23
+ *
24
+ * Empty fields are skipped. Returns '' if the card has nothing useful.
25
+ *
26
+ * @param {object} card Slice deck card (any shape)
27
+ * @returns {string}
28
+ */
29
+ function formatCardAsText(card) {
30
+ if (!card || typeof card !== 'object') return '';
31
+ const lines = [];
32
+ let title = null;
33
+ if (Array.isArray(card.title)) title = card.title.join(' · ');
34
+ else if (typeof card.title === 'string') title = card.title;
35
+ if (title && title.trim()) lines.push(title.trim());
36
+
37
+ if (typeof card.caption === 'string'
38
+ && card.caption.trim()
39
+ && card.caption.trim() !== title) {
40
+ lines.push(card.caption.trim());
41
+ }
42
+
43
+ if (typeof card.narration === 'string' && card.narration.trim()) {
44
+ if (lines.length > 0) lines.push('');
45
+ lines.push(card.narration.trim());
46
+ }
47
+
48
+ return lines.join('\n').trim();
49
+ }
50
+
51
+ /**
52
+ * Convert a whole Slice deck to a clean Markdown document. Useful for pasting
53
+ * into Notion / blog editors / 飞书 docs / hand-formatting elsewhere:
54
+ *
55
+ * # {seriesTitle || header || 'Deck'}
56
+ *
57
+ * ## 1. {card title}
58
+ *
59
+ * {caption if distinct}
60
+ *
61
+ * > {narration, multi-line preserved as blockquote}
62
+ *
63
+ * Returns '' if the deck shape is unusable.
64
+ *
65
+ * @param {object} deck
66
+ * @returns {string}
67
+ */
68
+ function formatDeckAsMarkdown(deck) {
69
+ if (!deck || typeof deck !== 'object') return '';
70
+ const out = [];
71
+ const top = (typeof deck.seriesTitle === 'string' && deck.seriesTitle.trim())
72
+ || (typeof deck.header === 'string' && deck.header.trim())
73
+ || 'Deck';
74
+ out.push('# ' + top);
75
+ out.push('');
76
+
77
+ const cards = Array.isArray(deck.cards) ? deck.cards : [];
78
+ for (let i = 0; i < cards.length; i++) {
79
+ const c = cards[i] || {};
80
+ let title = null;
81
+ if (Array.isArray(c.title)) title = c.title.join(' · ');
82
+ else if (typeof c.title === 'string') title = c.title;
83
+ if (!title || !title.trim()) {
84
+ title = (typeof c.caption === 'string' && c.caption.trim())
85
+ ? c.caption.trim()
86
+ : 'Card ' + (i + 1);
87
+ }
88
+ out.push('## ' + (i + 1) + '. ' + title);
89
+ out.push('');
90
+
91
+ if (typeof c.caption === 'string'
92
+ && c.caption.trim()
93
+ && c.caption.trim() !== title) {
94
+ out.push(c.caption.trim());
95
+ out.push('');
96
+ }
97
+
98
+ if (typeof c.narration === 'string' && c.narration.trim()) {
99
+ // Multi-line narration → each line gets the blockquote prefix.
100
+ const quoted = c.narration.trim().split('\n').map((ln) => '> ' + ln).join('\n');
101
+ out.push(quoted);
102
+ out.push('');
103
+ }
104
+ }
105
+
106
+ return out.join('\n').trim() + '\n';
107
+ }
108
+
109
+ /**
110
+ * Suggest a friendly filename for the deck JSON download. Sanitises the
111
+ * deck title to filesystem-safe ASCII and tacks on `.json`.
112
+ *
113
+ * @param {object} deck
114
+ * @returns {string}
115
+ */
116
+ function suggestDeckFilename(deck) {
117
+ const top = (deck && typeof deck.seriesTitle === 'string' && deck.seriesTitle.trim())
118
+ || (deck && typeof deck.header === 'string' && deck.header.trim())
119
+ || 'deck';
120
+ const safe = top
121
+ .replace(/[<>:"/\\|?*]+/g, '-')
122
+ .replace(/\s+/g, '-')
123
+ .replace(/-+/g, '-')
124
+ .replace(/^-+|-+$/g, '')
125
+ .slice(0, 60);
126
+ return (safe || 'deck') + '.json';
127
+ }
128
+
129
+ module.exports = {
130
+ formatCardAsText,
131
+ formatDeckAsMarkdown,
132
+ suggestDeckFilename,
133
+ };