@nodaro/shared 1.4.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 (665) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +32 -0
  3. package/dist/index.cjs +12506 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +12269 -0
  6. package/dist/index.d.ts +12269 -0
  7. package/dist/index.js +11831 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +54 -0
  10. package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
  11. package/src/__tests__/ai-avatar-pricing.test.ts +321 -0
  12. package/src/__tests__/apply-video-negative-prompt.test.ts +76 -0
  13. package/src/__tests__/attach-columns.test.ts +28 -0
  14. package/src/__tests__/audio-crossfade-curves.test.ts +33 -0
  15. package/src/__tests__/build-model-menu.test.ts +58 -0
  16. package/src/__tests__/character-aspect-defaults.test.ts +142 -0
  17. package/src/__tests__/character-mention-slug.test.ts +199 -0
  18. package/src/__tests__/character-reference-photo-kinds.test.ts +32 -0
  19. package/src/__tests__/character-usage-mode.test.ts +147 -0
  20. package/src/__tests__/character-variant-assets.test.ts +142 -0
  21. package/src/__tests__/character-voice.test.ts +130 -0
  22. package/src/__tests__/cinematic-avatar-pricing.test.ts +119 -0
  23. package/src/__tests__/combine-transitions.test.ts +137 -0
  24. package/src/__tests__/component-types.test.ts +87 -0
  25. package/src/__tests__/condition-variables.test.ts +98 -0
  26. package/src/__tests__/credit-identifiers.test.ts +811 -0
  27. package/src/__tests__/default-video-provider.test.ts +63 -0
  28. package/src/__tests__/entity-image-handle.test.ts +43 -0
  29. package/src/__tests__/entity-variants.test.ts +33 -0
  30. package/src/__tests__/fal-lip-sync-sets.test.ts +31 -0
  31. package/src/__tests__/featured-entities.test.ts +33 -0
  32. package/src/__tests__/film-pricing.test.ts +64 -0
  33. package/src/__tests__/filter-condition.test.ts +285 -0
  34. package/src/__tests__/freecut-protocol.test.ts +35 -0
  35. package/src/__tests__/generate-text-items.test.ts +19 -0
  36. package/src/__tests__/generated-results.test.ts +50 -0
  37. package/src/__tests__/graph-utils.test.ts +618 -0
  38. package/src/__tests__/group-aggregation.test.ts +83 -0
  39. package/src/__tests__/i18n-locale-completeness.test.ts +156 -0
  40. package/src/__tests__/image-critic-node.test.ts +86 -0
  41. package/src/__tests__/image-mask-mode.test.ts +16 -0
  42. package/src/__tests__/image-ref-limit.test.ts +78 -0
  43. package/src/__tests__/inpaint-region.test.ts +24 -0
  44. package/src/__tests__/input-field-map-parity.test.ts +30 -0
  45. package/src/__tests__/json-evaluator.test.ts +289 -0
  46. package/src/__tests__/json-path.test.ts +64 -0
  47. package/src/__tests__/lip-sync-pricing.test.ts +142 -0
  48. package/src/__tests__/list-merge.test.ts +96 -0
  49. package/src/__tests__/list-sort.test.ts +134 -0
  50. package/src/__tests__/llm-models.test.ts +445 -0
  51. package/src/__tests__/location-mention-slug.test.ts +246 -0
  52. package/src/__tests__/locations-coverage-critic-schema.test.ts +92 -0
  53. package/src/__tests__/loop-delimiter.test.ts +39 -0
  54. package/src/__tests__/lottie-slots.test.ts +245 -0
  55. package/src/__tests__/model-catalog-flux2-resolutions.test.ts +21 -0
  56. package/src/__tests__/model-constants-location.test.ts +20 -0
  57. package/src/__tests__/model-constants.test.ts +122 -0
  58. package/src/__tests__/model-tree.test.ts +86 -0
  59. package/src/__tests__/no-internal-imports.test.ts +25 -0
  60. package/src/__tests__/node-default-mappings.test.ts +175 -0
  61. package/src/__tests__/node-mappable-fields.test.ts +7 -0
  62. package/src/__tests__/node-preset-export.test.ts +46 -0
  63. package/src/__tests__/node-preset-extract.test.ts +166 -0
  64. package/src/__tests__/node-refs-canonical.test.ts +54 -0
  65. package/src/__tests__/node-refs-combine.test.ts +54 -0
  66. package/src/__tests__/node-refs.test.ts +187 -0
  67. package/src/__tests__/node-runtime-keys.test.ts +99 -0
  68. package/src/__tests__/object-aspect-defaults.test.ts +59 -0
  69. package/src/__tests__/parameter-node-value.test.ts +137 -0
  70. package/src/__tests__/pinterest-url.test.ts +35 -0
  71. package/src/__tests__/pipeline-chat.test.ts +110 -0
  72. package/src/__tests__/pipeline-validation.test.ts +98 -0
  73. package/src/__tests__/presentation-utils.test.ts +1137 -0
  74. package/src/__tests__/producer-types.test.ts +47 -0
  75. package/src/__tests__/prompt-length-limits.test.ts +156 -0
  76. package/src/__tests__/provider-directive-defaults.test.ts +46 -0
  77. package/src/__tests__/reduce-strategy-registry.test.ts +43 -0
  78. package/src/__tests__/reference-board-templates.test.ts +33 -0
  79. package/src/__tests__/reference-roles.test.ts +36 -0
  80. package/src/__tests__/resolve-field-mappings.test.ts +110 -0
  81. package/src/__tests__/resolve-pipeline-model.test.ts +76 -0
  82. package/src/__tests__/scene-node-types.test.ts +80 -0
  83. package/src/__tests__/scraper-actors.test.ts +67 -0
  84. package/src/__tests__/seedance-2-catalog.test.ts +18 -0
  85. package/src/__tests__/seedance-2-credit-id.test.ts +23 -0
  86. package/src/__tests__/seedance-aspects.test.ts +52 -0
  87. package/src/__tests__/selector.test.ts +1151 -0
  88. package/src/__tests__/settled-with-limit.test.ts +214 -0
  89. package/src/__tests__/social-media-specs.test.ts +129 -0
  90. package/src/__tests__/sub-workflow-validation.test.ts +88 -0
  91. package/src/__tests__/switchx-pricing.test.ts +28 -0
  92. package/src/__tests__/to-connected-references.test.ts +153 -0
  93. package/src/__tests__/utilities.test.ts +541 -0
  94. package/src/__tests__/video-analysis-pricing.test.ts +58 -0
  95. package/src/__tests__/video-analysis.test.ts +63 -0
  96. package/src/__tests__/video-audio-capability.test.ts +204 -0
  97. package/src/__tests__/video-gen-providers.test.ts +28 -0
  98. package/src/__tests__/video-mode-aliases.test.ts +150 -0
  99. package/src/__tests__/video-ref-limits.test.ts +72 -0
  100. package/src/__tests__/video-utils.test.ts +303 -0
  101. package/src/__tests__/voice-changer-models.test.ts +33 -0
  102. package/src/__tests__/workflow-export-strip.test.ts +46 -0
  103. package/src/ai-avatar-pricing.ts +213 -0
  104. package/src/ancestor-refs.ts +48 -0
  105. package/src/animals.ts +231 -0
  106. package/src/audio-crossfade-curves.ts +73 -0
  107. package/src/audio-fx-presets.ts +64 -0
  108. package/src/avatar-payload-validators.ts +214 -0
  109. package/src/caption-styles.ts +31 -0
  110. package/src/character-aspect-defaults.ts +102 -0
  111. package/src/character-facets.ts +37 -0
  112. package/src/character-mention-slug.ts +166 -0
  113. package/src/character-usage-mode.ts +109 -0
  114. package/src/character-variant-assets.ts +121 -0
  115. package/src/character-voice.ts +151 -0
  116. package/src/cinematic-avatar-pricing.ts +111 -0
  117. package/src/clone-utils.ts +46 -0
  118. package/src/combine-transitions.ts +482 -0
  119. package/src/community.ts +112 -0
  120. package/src/component-types.ts +127 -0
  121. package/src/condition-variables.ts +65 -0
  122. package/src/credit-estimators/index.ts +15 -0
  123. package/src/credit-estimators/video-utils.ts +165 -0
  124. package/src/credit-identifiers.ts +304 -0
  125. package/src/entity-approval-types.ts +31 -0
  126. package/src/entity-aspect-defaults.ts +82 -0
  127. package/src/entity-asset-types.ts +296 -0
  128. package/src/entity-image-handle.ts +36 -0
  129. package/src/extra-refs.ts +139 -0
  130. package/src/featured-entities.ts +136 -0
  131. package/src/film-pricing.ts +117 -0
  132. package/src/filter-condition.ts +250 -0
  133. package/src/flux2-pricing.ts +18 -0
  134. package/src/freecut-protocol.ts +52 -0
  135. package/src/furniture.ts +167 -0
  136. package/src/generate-text-items.ts +15 -0
  137. package/src/generated-results.ts +63 -0
  138. package/src/group-aggregation.ts +75 -0
  139. package/src/hint-graph-types.ts +18 -0
  140. package/src/i18n/action-fx.ar.ts +89 -0
  141. package/src/i18n/action-fx.de.ts +89 -0
  142. package/src/i18n/action-fx.es.ts +89 -0
  143. package/src/i18n/action-fx.fr.ts +89 -0
  144. package/src/i18n/action-fx.he.ts +89 -0
  145. package/src/i18n/action-fx.hi.ts +89 -0
  146. package/src/i18n/action-fx.ja.ts +89 -0
  147. package/src/i18n/action-fx.ko.ts +89 -0
  148. package/src/i18n/action-fx.pt-BR.ts +89 -0
  149. package/src/i18n/action-fx.ru.ts +89 -0
  150. package/src/i18n/action-fx.zh-CN.ts +89 -0
  151. package/src/i18n/aesthetic.ar.ts +170 -0
  152. package/src/i18n/aesthetic.de.ts +48 -0
  153. package/src/i18n/aesthetic.es.ts +169 -0
  154. package/src/i18n/aesthetic.fr.ts +52 -0
  155. package/src/i18n/aesthetic.he.ts +169 -0
  156. package/src/i18n/aesthetic.hi.ts +55 -0
  157. package/src/i18n/aesthetic.ja.ts +165 -0
  158. package/src/i18n/aesthetic.ko.ts +169 -0
  159. package/src/i18n/aesthetic.pt-BR.ts +148 -0
  160. package/src/i18n/aesthetic.ru.ts +155 -0
  161. package/src/i18n/aesthetic.zh-CN.ts +172 -0
  162. package/src/i18n/animals.ar.ts +145 -0
  163. package/src/i18n/animals.de.ts +144 -0
  164. package/src/i18n/animals.es.ts +158 -0
  165. package/src/i18n/animals.fr.ts +144 -0
  166. package/src/i18n/animals.he.ts +155 -0
  167. package/src/i18n/animals.hi.ts +157 -0
  168. package/src/i18n/animals.ja.ts +159 -0
  169. package/src/i18n/animals.ko.ts +159 -0
  170. package/src/i18n/animals.pt-BR.ts +157 -0
  171. package/src/i18n/animals.ru.ts +155 -0
  172. package/src/i18n/animals.zh-CN.ts +155 -0
  173. package/src/i18n/atmosphere.ar.ts +49 -0
  174. package/src/i18n/atmosphere.de.ts +47 -0
  175. package/src/i18n/atmosphere.es.ts +50 -0
  176. package/src/i18n/atmosphere.fr.ts +47 -0
  177. package/src/i18n/atmosphere.he.ts +47 -0
  178. package/src/i18n/atmosphere.hi.ts +49 -0
  179. package/src/i18n/atmosphere.ja.ts +49 -0
  180. package/src/i18n/atmosphere.ko.ts +49 -0
  181. package/src/i18n/atmosphere.pt-BR.ts +49 -0
  182. package/src/i18n/atmosphere.ru.ts +47 -0
  183. package/src/i18n/atmosphere.zh-CN.ts +152 -0
  184. package/src/i18n/backdrop.ar.ts +53 -0
  185. package/src/i18n/backdrop.de.ts +46 -0
  186. package/src/i18n/backdrop.es.ts +54 -0
  187. package/src/i18n/backdrop.fr.ts +52 -0
  188. package/src/i18n/backdrop.he.ts +51 -0
  189. package/src/i18n/backdrop.hi.ts +59 -0
  190. package/src/i18n/backdrop.ja.ts +61 -0
  191. package/src/i18n/backdrop.ko.ts +59 -0
  192. package/src/i18n/backdrop.pt-BR.ts +59 -0
  193. package/src/i18n/backdrop.ru.ts +57 -0
  194. package/src/i18n/backdrop.zh-CN.ts +57 -0
  195. package/src/i18n/camera-format.ar.ts +40 -0
  196. package/src/i18n/camera-format.de.ts +37 -0
  197. package/src/i18n/camera-format.es.ts +41 -0
  198. package/src/i18n/camera-format.fr.ts +37 -0
  199. package/src/i18n/camera-format.he.ts +37 -0
  200. package/src/i18n/camera-format.hi.ts +42 -0
  201. package/src/i18n/camera-format.ja.ts +37 -0
  202. package/src/i18n/camera-format.ko.ts +37 -0
  203. package/src/i18n/camera-format.pt-BR.ts +46 -0
  204. package/src/i18n/camera-format.ru.ts +42 -0
  205. package/src/i18n/camera-format.zh-CN.ts +42 -0
  206. package/src/i18n/camera-motions.ar.ts +86 -0
  207. package/src/i18n/camera-motions.de.ts +74 -0
  208. package/src/i18n/camera-motions.es.ts +86 -0
  209. package/src/i18n/camera-motions.fr.ts +86 -0
  210. package/src/i18n/camera-motions.he.ts +86 -0
  211. package/src/i18n/camera-motions.hi.ts +99 -0
  212. package/src/i18n/camera-motions.ja.ts +99 -0
  213. package/src/i18n/camera-motions.ko.ts +99 -0
  214. package/src/i18n/camera-motions.pt-BR.ts +99 -0
  215. package/src/i18n/camera-motions.ru.ts +99 -0
  216. package/src/i18n/camera-motions.zh-CN.ts +99 -0
  217. package/src/i18n/character-fx.ar.ts +72 -0
  218. package/src/i18n/character-fx.de.ts +72 -0
  219. package/src/i18n/character-fx.es.ts +72 -0
  220. package/src/i18n/character-fx.fr.ts +72 -0
  221. package/src/i18n/character-fx.he.ts +72 -0
  222. package/src/i18n/character-fx.hi.ts +72 -0
  223. package/src/i18n/character-fx.ja.ts +72 -0
  224. package/src/i18n/character-fx.ko.ts +72 -0
  225. package/src/i18n/character-fx.pt-BR.ts +72 -0
  226. package/src/i18n/character-fx.ru.ts +72 -0
  227. package/src/i18n/character-fx.zh-CN.ts +72 -0
  228. package/src/i18n/color-look.ar.ts +50 -0
  229. package/src/i18n/color-look.de.ts +47 -0
  230. package/src/i18n/color-look.es.ts +50 -0
  231. package/src/i18n/color-look.fr.ts +50 -0
  232. package/src/i18n/color-look.he.ts +49 -0
  233. package/src/i18n/color-look.hi.ts +52 -0
  234. package/src/i18n/color-look.ja.ts +54 -0
  235. package/src/i18n/color-look.ko.ts +54 -0
  236. package/src/i18n/color-look.pt-BR.ts +54 -0
  237. package/src/i18n/color-look.ru.ts +52 -0
  238. package/src/i18n/color-look.zh-CN.ts +53 -0
  239. package/src/i18n/composition-effects.ar.ts +26 -0
  240. package/src/i18n/composition-effects.de.ts +25 -0
  241. package/src/i18n/composition-effects.es.ts +27 -0
  242. package/src/i18n/composition-effects.fr.ts +25 -0
  243. package/src/i18n/composition-effects.he.ts +25 -0
  244. package/src/i18n/composition-effects.hi.ts +27 -0
  245. package/src/i18n/composition-effects.ja.ts +27 -0
  246. package/src/i18n/composition-effects.ko.ts +27 -0
  247. package/src/i18n/composition-effects.pt-BR.ts +25 -0
  248. package/src/i18n/composition-effects.ru.ts +82 -0
  249. package/src/i18n/composition-effects.zh-CN.ts +25 -0
  250. package/src/i18n/era.ar.ts +37 -0
  251. package/src/i18n/era.de.ts +34 -0
  252. package/src/i18n/era.es.ts +37 -0
  253. package/src/i18n/era.fr.ts +37 -0
  254. package/src/i18n/era.he.ts +36 -0
  255. package/src/i18n/era.hi.ts +39 -0
  256. package/src/i18n/era.ja.ts +41 -0
  257. package/src/i18n/era.ko.ts +39 -0
  258. package/src/i18n/era.pt-BR.ts +41 -0
  259. package/src/i18n/era.ru.ts +121 -0
  260. package/src/i18n/era.zh-CN.ts +39 -0
  261. package/src/i18n/exposure-settings.ar.ts +28 -0
  262. package/src/i18n/exposure-settings.de.ts +26 -0
  263. package/src/i18n/exposure-settings.es.ts +28 -0
  264. package/src/i18n/exposure-settings.fr.ts +29 -0
  265. package/src/i18n/exposure-settings.he.ts +28 -0
  266. package/src/i18n/exposure-settings.hi.ts +31 -0
  267. package/src/i18n/exposure-settings.ja.ts +31 -0
  268. package/src/i18n/exposure-settings.ko.ts +31 -0
  269. package/src/i18n/exposure-settings.pt-BR.ts +31 -0
  270. package/src/i18n/exposure-settings.ru.ts +31 -0
  271. package/src/i18n/exposure-settings.zh-CN.ts +31 -0
  272. package/src/i18n/framing.ar.ts +78 -0
  273. package/src/i18n/framing.de.ts +73 -0
  274. package/src/i18n/framing.es.ts +78 -0
  275. package/src/i18n/framing.fr.ts +78 -0
  276. package/src/i18n/framing.he.ts +77 -0
  277. package/src/i18n/framing.hi.ts +82 -0
  278. package/src/i18n/framing.ja.ts +84 -0
  279. package/src/i18n/framing.ko.ts +82 -0
  280. package/src/i18n/framing.pt-BR.ts +84 -0
  281. package/src/i18n/framing.ru.ts +82 -0
  282. package/src/i18n/framing.zh-CN.ts +82 -0
  283. package/src/i18n/furniture.ar.ts +92 -0
  284. package/src/i18n/furniture.de.ts +93 -0
  285. package/src/i18n/furniture.es.ts +101 -0
  286. package/src/i18n/furniture.fr.ts +93 -0
  287. package/src/i18n/furniture.he.ts +101 -0
  288. package/src/i18n/furniture.hi.ts +101 -0
  289. package/src/i18n/furniture.ja.ts +101 -0
  290. package/src/i18n/furniture.ko.ts +101 -0
  291. package/src/i18n/furniture.pt-BR.ts +101 -0
  292. package/src/i18n/furniture.ru.ts +101 -0
  293. package/src/i18n/furniture.zh-CN.ts +101 -0
  294. package/src/i18n/held-prop.ar.ts +74 -0
  295. package/src/i18n/held-prop.de.ts +65 -0
  296. package/src/i18n/held-prop.es.ts +83 -0
  297. package/src/i18n/held-prop.fr.ts +74 -0
  298. package/src/i18n/held-prop.he.ts +73 -0
  299. package/src/i18n/held-prop.hi.ts +82 -0
  300. package/src/i18n/held-prop.ja.ts +84 -0
  301. package/src/i18n/held-prop.ko.ts +84 -0
  302. package/src/i18n/held-prop.pt-BR.ts +84 -0
  303. package/src/i18n/held-prop.ru.ts +82 -0
  304. package/src/i18n/held-prop.zh-CN.ts +82 -0
  305. package/src/i18n/index.ts +208 -0
  306. package/src/i18n/instrumentation.ar.ts +195 -0
  307. package/src/i18n/instrumentation.de.ts +195 -0
  308. package/src/i18n/instrumentation.es.ts +195 -0
  309. package/src/i18n/instrumentation.fr.ts +195 -0
  310. package/src/i18n/instrumentation.he.ts +195 -0
  311. package/src/i18n/instrumentation.hi.ts +195 -0
  312. package/src/i18n/instrumentation.ja.ts +195 -0
  313. package/src/i18n/instrumentation.ko.ts +195 -0
  314. package/src/i18n/instrumentation.pt-BR.ts +195 -0
  315. package/src/i18n/instrumentation.ru.ts +195 -0
  316. package/src/i18n/instrumentation.zh-CN.ts +195 -0
  317. package/src/i18n/lens.ar.ts +24 -0
  318. package/src/i18n/lens.de.ts +24 -0
  319. package/src/i18n/lens.es.ts +24 -0
  320. package/src/i18n/lens.fr.ts +24 -0
  321. package/src/i18n/lens.he.ts +24 -0
  322. package/src/i18n/lens.hi.ts +24 -0
  323. package/src/i18n/lens.ja.ts +24 -0
  324. package/src/i18n/lens.ko.ts +24 -0
  325. package/src/i18n/lens.pt-BR.ts +24 -0
  326. package/src/i18n/lens.ru.ts +25 -0
  327. package/src/i18n/lens.zh-CN.ts +24 -0
  328. package/src/i18n/lighting.ar.ts +86 -0
  329. package/src/i18n/lighting.de.ts +84 -0
  330. package/src/i18n/lighting.es.ts +89 -0
  331. package/src/i18n/lighting.fr.ts +84 -0
  332. package/src/i18n/lighting.he.ts +88 -0
  333. package/src/i18n/lighting.hi.ts +90 -0
  334. package/src/i18n/lighting.ja.ts +90 -0
  335. package/src/i18n/lighting.ko.ts +90 -0
  336. package/src/i18n/lighting.pt-BR.ts +90 -0
  337. package/src/i18n/lighting.ru.ts +88 -0
  338. package/src/i18n/lighting.zh-CN.ts +92 -0
  339. package/src/i18n/loop-subject.ar.ts +39 -0
  340. package/src/i18n/loop-subject.de.ts +39 -0
  341. package/src/i18n/loop-subject.es.ts +39 -0
  342. package/src/i18n/loop-subject.fr.ts +39 -0
  343. package/src/i18n/loop-subject.he.ts +39 -0
  344. package/src/i18n/loop-subject.hi.ts +39 -0
  345. package/src/i18n/loop-subject.ja.ts +39 -0
  346. package/src/i18n/loop-subject.ko.ts +39 -0
  347. package/src/i18n/loop-subject.pt-BR.ts +39 -0
  348. package/src/i18n/loop-subject.ru.ts +39 -0
  349. package/src/i18n/loop-subject.zh-CN.ts +39 -0
  350. package/src/i18n/materials.ar.ts +80 -0
  351. package/src/i18n/materials.de.ts +80 -0
  352. package/src/i18n/materials.es.ts +87 -0
  353. package/src/i18n/materials.fr.ts +80 -0
  354. package/src/i18n/materials.he.ts +86 -0
  355. package/src/i18n/materials.hi.ts +86 -0
  356. package/src/i18n/materials.ja.ts +88 -0
  357. package/src/i18n/materials.ko.ts +88 -0
  358. package/src/i18n/materials.pt-BR.ts +88 -0
  359. package/src/i18n/materials.ru.ts +86 -0
  360. package/src/i18n/materials.zh-CN.ts +86 -0
  361. package/src/i18n/mood.ar.ts +61 -0
  362. package/src/i18n/mood.de.ts +61 -0
  363. package/src/i18n/mood.es.ts +66 -0
  364. package/src/i18n/mood.fr.ts +61 -0
  365. package/src/i18n/mood.he.ts +64 -0
  366. package/src/i18n/mood.hi.ts +66 -0
  367. package/src/i18n/mood.ja.ts +66 -0
  368. package/src/i18n/mood.ko.ts +66 -0
  369. package/src/i18n/mood.pt-BR.ts +64 -0
  370. package/src/i18n/mood.ru.ts +64 -0
  371. package/src/i18n/mood.zh-CN.ts +64 -0
  372. package/src/i18n/music-genre.ar.ts +302 -0
  373. package/src/i18n/music-genre.de.ts +302 -0
  374. package/src/i18n/music-genre.es.ts +302 -0
  375. package/src/i18n/music-genre.fr.ts +302 -0
  376. package/src/i18n/music-genre.he.ts +302 -0
  377. package/src/i18n/music-genre.hi.ts +302 -0
  378. package/src/i18n/music-genre.ja.ts +302 -0
  379. package/src/i18n/music-genre.ko.ts +302 -0
  380. package/src/i18n/music-genre.pt-BR.ts +302 -0
  381. package/src/i18n/music-genre.ru.ts +302 -0
  382. package/src/i18n/music-genre.zh-CN.ts +302 -0
  383. package/src/i18n/music-mood.ar.ts +75 -0
  384. package/src/i18n/music-mood.de.ts +75 -0
  385. package/src/i18n/music-mood.es.ts +75 -0
  386. package/src/i18n/music-mood.fr.ts +75 -0
  387. package/src/i18n/music-mood.he.ts +75 -0
  388. package/src/i18n/music-mood.hi.ts +75 -0
  389. package/src/i18n/music-mood.ja.ts +75 -0
  390. package/src/i18n/music-mood.ko.ts +75 -0
  391. package/src/i18n/music-mood.pt-BR.ts +75 -0
  392. package/src/i18n/music-mood.ru.ts +75 -0
  393. package/src/i18n/music-mood.zh-CN.ts +75 -0
  394. package/src/i18n/person.ar.ts +638 -0
  395. package/src/i18n/person.de.ts +629 -0
  396. package/src/i18n/person.es.ts +645 -0
  397. package/src/i18n/person.fr.ts +621 -0
  398. package/src/i18n/person.he.ts +656 -0
  399. package/src/i18n/person.hi.ts +654 -0
  400. package/src/i18n/person.ja.ts +662 -0
  401. package/src/i18n/person.ko.ts +662 -0
  402. package/src/i18n/person.pt-BR.ts +656 -0
  403. package/src/i18n/person.ru.ts +656 -0
  404. package/src/i18n/person.zh-CN.ts +656 -0
  405. package/src/i18n/photo-genre.ar.ts +58 -0
  406. package/src/i18n/photo-genre.de.ts +52 -0
  407. package/src/i18n/photo-genre.es.ts +64 -0
  408. package/src/i18n/photo-genre.fr.ts +58 -0
  409. package/src/i18n/photo-genre.he.ts +62 -0
  410. package/src/i18n/photo-genre.hi.ts +63 -0
  411. package/src/i18n/photo-genre.ja.ts +65 -0
  412. package/src/i18n/photo-genre.ko.ts +63 -0
  413. package/src/i18n/photo-genre.pt-BR.ts +65 -0
  414. package/src/i18n/photo-genre.ru.ts +63 -0
  415. package/src/i18n/photo-genre.zh-CN.ts +63 -0
  416. package/src/i18n/photographer.ar.ts +75 -0
  417. package/src/i18n/photographer.de.ts +74 -0
  418. package/src/i18n/photographer.es.ts +77 -0
  419. package/src/i18n/photographer.fr.ts +77 -0
  420. package/src/i18n/photographer.he.ts +78 -0
  421. package/src/i18n/photographer.hi.ts +80 -0
  422. package/src/i18n/photographer.ja.ts +84 -0
  423. package/src/i18n/photographer.ko.ts +84 -0
  424. package/src/i18n/photographer.pt-BR.ts +83 -0
  425. package/src/i18n/photographer.ru.ts +78 -0
  426. package/src/i18n/photographer.zh-CN.ts +78 -0
  427. package/src/i18n/pose.ar.ts +95 -0
  428. package/src/i18n/pose.de.ts +96 -0
  429. package/src/i18n/pose.es.ts +104 -0
  430. package/src/i18n/pose.fr.ts +96 -0
  431. package/src/i18n/pose.he.ts +104 -0
  432. package/src/i18n/pose.hi.ts +104 -0
  433. package/src/i18n/pose.ja.ts +104 -0
  434. package/src/i18n/pose.ko.ts +104 -0
  435. package/src/i18n/pose.pt-BR.ts +104 -0
  436. package/src/i18n/pose.ru.ts +104 -0
  437. package/src/i18n/pose.zh-CN.ts +104 -0
  438. package/src/i18n/post-process-effects.ar.ts +25 -0
  439. package/src/i18n/post-process-effects.de.ts +24 -0
  440. package/src/i18n/post-process-effects.es.ts +26 -0
  441. package/src/i18n/post-process-effects.fr.ts +24 -0
  442. package/src/i18n/post-process-effects.he.ts +24 -0
  443. package/src/i18n/post-process-effects.hi.ts +26 -0
  444. package/src/i18n/post-process-effects.ja.ts +26 -0
  445. package/src/i18n/post-process-effects.ko.ts +26 -0
  446. package/src/i18n/post-process-effects.pt-BR.ts +24 -0
  447. package/src/i18n/post-process-effects.ru.ts +24 -0
  448. package/src/i18n/post-process-effects.zh-CN.ts +24 -0
  449. package/src/i18n/render-quality.ar.ts +34 -0
  450. package/src/i18n/render-quality.de.ts +30 -0
  451. package/src/i18n/render-quality.es.ts +35 -0
  452. package/src/i18n/render-quality.fr.ts +34 -0
  453. package/src/i18n/render-quality.he.ts +33 -0
  454. package/src/i18n/render-quality.hi.ts +39 -0
  455. package/src/i18n/render-quality.ja.ts +39 -0
  456. package/src/i18n/render-quality.ko.ts +39 -0
  457. package/src/i18n/render-quality.pt-BR.ts +37 -0
  458. package/src/i18n/render-quality.ru.ts +37 -0
  459. package/src/i18n/render-quality.zh-CN.ts +37 -0
  460. package/src/i18n/seasons.ar.ts +10 -0
  461. package/src/i18n/seasons.de.ts +10 -0
  462. package/src/i18n/seasons.es.ts +10 -0
  463. package/src/i18n/seasons.fr.ts +10 -0
  464. package/src/i18n/seasons.he.ts +10 -0
  465. package/src/i18n/seasons.hi.ts +10 -0
  466. package/src/i18n/seasons.ja.ts +10 -0
  467. package/src/i18n/seasons.ko.ts +10 -0
  468. package/src/i18n/seasons.pt-BR.ts +10 -0
  469. package/src/i18n/seasons.ru.ts +10 -0
  470. package/src/i18n/seasons.zh-CN.ts +10 -0
  471. package/src/i18n/setting.ar.ts +73 -0
  472. package/src/i18n/setting.de.ts +73 -0
  473. package/src/i18n/setting.es.ts +77 -0
  474. package/src/i18n/setting.fr.ts +73 -0
  475. package/src/i18n/setting.he.ts +76 -0
  476. package/src/i18n/setting.hi.ts +76 -0
  477. package/src/i18n/setting.ja.ts +78 -0
  478. package/src/i18n/setting.ko.ts +76 -0
  479. package/src/i18n/setting.pt-BR.ts +78 -0
  480. package/src/i18n/setting.ru.ts +76 -0
  481. package/src/i18n/setting.zh-CN.ts +76 -0
  482. package/src/i18n/style.ar.ts +55 -0
  483. package/src/i18n/style.de.ts +54 -0
  484. package/src/i18n/style.es.ts +54 -0
  485. package/src/i18n/style.fr.ts +54 -0
  486. package/src/i18n/style.he.ts +54 -0
  487. package/src/i18n/style.hi.ts +54 -0
  488. package/src/i18n/style.ja.ts +54 -0
  489. package/src/i18n/style.ko.ts +54 -0
  490. package/src/i18n/style.pt-BR.ts +56 -0
  491. package/src/i18n/style.ru.ts +54 -0
  492. package/src/i18n/style.zh-CN.ts +54 -0
  493. package/src/i18n/styling.ar.ts +295 -0
  494. package/src/i18n/styling.de.ts +285 -0
  495. package/src/i18n/styling.es.ts +317 -0
  496. package/src/i18n/styling.fr.ts +285 -0
  497. package/src/i18n/styling.he.ts +307 -0
  498. package/src/i18n/styling.hi.ts +323 -0
  499. package/src/i18n/styling.ja.ts +317 -0
  500. package/src/i18n/styling.ko.ts +311 -0
  501. package/src/i18n/styling.pt-BR.ts +301 -0
  502. package/src/i18n/styling.ru.ts +301 -0
  503. package/src/i18n/styling.zh-CN.ts +307 -0
  504. package/src/i18n/temporal.ar.ts +31 -0
  505. package/src/i18n/temporal.de.ts +24 -0
  506. package/src/i18n/temporal.es.ts +27 -0
  507. package/src/i18n/temporal.fr.ts +28 -0
  508. package/src/i18n/temporal.he.ts +31 -0
  509. package/src/i18n/temporal.hi.ts +31 -0
  510. package/src/i18n/temporal.ja.ts +31 -0
  511. package/src/i18n/temporal.ko.ts +31 -0
  512. package/src/i18n/temporal.pt-BR.ts +31 -0
  513. package/src/i18n/temporal.ru.ts +31 -0
  514. package/src/i18n/temporal.zh-CN.ts +31 -0
  515. package/src/i18n/transitions.ar.ts +97 -0
  516. package/src/i18n/transitions.de.ts +97 -0
  517. package/src/i18n/transitions.es.ts +97 -0
  518. package/src/i18n/transitions.fr.ts +97 -0
  519. package/src/i18n/transitions.he.ts +97 -0
  520. package/src/i18n/transitions.hi.ts +97 -0
  521. package/src/i18n/transitions.ja.ts +97 -0
  522. package/src/i18n/transitions.ko.ts +97 -0
  523. package/src/i18n/transitions.pt-BR.ts +97 -0
  524. package/src/i18n/transitions.ru.ts +97 -0
  525. package/src/i18n/transitions.zh-CN.ts +97 -0
  526. package/src/i18n/types.ts +158 -0
  527. package/src/i18n/vehicles.ar.ts +137 -0
  528. package/src/i18n/vehicles.de.ts +125 -0
  529. package/src/i18n/vehicles.es.ts +138 -0
  530. package/src/i18n/vehicles.fr.ts +125 -0
  531. package/src/i18n/vehicles.he.ts +136 -0
  532. package/src/i18n/vehicles.hi.ts +138 -0
  533. package/src/i18n/vehicles.ja.ts +138 -0
  534. package/src/i18n/vehicles.ko.ts +138 -0
  535. package/src/i18n/vehicles.pt-BR.ts +136 -0
  536. package/src/i18n/vehicles.ru.ts +136 -0
  537. package/src/i18n/vehicles.zh-CN.ts +136 -0
  538. package/src/i18n/voice-character.ar.ts +126 -0
  539. package/src/i18n/voice-character.de.ts +126 -0
  540. package/src/i18n/voice-character.es.ts +126 -0
  541. package/src/i18n/voice-character.fr.ts +126 -0
  542. package/src/i18n/voice-character.he.ts +126 -0
  543. package/src/i18n/voice-character.hi.ts +126 -0
  544. package/src/i18n/voice-character.ja.ts +126 -0
  545. package/src/i18n/voice-character.ko.ts +126 -0
  546. package/src/i18n/voice-character.pt-BR.ts +126 -0
  547. package/src/i18n/voice-character.ru.ts +126 -0
  548. package/src/i18n/voice-character.zh-CN.ts +126 -0
  549. package/src/i18n/voice-delivery.ar.ts +77 -0
  550. package/src/i18n/voice-delivery.de.ts +77 -0
  551. package/src/i18n/voice-delivery.es.ts +77 -0
  552. package/src/i18n/voice-delivery.fr.ts +77 -0
  553. package/src/i18n/voice-delivery.he.ts +77 -0
  554. package/src/i18n/voice-delivery.hi.ts +77 -0
  555. package/src/i18n/voice-delivery.ja.ts +77 -0
  556. package/src/i18n/voice-delivery.ko.ts +77 -0
  557. package/src/i18n/voice-delivery.pt-BR.ts +77 -0
  558. package/src/i18n/voice-delivery.ru.ts +77 -0
  559. package/src/i18n/voice-delivery.zh-CN.ts +77 -0
  560. package/src/i18n/weapons.ar.ts +115 -0
  561. package/src/i18n/weapons.de.ts +114 -0
  562. package/src/i18n/weapons.es.ts +116 -0
  563. package/src/i18n/weapons.fr.ts +103 -0
  564. package/src/i18n/weapons.he.ts +114 -0
  565. package/src/i18n/weapons.hi.ts +116 -0
  566. package/src/i18n/weapons.ja.ts +116 -0
  567. package/src/i18n/weapons.ko.ts +116 -0
  568. package/src/i18n/weapons.pt-BR.ts +114 -0
  569. package/src/i18n/weapons.ru.ts +114 -0
  570. package/src/i18n/weapons.zh-CN.ts +114 -0
  571. package/src/image-critic-node.ts +61 -0
  572. package/src/image-critic-types.ts +40 -0
  573. package/src/index.ts +866 -0
  574. package/src/inject-upstream.ts +15 -0
  575. package/src/inpaint-region.ts +51 -0
  576. package/src/json-evaluator.ts +732 -0
  577. package/src/json-path.ts +52 -0
  578. package/src/lip-sync-pricing.ts +90 -0
  579. package/src/list-merge.ts +40 -0
  580. package/src/list-sort.ts +122 -0
  581. package/src/list-source-resolver.ts +50 -0
  582. package/src/llm-models.ts +245 -0
  583. package/src/location-mention-slug.ts +291 -0
  584. package/src/location-preset-catalog-map.ts +128 -0
  585. package/src/loop-delimiter.ts +73 -0
  586. package/src/lora-routing.ts +95 -0
  587. package/src/lottie-overlay-catalog.ts +175 -0
  588. package/src/lottie-slots.ts +309 -0
  589. package/src/model-catalog.ts +2386 -0
  590. package/src/model-constants.ts +1910 -0
  591. package/src/model-tree.ts +120 -0
  592. package/src/monetization.ts +15 -0
  593. package/src/multi-pick.ts +42 -0
  594. package/src/node-default-mappings.ts +262 -0
  595. package/src/node-mappable-fields.ts +70 -0
  596. package/src/node-preset-export.ts +56 -0
  597. package/src/node-preset-extract.ts +147 -0
  598. package/src/node-refs.ts +253 -0
  599. package/src/node-runtime-keys.ts +117 -0
  600. package/src/object-aspect-defaults.ts +72 -0
  601. package/src/object-picker-types.ts +22 -0
  602. package/src/parameter-node-value.ts +284 -0
  603. package/src/pinterest-url.ts +28 -0
  604. package/src/pipeline-chat.ts +72 -0
  605. package/src/pipeline-defaults.ts +318 -0
  606. package/src/pipeline-events.ts +162 -0
  607. package/src/pipeline-state-types.ts +196 -0
  608. package/src/pipeline-types.ts +647 -0
  609. package/src/pipeline-validation.ts +87 -0
  610. package/src/presentation-types.ts +23 -0
  611. package/src/presentation-utils.ts +397 -0
  612. package/src/producer-types.ts +188 -0
  613. package/src/progress-curve.ts +99 -0
  614. package/src/provider-directive-defaults.ts +69 -0
  615. package/src/reduce-strategy-registry.ts +121 -0
  616. package/src/reference-board-templates.ts +81 -0
  617. package/src/reference-roles.ts +153 -0
  618. package/src/reference-sheet/__tests__/board-to-column.test.ts +20 -0
  619. package/src/reference-sheet/__tests__/catalog.test.ts +50 -0
  620. package/src/reference-sheet/__tests__/cost.test.ts +38 -0
  621. package/src/reference-sheet/__tests__/no-drift.test.ts +28 -0
  622. package/src/reference-sheet/__tests__/panel-plan.test.ts +61 -0
  623. package/src/reference-sheet/__tests__/panel-prompts.test.ts +17 -0
  624. package/src/reference-sheet/__tests__/plan-generation.test.ts +72 -0
  625. package/src/reference-sheet/__tests__/presets.test.ts +54 -0
  626. package/src/reference-sheet/__tests__/types.test.ts +24 -0
  627. package/src/reference-sheet/catalog.ts +144 -0
  628. package/src/reference-sheet/cost.ts +37 -0
  629. package/src/reference-sheet/credit-id.ts +7 -0
  630. package/src/reference-sheet/index.ts +8 -0
  631. package/src/reference-sheet/panel-plan.ts +65 -0
  632. package/src/reference-sheet/panel-prompts.ts +14 -0
  633. package/src/reference-sheet/plan-generation.ts +79 -0
  634. package/src/reference-sheet/presets.ts +77 -0
  635. package/src/reference-sheet/types.ts +92 -0
  636. package/src/repeat-types.ts +97 -0
  637. package/src/resolve-field-mappings.ts +119 -0
  638. package/src/route-filter.ts +80 -0
  639. package/src/scene-helper-types.ts +249 -0
  640. package/src/scene-node-types.ts +281 -0
  641. package/src/scraper-actors.ts +57 -0
  642. package/src/scraper-output-schemas.ts +25 -0
  643. package/src/seasons.ts +45 -0
  644. package/src/selector.ts +864 -0
  645. package/src/settled-with-limit.ts +42 -0
  646. package/src/shot-element.ts +43 -0
  647. package/src/social-media-specs.ts +121 -0
  648. package/src/social-post.ts +24 -0
  649. package/src/sub-workflow-validation.ts +78 -0
  650. package/src/supported-fonts.ts +40 -0
  651. package/src/surround.ts +113 -0
  652. package/src/switchx-pricing.ts +33 -0
  653. package/src/text-separators.ts +59 -0
  654. package/src/to-connected-references.ts +77 -0
  655. package/src/types.ts +272 -0
  656. package/src/variant-job-id.ts +17 -0
  657. package/src/vehicles.ts +213 -0
  658. package/src/video-analysis-pricing.ts +74 -0
  659. package/src/video-analysis.ts +127 -0
  660. package/src/video-duration.ts +25 -0
  661. package/src/voice-changer-models.ts +36 -0
  662. package/src/voice-matcher-types.ts +37 -0
  663. package/src/voices.ts +92 -0
  664. package/src/weapons.ts +190 -0
  665. package/src/workflow-export.ts +153 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Video-analysis node — shared data contract.
3
+ *
4
+ * Single source of truth for the video-analysis node: the backend worker, the
5
+ * frontend, and a future generator all import from here. Two schema layers:
6
+ * - `windowAnalysisSchema` — what the LLM emits per analysis window
7
+ * (strict-JSON footer). `scenes` has NO min (a quiet window returning zero
8
+ * scenes is a VALID result). Model-emitted `oversized`/`slotRefs` are
9
+ * STRIPPED here — they are validator-computed later (z.object drops unknown
10
+ * keys by default).
11
+ * - `videoAnalysisResultSchema` — the merged, validator-computed result across
12
+ * all windows; requires >=1 scene overall.
13
+ *
14
+ * Entity slots are referenced in scene `visual` text via `{slot:<id>}` tokens
15
+ * (`SLOT_TOKEN_RE`) — a grammar distinct from `NODE_REF_PATTERN` and the
16
+ * `{image:N}` reference tokens. Unresolved tokens UNWRAP to their literal id
17
+ * text, never delete (spec invariant), so a downstream prompt never carries a
18
+ * dangling `{slot:…}` placeholder.
19
+ */
20
+ import { z } from "zod"
21
+
22
+ export const VIDEO_ANALYSIS_MAX_SCENE_SEC = 8
23
+ export const VIDEO_ANALYSIS_ENTITY_SOURCES = ["wired-character", "wired-object", "wired-location", "wired-creature"] as const
24
+ export type VideoAnalysisEntitySource = (typeof VIDEO_ANALYSIS_ENTITY_SOURCES)[number]
25
+ /** Matches {slot:<id>} tokens. Distinct from NODE_REF_PATTERN / {image:N} grammars. */
26
+ export const SLOT_TOKEN_RE = /\{slot:([a-z0-9-]+)\}/g
27
+
28
+ export const entitySlotSchema = z.object({
29
+ slotId: z.string().min(1).regex(/^[a-z0-9-]+$/),
30
+ label: z.string().min(1),
31
+ source: z.enum(VIDEO_ANALYSIS_ENTITY_SOURCES),
32
+ role: z.string().min(1),
33
+ description: z.string().min(1),
34
+ })
35
+ export type EntitySlot = z.infer<typeof entitySlotSchema>
36
+
37
+ const audioSchema = z.object({
38
+ mode: z.enum(["speech", "music", "sfx", "silence"]),
39
+ content: z.string(), // speech: verbatim quote; music/sfx: gen-ready description; silence: "" allowed
40
+ voice: z.string().optional(),
41
+ })
42
+
43
+ const windowSceneBase = z.object({
44
+ startSec: z.number().min(0),
45
+ endSec: z.number().min(0),
46
+ label: z.string().min(1),
47
+ shotType: z.string().min(1),
48
+ camera: z.string(),
49
+ visual: z.string().min(1),
50
+ transitionOut: z.enum(["cut", "fade", "wipe", "whip"]).optional(),
51
+ audio: audioSchema,
52
+ })
53
+ // .strip() (default) drops model-emitted oversized/slotRefs — validator-computed only.
54
+ const windowSceneSchema = windowSceneBase.refine((s) => s.endSec > s.startSec, { message: "endSec must be > startSec" })
55
+ export type WindowScene = z.infer<typeof windowSceneSchema>
56
+
57
+ /** What the MODEL emits per window (strict-JSON footer schema). scenes has NO min. */
58
+ export const windowAnalysisSchema = z.object({
59
+ language: z.string().optional(),
60
+ slots: z.array(entitySlotSchema),
61
+ scenes: z.array(windowSceneSchema),
62
+ })
63
+ export type WindowAnalysis = z.infer<typeof windowAnalysisSchema>
64
+
65
+ export const analyzedSceneSchema = windowSceneBase.extend({
66
+ sceneNumber: z.number().int().min(1),
67
+ visualResolved: z.string().min(1),
68
+ oversized: z.boolean().optional(),
69
+ slotRefs: z.array(z.string()),
70
+ }).refine((s) => s.endSec > s.startSec, { message: "endSec must be > startSec" })
71
+ export type AnalyzedScene = z.infer<typeof analyzedSceneSchema>
72
+
73
+ export const videoAnalysisResultSchema = z.object({
74
+ meta: z.object({
75
+ durationSec: z.number().positive(),
76
+ width: z.number().int().positive(),
77
+ height: z.number().int().positive(),
78
+ aspectRatio: z.string(),
79
+ title: z.string().optional(),
80
+ language: z.string().optional(),
81
+ }),
82
+ slots: z.array(entitySlotSchema),
83
+ scenes: z.array(analyzedSceneSchema).min(1),
84
+ })
85
+ export type VideoAnalysisResult = z.infer<typeof videoAnalysisResultSchema>
86
+
87
+ export function deriveSlotRefs(visual: string): string[] {
88
+ const out: string[] = []
89
+ for (const m of visual.matchAll(SLOT_TOKEN_RE)) if (!out.includes(m[1])) out.push(m[1])
90
+ return out
91
+ }
92
+
93
+ export function rewriteSlotTokens(visual: string, renames: Record<string, string>): string {
94
+ return visual.replace(SLOT_TOKEN_RE, (whole, id: string) => (renames[id] ? `{slot:${renames[id]}}` : whole))
95
+ }
96
+
97
+ /** Unresolved tokens unwrap to their literal id text — never deleted (spec invariant). */
98
+ export function unwrapUnresolvedTokens(text: string, validIds: Set<string>): { text: string; unresolved: string[] } {
99
+ const unresolved: string[] = []
100
+ const out = text.replace(SLOT_TOKEN_RE, (whole, id: string) => {
101
+ if (validIds.has(id)) return whole
102
+ if (!unresolved.includes(id)) unresolved.push(id)
103
+ return id
104
+ })
105
+ return { text: out, unresolved }
106
+ }
107
+
108
+ /** Substitute {slot:x}: castMap binding wins, else the slot's description, else literal id. */
109
+ export function renderAnalyzedScene(scene: { visual: string }, slots: EntitySlot[], castMap?: Record<string, string>): string {
110
+ const byId = new Map(slots.map((s) => [s.slotId, s]))
111
+ return scene.visual.replace(SLOT_TOKEN_RE, (_whole, id: string) => castMap?.[id] ?? byId.get(id)?.description ?? id)
112
+ }
113
+
114
+ export function isOversizedScene(startSec: number, endSec: number): boolean {
115
+ return endSec - startSec > VIDEO_ANALYSIS_MAX_SCENE_SEC
116
+ }
117
+
118
+ const STANDARD_RATIOS: Array<[string, number]> = [
119
+ ["16:9", 16 / 9], ["9:16", 9 / 16], ["1:1", 1], ["4:3", 4 / 3], ["3:4", 3 / 4], ["21:9", 21 / 9],
120
+ ]
121
+ export function aspectRatioFromDims(w: number, h: number): string {
122
+ const r = w / h
123
+ for (const [label, v] of STANDARD_RATIOS) if (Math.abs(r - v) / v < 0.03) return label
124
+ const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b))
125
+ const g = gcd(Math.round(w), Math.round(h))
126
+ return `${Math.round(w) / g}:${Math.round(h) / g}`
127
+ }
@@ -0,0 +1,25 @@
1
+ /** Read a video node's duration (in seconds) from its data shape.
2
+ * Tries `generatedResults[activeResultIndex].duration` (post-execution
3
+ * metadata) first, then `data.duration` (configured at design time on
4
+ * generate-video / image-to-video / text-to-video nodes). Returns
5
+ * undefined when neither is set — callers should fall back to a sensible
6
+ * default (e.g. 8s).
7
+ *
8
+ * Lives in `@nodaro/shared` so frontend (store walks) and backend
9
+ * (orchestrator input-resolver) can use one source of truth. */
10
+ export function extractVideoDurationFromNode(
11
+ data: Record<string, unknown> | undefined,
12
+ ): number | undefined {
13
+ if (!data) return undefined
14
+ const results = data.generatedResults as Array<{ duration?: number }> | undefined
15
+ const idx = (data.activeResultIndex as number | undefined) ?? 0
16
+ const fromResult = results?.[idx]?.duration
17
+ if (typeof fromResult === "number" && Number.isFinite(fromResult) && fromResult > 0) {
18
+ return fromResult
19
+ }
20
+ const fromConfig = data.duration
21
+ if (typeof fromConfig === "number" && Number.isFinite(fromConfig) && fromConfig > 0) {
22
+ return fromConfig
23
+ }
24
+ return undefined
25
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ElevenLabs speech-to-speech (voice changer) models — single source of truth
3
+ * for the Voice Changer node's model picker (frontend config panel) AND the
4
+ * `/v1/voice-changer` route Zod enum (backend). Lifting the list here keeps the
5
+ * dropdown and the validation enum from ever drifting apart.
6
+ *
7
+ * These are the two `model_id` values ElevenLabs' `/v1/speech-to-speech/{voiceId}`
8
+ * endpoint accepts with `can_do_voice_conversion`. ElevenLabs' own docs
9
+ * recommend Multilingual v2 even for English source audio (it often
10
+ * outperforms the English-only model), and it's required for non-English
11
+ * audio (e.g. Hebrew), so it's the default — English v2 stays selectable.
12
+ */
13
+ export const VOICE_CHANGER_MODELS = [
14
+ {
15
+ value: "eleven_multilingual_sts_v2",
16
+ label: "Multilingual v2",
17
+ desc: "29 languages; preserves emotion, cadence & timing. Default — ElevenLabs recommends this even for English audio.",
18
+ },
19
+ {
20
+ value: "eleven_english_sts_v2",
21
+ label: "English v2",
22
+ desc: "English-optimized speech-to-speech.",
23
+ },
24
+ ] as const
25
+
26
+ export type VoiceChangerModel = (typeof VOICE_CHANGER_MODELS)[number]["value"]
27
+
28
+ /** Plain id list for the route's `z.enum(...)`. */
29
+ export const VOICE_CHANGER_MODEL_IDS: VoiceChangerModel[] = VOICE_CHANGER_MODELS.map(
30
+ (m) => m.value,
31
+ )
32
+
33
+ /** ElevenLabs recommends Multilingual v2 even for English source audio (it
34
+ * often outperforms the English-only model), and it's required for
35
+ * non-English audio — so it's the default. English v2 remains selectable. */
36
+ export const DEFAULT_VOICE_CHANGER_MODEL: VoiceChangerModel = "eleven_multilingual_sts_v2"
@@ -0,0 +1,37 @@
1
+ import { z } from "zod"
2
+
3
+ /**
4
+ * Voice Matcher (LLM spec §7.1) picks an ElevenLabs voice per dialogue-bearing
5
+ * cast member. Two variants discriminated by `voice_source`:
6
+ * - 'premade' → uses a catalog voice_id
7
+ * - 'custom' → emits a prompt for ElevenLabs voice_design (used when no
8
+ * premade fits)
9
+ *
10
+ * **Schema shape note:** this was originally `z.discriminatedUnion(...)` but
11
+ * Anthropic's tool input_schema validator rejects union-at-root JSON Schemas —
12
+ * it requires `"type": "object"` at the top level (`tools.0.custom.input_schema.type:
13
+ * Field required`). Flattening to a single object with both optional fields +
14
+ * a `.refine()` produces a valid schema (root is an object) while still
15
+ * enforcing the per-variant required-field rule at parse time. `callLLM`'s
16
+ * schema.safeParse triggers a retry-with-feedback if the LLM emits an invalid
17
+ * combination (e.g., 'premade' without voice_id), so the LLM converges on
18
+ * correct output.
19
+ */
20
+ export const VoiceMatchSchema = z
21
+ .object({
22
+ voice_source: z.enum(["premade", "custom"]),
23
+ voice_id: z.string().min(1).optional(),
24
+ voice_design_prompt: z.string().max(500).optional(),
25
+ reasoning: z.string().max(500),
26
+ })
27
+ .refine(
28
+ (v) =>
29
+ v.voice_source === "premade"
30
+ ? typeof v.voice_id === "string" && v.voice_id.length > 0
31
+ : typeof v.voice_design_prompt === "string" && v.voice_design_prompt.length > 0,
32
+ {
33
+ message:
34
+ "voice_id is required when voice_source='premade'; voice_design_prompt is required when voice_source='custom'",
35
+ },
36
+ )
37
+ export type VoiceMatch = z.infer<typeof VoiceMatchSchema>
package/src/voices.ts ADDED
@@ -0,0 +1,92 @@
1
+ import type { TtsProvider } from "./model-constants.js"
2
+
3
+ /**
4
+ * A premade ElevenLabs voice as returned by `GET /v1/voices`.
5
+ * Field names + types mirror the route verbatim: the route normalizes every
6
+ * optional field to `""` (never null), so these are all non-null strings.
7
+ */
8
+ export interface Voice {
9
+ voice_id: string
10
+ name: string
11
+ preview_url: string
12
+ gender: string
13
+ accent: string
14
+ age: string
15
+ description: string
16
+ use_case: string
17
+ category: string
18
+ }
19
+
20
+ /**
21
+ * A shared/community Voice Library entry (`GET /v1/voices/library`).
22
+ * Same shape as {@link Voice} plus the TTS provider the voice is verified on.
23
+ */
24
+ export interface SharedVoice extends Voice {
25
+ /**
26
+ * The best of our TTS providers whose underlying ElevenLabs model the
27
+ * voice lists in `verified_languages` (v3 preferred when verified — it's
28
+ * the fully-multilingual default and renders any voice unmodified —
29
+ * else the cheapest v2 model: turbo preferred, else multilingual).
30
+ * Clients without a provider picker should send it as the `provider` on
31
+ * text-to-speech so generation uses a model the voice is actually verified
32
+ * for — rendering a voice on an unverified model is what makes output drift
33
+ * audibly from its preview. Absent when the entry has no model metadata.
34
+ */
35
+ recommendedProvider?: TtsProvider
36
+ /**
37
+ * Every TTS provider the voice is verified on (subset of `elevenlabs-v3` /
38
+ * `elevenlabs-turbo` / `elevenlabs-multilingual`, v3 first when present,
39
+ * then turbo). Clients WITH a provider picker should only snap the
40
+ * provider when the current choice is NOT in this set — most voices verify
41
+ * more than one, and an explicit user choice within the set must win.
42
+ */
43
+ verifiedProviders?: TtsProvider[]
44
+ }
45
+
46
+ /** Query params for `GET /v1/voices/library`. All optional; sent as a querystring. */
47
+ export interface VoiceLibraryParams {
48
+ search?: string
49
+ gender?: string
50
+ age?: string
51
+ accent?: string
52
+ language?: string
53
+ category?: string
54
+ use_cases?: string
55
+ descriptives?: string
56
+ featured?: boolean
57
+ /** e.g. "trending" — passed through to the route. */
58
+ sort?: string
59
+ /** 0-based page; route default 0. */
60
+ page?: number
61
+ /** Route default 30, clamped to 1..100. */
62
+ page_size?: number
63
+ }
64
+
65
+ /** `GET /v1/voices/library` response. */
66
+ export interface VoiceLibraryResponse {
67
+ voices: SharedVoice[]
68
+ hasMore: boolean
69
+ }
70
+
71
+ /**
72
+ * A user voice clone. `GET /v1/voice-clones` returns the full shape; the create
73
+ * response (`POST /v1/voice-clones/from-url`) returns only `id, name,
74
+ * elevenlabsVoiceId, sampleAudioUrl, createdAt` (+ a credit-tracking `jobId`),
75
+ * so the list-only fields are optional here. `elevenlabsVoiceId` is the id that
76
+ * resolves the clone at text-to-speech time (use it as the selected voiceId).
77
+ */
78
+ export interface VoiceClone {
79
+ id: string
80
+ name: string
81
+ elevenlabsVoiceId: string
82
+ sampleAudioUrl: string
83
+ createdAt: string
84
+ /** Present on the list response; absent on the create response. */
85
+ description?: string | null
86
+ previewUrl?: string | null
87
+ gender?: string | null
88
+ accent?: string | null
89
+ updatedAt?: string
90
+ /** Only on the create response (credit-tracking); never on the list. */
91
+ jobId?: string
92
+ }
package/src/weapons.ts ADDED
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Canonical catalog of weapon presets for the Object entity node.
3
+ *
4
+ * When the Object category is "weapon", users pick a specific weapon from
5
+ * this catalog. The picker auto-fills the object's `objectName` and
6
+ * `description` so the existing `buildObjectPrompt` pipeline just works —
7
+ * no backend prompt changes needed.
8
+ *
9
+ * Organized by weapon family: swords, daggers, axes, polearms, bows,
10
+ * blunt/impact, throwing, modern firearms, historical firearms, explosives
11
+ * & siege, sci-fi, and fantasy.
12
+ */
13
+
14
+ export type WeaponSubcategory =
15
+ | "swords"
16
+ | "daggers"
17
+ | "axes"
18
+ | "polearms"
19
+ | "bows"
20
+ | "blunt"
21
+ | "throwing"
22
+ | "firearms-modern"
23
+ | "firearms-historical"
24
+ | "explosives-siege"
25
+ | "sci-fi"
26
+ | "fantasy"
27
+
28
+ export interface Weapon {
29
+ readonly id: string
30
+ readonly label: string
31
+ readonly subcategory: WeaponSubcategory
32
+ readonly description: string
33
+ }
34
+
35
+ export const WEAPONS: ReadonlyArray<Weapon> = [
36
+ // -------------------- Swords --------------------
37
+ { id: "katana", label: "Katana", subcategory: "swords", description: "Japanese katana with a single-edged gently curved blade, wrapped rayskin handle, disc-shaped tsuba guard and a polished mirror finish" },
38
+ { id: "longsword", label: "Longsword", subcategory: "swords", description: "Medieval double-edged longsword with a straight tapering blade, cross guard, leather-wrapped grip and a round pommel" },
39
+ { id: "broadsword", label: "Broadsword", subcategory: "swords", description: "Heavy broadsword with a wide straight double-edged blade, basket hilt guard and a sturdy leather-wrapped grip" },
40
+ { id: "rapier", label: "Rapier", subcategory: "swords", description: "Slim rapier with a long narrow thrusting blade, ornate swept-hilt basket guard and a spherical pommel" },
41
+ { id: "saber", label: "Saber", subcategory: "swords", description: "Cavalry saber with a single-edged curved blade, brass knuckle-bow guard and a ribbed leather grip" },
42
+ { id: "scimitar", label: "Scimitar", subcategory: "swords", description: "Curved scimitar with a wide single-edged blade, ornate crossguard and a rounded metal pommel" },
43
+ { id: "claymore", label: "Claymore", subcategory: "swords", description: "Massive two-handed Scottish claymore with a long straight blade, forward-angled crossguard and a large leather-bound grip" },
44
+ { id: "zweihander", label: "Zweihänder", subcategory: "swords", description: "Two-handed German Renaissance greatsword with an enormous straight double-edged blade, leather-wrapped ricasso above the crossguard, parrying lugs partway up the blade and a long extended grip ending in a heavy pear-shaped pommel" },
45
+ { id: "cutlass", label: "Cutlass", subcategory: "swords", description: "Pirate cutlass with a short curved single-edged blade, brass cup-shaped hand guard and a weathered wooden grip" },
46
+ { id: "wakizashi", label: "Wakizashi", subcategory: "swords", description: "Short Japanese wakizashi companion blade with a gently curved edge, small tsuba and rayskin-wrapped handle" },
47
+ { id: "falchion", label: "Falchion", subcategory: "swords", description: "Heavy single-edged falchion with a cleaver-like tapering blade, simple crossguard and a riveted leather grip" },
48
+
49
+ // -------------------- Daggers & Knives --------------------
50
+ { id: "dagger", label: "Dagger", subcategory: "daggers", description: "Classic double-edged dagger with a narrow pointed blade, crossguard and a wrapped leather grip" },
51
+ { id: "bowie-knife", label: "Bowie Knife", subcategory: "daggers", description: "Large bowie knife with a clip-point blade, brass guard, stacked leather-washer handle and a crossguard" },
52
+ { id: "kukri", label: "Kukri", subcategory: "daggers", description: "Nepalese kukri with a forward-curving broad blade, wooden handle and distinctive inwardly-angled recurve" },
53
+ { id: "stiletto", label: "Stiletto", subcategory: "daggers", description: "Slim stiletto with a long needle-thin triangular blade, minimal crossguard and a tapered handle" },
54
+ { id: "dirk", label: "Dirk", subcategory: "daggers", description: "Scottish dirk with a long straight single-edged blade, interwoven Celtic-knot handle and an ornate pommel" },
55
+ { id: "tanto", label: "Tanto", subcategory: "daggers", description: "Japanese tanto dagger with an angular chisel-point tip, small tsuba and wrapped rayskin handle" },
56
+ { id: "switchblade", label: "Switchblade", subcategory: "daggers", description: "Pocket switchblade with a spring-loaded folding blade, pearl or resin side panels and a polished release button" },
57
+ { id: "trench-knife", label: "Trench Knife", subcategory: "daggers", description: "Military trench knife with a slim double-edged blade and a brass knuckle-duster handguard wrapping the grip" },
58
+
59
+ // -------------------- Axes --------------------
60
+ { id: "battle-axe", label: "Battle Axe", subcategory: "axes", description: "Heavy two-handed battle axe with a wide curved cutting edge, bearded profile and a long wooden haft bound with iron bands" },
61
+ { id: "tomahawk", label: "Tomahawk", subcategory: "axes", description: "Light throwing tomahawk with a small single-bit iron head, straight wooden haft and leather wrapping near the grip" },
62
+ { id: "hatchet", label: "Hatchet", subcategory: "axes", description: "Compact hatchet with a short wooden handle, small single-bit steel head and a hammered finish" },
63
+ { id: "halberd", label: "Halberd", subcategory: "axes", description: "Long-pole halberd combining an axe blade, a thrusting spear point and a rear hook atop a tall wooden shaft" },
64
+ { id: "greataxe", label: "Greataxe", subcategory: "axes", description: "Massive greataxe with a huge double-sided crescent head, iron reinforcing bands and a long heavy haft requiring two hands" },
65
+ { id: "bearded-axe", label: "Bearded Axe", subcategory: "axes", description: "Viking bearded axe with an elongated lower blade edge, narrow iron head and a tall wooden haft wrapped with leather" },
66
+
67
+ // -------------------- Polearms --------------------
68
+ { id: "spear", label: "Spear", subcategory: "polearms", description: "Simple spear with a leaf-shaped iron spearhead lashed to a tall straight wooden shaft and a small butt cap at the base" },
69
+ { id: "lance", label: "Lance", subcategory: "polearms", description: "Jousting lance with a long wooden shaft, conical steel tip and a flared hand guard protecting the grip" },
70
+ { id: "pike", label: "Pike", subcategory: "polearms", description: "Very long pike with a small triangular spearhead mounted atop a towering wooden pole twice the height of a man" },
71
+ { id: "glaive", label: "Glaive", subcategory: "polearms", description: "Glaive polearm with a long curved single-edged blade mounted on a wooden shaft, tapering to a small crossguard" },
72
+ { id: "trident", label: "Trident", subcategory: "polearms", description: "Three-pronged trident with sharp barbed tines, a central shaft and a long wooden pole" },
73
+ { id: "naginata", label: "Naginata", subcategory: "polearms", description: "Japanese naginata with a curved single-edged blade mounted on a long lacquered wooden pole with silk wrappings" },
74
+
75
+ // -------------------- Bows & Crossbows --------------------
76
+ { id: "longbow", label: "Longbow", subcategory: "bows", description: "Tall English longbow with a single piece of yew wood, waxed linen string and a leather-wrapped grip" },
77
+ { id: "recurve-bow", label: "Recurve Bow", subcategory: "bows", description: "Traditional recurve bow with limbs that curve away from the archer, a leather-wrapped riser and a taut bowstring" },
78
+ { id: "compound-bow", label: "Compound Bow", subcategory: "bows", description: "Modern compound bow with aluminum cams, pulley wheels at each tip, carbon-fiber arrow rest and a sighting pin array" },
79
+ { id: "crossbow", label: "Crossbow", subcategory: "bows", description: "Medieval crossbow with a horizontal wooden stock, steel prod, taut string and a trigger mechanism under the rail" },
80
+ { id: "short-bow", label: "Short Bow", subcategory: "bows", description: "Compact wooden short bow with a simple curved profile, a waxed string and a leather grip in the middle" },
81
+
82
+ // -------------------- Blunt & Impact --------------------
83
+ { id: "mace", label: "Mace", subcategory: "blunt", description: "Medieval flanged mace with a heavy crowned head bearing protruding iron flanges atop a short iron shaft" },
84
+ { id: "war-hammer", label: "War Hammer", subcategory: "blunt", description: "Long-handled war hammer with a heavy iron head featuring a flat striking face on one side and a curved spike on the other" },
85
+ { id: "club", label: "Club", subcategory: "blunt", description: "Simple wooden club with a thick knotted head, tapering shaft and a well-worn leather grip near the base" },
86
+ { id: "morning-star", label: "Morning Star", subcategory: "blunt", description: "Morning star with a wooden shaft topped by a large iron ball bristling with tall spikes in every direction" },
87
+ { id: "flail", label: "Flail", subcategory: "blunt", description: "Military flail with a spiked iron ball connected by a short chain to a wooden haft with an iron end cap" },
88
+ { id: "nunchaku", label: "Nunchaku", subcategory: "blunt", description: "Martial-arts nunchaku with two polished wooden batons connected by a short length of braided cord or chain" },
89
+
90
+ // -------------------- Throwing --------------------
91
+ { id: "shuriken", label: "Shuriken", subcategory: "throwing", description: "Metal throwing star with multiple razor-sharp points radiating from a central hub and a blackened steel finish" },
92
+ { id: "throwing-knife", label: "Throwing Knife", subcategory: "throwing", description: "Balanced throwing knife with a leaf-shaped double-edged blade, minimal handle and a polished steel finish" },
93
+ { id: "boomerang", label: "Boomerang", subcategory: "throwing", description: "Curved wooden boomerang with an elbow bend, painted tribal patterns and a smooth aerodynamic profile" },
94
+ { id: "javelin", label: "Javelin", subcategory: "throwing", description: "Lightweight throwing javelin with a slim steel tip, tapered wooden shaft and a leather grip wrap near the balance point" },
95
+ { id: "bolas", label: "Bolas", subcategory: "throwing", description: "Three weighted stone or iron balls tied together by braided leather cords meeting at a central knot" },
96
+ { id: "slingshot", label: "Slingshot", subcategory: "throwing", description: "Y-shaped wooden slingshot with a forked handle whittled from a tree branch, two thick rubber elastic bands stretched between the prongs and a small leather pouch in the middle for cradling stone projectiles" },
97
+ { id: "blowgun", label: "Blowgun", subcategory: "throwing", description: "Long hollow blowpipe traditionally used by indigenous hunters, fashioned from a single piece of bamboo or carved hardwood with a small mouthpiece at one end and slender feather-fletched darts loaded for breath-launched shots" },
98
+
99
+ // -------------------- Modern Firearms --------------------
100
+ { id: "pistol", label: "Pistol", subcategory: "firearms-modern", description: "Modern semi-automatic pistol with a matte black polymer frame, ribbed slide, trigger guard and a flush magazine base" },
101
+ { id: "revolver", label: "Revolver", subcategory: "firearms-modern", description: "Six-shooter revolver with a rotating cylinder, long barrel, hammer cocked back and a checkered wooden grip" },
102
+ { id: "assault-rifle", label: "Assault Rifle", subcategory: "firearms-modern", description: "Military assault rifle with a long barrel, collapsible stock, optical sight on the rail and a curved detachable magazine" },
103
+ { id: "shotgun", label: "Shotgun", subcategory: "firearms-modern", description: "Pump-action shotgun with a wide bore barrel, tactical fore-end, tube magazine underneath and a wooden or synthetic stock" },
104
+ { id: "smg", label: "Submachine Gun", subcategory: "firearms-modern", description: "Compact submachine gun with a short barrel, side-mounted magazine, folding wire stock and an integral foregrip" },
105
+ { id: "sniper-rifle", label: "Sniper Rifle", subcategory: "firearms-modern", description: "Bolt-action sniper rifle with a long barrel, high-magnification scope, bipod legs and an ergonomic polymer stock" },
106
+ { id: "machine-gun", label: "Machine Gun", subcategory: "firearms-modern", description: "Heavy belt-fed machine gun with a long finned barrel, bipod, carry handle and an ammunition belt feeding from the side" },
107
+ { id: "service-pistol", label: "Service Pistol", subcategory: "firearms-modern", description: "Modern semi-automatic service sidearm in the Glock or Beretta style with a matte black polymer frame, ribbed steel slide, accessory rail under the dust cover, three-dot iron sights and a flush double-stack magazine" },
108
+ { id: "hunting-rifle", label: "Hunting Rifle", subcategory: "firearms-modern", description: "Bolt-action hunting rifle with a polished walnut wood stock, blued steel barrel, mounted variable-zoom scope on the receiver rail, leather sling and a checkered grip and forend for steady offhand shooting" },
109
+
110
+ // -------------------- Historical Firearms --------------------
111
+ { id: "musket", label: "Musket", subcategory: "firearms-historical", description: "Long flintlock musket with a smooth-bore iron barrel, walnut stock, brass fittings and a bayonet fixed near the muzzle" },
112
+ { id: "flintlock-pistol", label: "Flintlock Pistol", subcategory: "firearms-historical", description: "Ornate flintlock pistol with a curved wooden grip, engraved brass fittings, a flint hammer and a single long barrel" },
113
+ { id: "blunderbuss", label: "Blunderbuss", subcategory: "firearms-historical", description: "Short flintlock blunderbuss with a flared muzzle, brass fittings on a stout wooden stock and a pirate-era presence" },
114
+ { id: "dueling-pistol", label: "Dueling Pistol", subcategory: "firearms-historical", description: "Elegant dueling pistol with a slim octagonal barrel, finely engraved lockwork and a polished walnut grip" },
115
+
116
+ // -------------------- Explosives & Siege --------------------
117
+ { id: "grenade", label: "Grenade", subcategory: "explosives-siege", description: "Pineapple-textured iron fragmentation grenade with a spoon lever held in place by a pulled safety pin" },
118
+ { id: "stick-grenade", label: "Stick Grenade", subcategory: "explosives-siege", description: "Cylindrical stick grenade with an iron warhead mounted atop a long wooden handle and a pull-string fuse at the base" },
119
+ { id: "dynamite", label: "Dynamite", subcategory: "explosives-siege", description: "Bundled sticks of red dynamite wrapped with twine and joined to a long sputtering fuse with a burning tip" },
120
+ { id: "bomb", label: "Cartoon Bomb", subcategory: "explosives-siege", description: "Round black cartoon bomb with a smoking curled fuse coming out of the top and a shiny spherical iron shell" },
121
+ { id: "rocket-launcher", label: "Rocket Launcher", subcategory: "explosives-siege", description: "Shoulder-fired rocket launcher with a long tube, forward grip, rear exhaust cone and optical targeting sight" },
122
+ { id: "cannon", label: "Cannon", subcategory: "explosives-siege", description: "Cast-iron muzzle-loading cannon mounted on a wooden wheeled carriage with a long smooth-bore barrel and a smoking vent" },
123
+ { id: "catapult", label: "Catapult", subcategory: "explosives-siege", description: "Wooden siege catapult with a long throwing arm cocked back, a counterweight or torsion bundle and a basket loaded with stone" },
124
+ { id: "trebuchet", label: "Trebuchet", subcategory: "explosives-siege", description: "Tall medieval trebuchet with a massive counterweight, long throwing arm, braided sling and heavy timber frame" },
125
+
126
+ // -------------------- Sci-Fi --------------------
127
+ { id: "laser-pistol", label: "Laser Pistol", subcategory: "sci-fi", description: "Compact sci-fi laser pistol with glowing neon energy coils, a ridged metal body and a short emitter barrel" },
128
+ { id: "plasma-rifle", label: "Plasma Rifle", subcategory: "sci-fi", description: "Futuristic plasma rifle with glowing blue energy cells, ventilated barrel shrouds and a holographic sight" },
129
+ { id: "lightsaber", label: "Lightsaber", subcategory: "sci-fi", description: "Laser sword with a metallic ribbed handle emitting a tall glowing blade of saturated energy with a hazy plasma halo" },
130
+ { id: "blaster", label: "Blaster", subcategory: "sci-fi", description: "Retro-futuristic blaster pistol with a chunky body, glowing energy chamber, cooling vents and a scope mounted on top" },
131
+ { id: "phaser", label: "Phaser", subcategory: "sci-fi", description: "Sleek sci-fi phaser with a minimalist curved grip, glowing emitter tip and a smooth panel controlling intensity" },
132
+ { id: "rail-gun", label: "Rail Gun", subcategory: "sci-fi", description: "Heavy electromagnetic rail gun with parallel metal rails, massive capacitors along the body and a glowing projectile chamber" },
133
+ { id: "emp-grenade", label: "EMP Grenade", subcategory: "sci-fi", description: "Spherical electromagnetic pulse grenade with exposed coils, glowing blue indicator lights and a holographic arming dial" },
134
+ { id: "plasma-sword", label: "Plasma Sword / Lightsaber", subcategory: "sci-fi", description: "Sci-fi energy blade with a metallic ridged hilt projecting a tall column of saturated plasma energy, surrounded by a bright halo of glowing light, casting colored reflections on the wielder and humming softly with contained power" },
135
+ { id: "gravity-gun", label: "Gravity Gun", subcategory: "sci-fi", description: "Sci-fi physics-manipulating ranged weapon with a chunky metallic body, three articulated prongs at the muzzle that crackle with blue gravitational energy, exposed power conduits along the barrel and a heavy two-handed grip with a glowing trigger assembly" },
136
+
137
+ // -------------------- Fantasy / Magical --------------------
138
+ { id: "enchanted-sword", label: "Enchanted Sword", subcategory: "fantasy", description: "Enchanted sword with a glowing rune-etched blade, gold-inlaid crossguard and a gemstone embedded in the pommel" },
139
+ { id: "magic-staff", label: "Magic Staff", subcategory: "fantasy", description: "Tall gnarled wizard's staff with a twisted wooden shaft ending in a crown of branches holding a glowing crystal" },
140
+ { id: "runed-dagger", label: "Runed Dagger", subcategory: "fantasy", description: "Mystical dagger with a blade inscribed in glowing runes, a bone handle and dark energy swirling along the edge" },
141
+ { id: "wizard-wand", label: "Wizard Wand", subcategory: "fantasy", description: "Slender wooden wand with knurled swirls, a leather grip and tiny sparks of magic leaking from the pointed tip" },
142
+ { id: "war-horn", label: "War Horn", subcategory: "fantasy", description: "Massive curved war horn bound in leather and silver bands with a mouthpiece at one end and a flared bellowing opening at the other" },
143
+ { id: "sorcerer-orb", label: "Sorcerer's Orb", subcategory: "fantasy", description: "Crystal sorcerer's orb held in a twisted silver claw-stand with swirling arcane mist suspended inside the glass sphere" },
144
+ ] as const
145
+
146
+ const weaponById = new Map<string, Weapon>(WEAPONS.map((w) => [w.id, w]))
147
+
148
+ export function getWeapon(id: string | undefined | null): Weapon | undefined {
149
+ if (!id) return undefined
150
+ return weaponById.get(id)
151
+ }
152
+
153
+ export function getWeaponLabel(id: string | undefined | null, fallback?: string): string {
154
+ const w = getWeapon(id)
155
+ if (w) return w.label
156
+ if (fallback !== undefined) return fallback
157
+ return (id ?? "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
158
+ }
159
+
160
+ export const WEAPON_IDS: ReadonlyArray<string> = WEAPONS.map((w) => w.id)
161
+
162
+ export const WEAPON_SUBCATEGORY_LABELS: Readonly<Record<WeaponSubcategory, string>> = {
163
+ swords: "Swords",
164
+ daggers: "Daggers & Knives",
165
+ axes: "Axes",
166
+ polearms: "Polearms",
167
+ bows: "Bows & Crossbows",
168
+ blunt: "Blunt & Impact",
169
+ throwing: "Throwing",
170
+ "firearms-modern": "Firearms (Modern)",
171
+ "firearms-historical": "Firearms (Historical)",
172
+ "explosives-siege": "Explosives & Siege",
173
+ "sci-fi": "Sci-Fi",
174
+ fantasy: "Fantasy",
175
+ }
176
+
177
+ export const WEAPON_SUBCATEGORY_ORDER: ReadonlyArray<WeaponSubcategory> = [
178
+ "swords",
179
+ "daggers",
180
+ "axes",
181
+ "polearms",
182
+ "bows",
183
+ "blunt",
184
+ "throwing",
185
+ "firearms-modern",
186
+ "firearms-historical",
187
+ "explosives-siege",
188
+ "sci-fi",
189
+ "fantasy",
190
+ ]