videoclaw 3.0.0-alpha.1

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 (1452) hide show
  1. package/AGENTS.md +165 -0
  2. package/CLAUDE.md +232 -0
  3. package/LICENSE +36 -0
  4. package/README.md +737 -0
  5. package/dist/cli/args.d.ts +59 -0
  6. package/dist/cli/args.d.ts.map +1 -0
  7. package/dist/cli/args.js +279 -0
  8. package/dist/cli/args.js.map +1 -0
  9. package/dist/cli/handlers/analysis.d.ts +3 -0
  10. package/dist/cli/handlers/analysis.d.ts.map +1 -0
  11. package/dist/cli/handlers/analysis.js +111 -0
  12. package/dist/cli/handlers/analysis.js.map +1 -0
  13. package/dist/cli/handlers/audio.d.ts +25 -0
  14. package/dist/cli/handlers/audio.d.ts.map +1 -0
  15. package/dist/cli/handlers/audio.js +321 -0
  16. package/dist/cli/handlers/audio.js.map +1 -0
  17. package/dist/cli/handlers/batch.d.ts +4 -0
  18. package/dist/cli/handlers/batch.d.ts.map +1 -0
  19. package/dist/cli/handlers/batch.js +424 -0
  20. package/dist/cli/handlers/batch.js.map +1 -0
  21. package/dist/cli/handlers/candidates.d.ts +10 -0
  22. package/dist/cli/handlers/candidates.d.ts.map +1 -0
  23. package/dist/cli/handlers/candidates.js +389 -0
  24. package/dist/cli/handlers/candidates.js.map +1 -0
  25. package/dist/cli/handlers/character.d.ts +8 -0
  26. package/dist/cli/handlers/character.d.ts.map +1 -0
  27. package/dist/cli/handlers/character.js +202 -0
  28. package/dist/cli/handlers/character.js.map +1 -0
  29. package/dist/cli/handlers/clone.d.ts +5 -0
  30. package/dist/cli/handlers/clone.d.ts.map +1 -0
  31. package/dist/cli/handlers/clone.js +303 -0
  32. package/dist/cli/handlers/clone.js.map +1 -0
  33. package/dist/cli/handlers/create.d.ts +5 -0
  34. package/dist/cli/handlers/create.d.ts.map +1 -0
  35. package/dist/cli/handlers/create.js +635 -0
  36. package/dist/cli/handlers/create.js.map +1 -0
  37. package/dist/cli/handlers/execution.d.ts +5 -0
  38. package/dist/cli/handlers/execution.d.ts.map +1 -0
  39. package/dist/cli/handlers/execution.js +132 -0
  40. package/dist/cli/handlers/execution.js.map +1 -0
  41. package/dist/cli/handlers/library.d.ts +8 -0
  42. package/dist/cli/handlers/library.d.ts.map +1 -0
  43. package/dist/cli/handlers/library.js +77 -0
  44. package/dist/cli/handlers/library.js.map +1 -0
  45. package/dist/cli/handlers/media-ops.d.ts +7 -0
  46. package/dist/cli/handlers/media-ops.d.ts.map +1 -0
  47. package/dist/cli/handlers/media-ops.js +120 -0
  48. package/dist/cli/handlers/media-ops.js.map +1 -0
  49. package/dist/cli/handlers/media-production.d.ts +56 -0
  50. package/dist/cli/handlers/media-production.d.ts.map +1 -0
  51. package/dist/cli/handlers/media-production.js +673 -0
  52. package/dist/cli/handlers/media-production.js.map +1 -0
  53. package/dist/cli/handlers/motion-overlay.d.ts +10 -0
  54. package/dist/cli/handlers/motion-overlay.d.ts.map +1 -0
  55. package/dist/cli/handlers/motion-overlay.js +651 -0
  56. package/dist/cli/handlers/motion-overlay.js.map +1 -0
  57. package/dist/cli/handlers/multi-shot.d.ts +2 -0
  58. package/dist/cli/handlers/multi-shot.d.ts.map +1 -0
  59. package/dist/cli/handlers/multi-shot.js +424 -0
  60. package/dist/cli/handlers/multi-shot.js.map +1 -0
  61. package/dist/cli/handlers/project-ops.d.ts +14 -0
  62. package/dist/cli/handlers/project-ops.d.ts.map +1 -0
  63. package/dist/cli/handlers/project-ops.js +241 -0
  64. package/dist/cli/handlers/project-ops.js.map +1 -0
  65. package/dist/cli/handlers/prompt-craft.d.ts +8 -0
  66. package/dist/cli/handlers/prompt-craft.d.ts.map +1 -0
  67. package/dist/cli/handlers/prompt-craft.js +397 -0
  68. package/dist/cli/handlers/prompt-craft.js.map +1 -0
  69. package/dist/cli/handlers/provider-registration.d.ts +29 -0
  70. package/dist/cli/handlers/provider-registration.d.ts.map +1 -0
  71. package/dist/cli/handlers/provider-registration.js +225 -0
  72. package/dist/cli/handlers/provider-registration.js.map +1 -0
  73. package/dist/cli/handlers/reference-sheets.d.ts +6 -0
  74. package/dist/cli/handlers/reference-sheets.d.ts.map +1 -0
  75. package/dist/cli/handlers/reference-sheets.js +181 -0
  76. package/dist/cli/handlers/reference-sheets.js.map +1 -0
  77. package/dist/cli/handlers/reporting.d.ts +18 -0
  78. package/dist/cli/handlers/reporting.d.ts.map +1 -0
  79. package/dist/cli/handlers/reporting.js +155 -0
  80. package/dist/cli/handlers/reporting.js.map +1 -0
  81. package/dist/cli/handlers/review-portal.d.ts +8 -0
  82. package/dist/cli/handlers/review-portal.d.ts.map +1 -0
  83. package/dist/cli/handlers/review-portal.js +276 -0
  84. package/dist/cli/handlers/review-portal.js.map +1 -0
  85. package/dist/cli/handlers/show.d.ts +11 -0
  86. package/dist/cli/handlers/show.d.ts.map +1 -0
  87. package/dist/cli/handlers/show.js +136 -0
  88. package/dist/cli/handlers/show.js.map +1 -0
  89. package/dist/cli/handlers/stages.d.ts +6 -0
  90. package/dist/cli/handlers/stages.d.ts.map +1 -0
  91. package/dist/cli/handlers/stages.js +333 -0
  92. package/dist/cli/handlers/stages.js.map +1 -0
  93. package/dist/cli/handlers/studio.d.ts +2 -0
  94. package/dist/cli/handlers/studio.d.ts.map +1 -0
  95. package/dist/cli/handlers/studio.js +159 -0
  96. package/dist/cli/handlers/studio.js.map +1 -0
  97. package/dist/cli/handlers/templates.d.ts +7 -0
  98. package/dist/cli/handlers/templates.d.ts.map +1 -0
  99. package/dist/cli/handlers/templates.js +61 -0
  100. package/dist/cli/handlers/templates.js.map +1 -0
  101. package/dist/cli/provider-adapter.d.ts +3 -0
  102. package/dist/cli/provider-adapter.d.ts.map +1 -0
  103. package/dist/cli/provider-adapter.js +96 -0
  104. package/dist/cli/provider-adapter.js.map +1 -0
  105. package/dist/cli/vclaw.d.ts +15 -0
  106. package/dist/cli/vclaw.d.ts.map +1 -0
  107. package/dist/cli/vclaw.js +318 -0
  108. package/dist/cli/vclaw.js.map +1 -0
  109. package/dist/index.d.ts +79 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +68 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/mcp/index.d.ts +3 -0
  114. package/dist/mcp/index.d.ts.map +1 -0
  115. package/dist/mcp/index.js +3 -0
  116. package/dist/mcp/index.js.map +1 -0
  117. package/dist/mcp/server.d.ts +27 -0
  118. package/dist/mcp/server.d.ts.map +1 -0
  119. package/dist/mcp/server.js +168 -0
  120. package/dist/mcp/server.js.map +1 -0
  121. package/dist/mcp/tools.d.ts +55 -0
  122. package/dist/mcp/tools.d.ts.map +1 -0
  123. package/dist/mcp/tools.js +80 -0
  124. package/dist/mcp/tools.js.map +1 -0
  125. package/dist/video/analyze-output.d.ts +5 -0
  126. package/dist/video/analyze-output.d.ts.map +1 -0
  127. package/dist/video/analyze-output.js +7 -0
  128. package/dist/video/analyze-output.js.map +1 -0
  129. package/dist/video/archive-project.d.ts +14 -0
  130. package/dist/video/archive-project.d.ts.map +1 -0
  131. package/dist/video/archive-project.js +41 -0
  132. package/dist/video/archive-project.js.map +1 -0
  133. package/dist/video/artifact-history.d.ts +8 -0
  134. package/dist/video/artifact-history.d.ts.map +1 -0
  135. package/dist/video/artifact-history.js +12 -0
  136. package/dist/video/artifact-history.js.map +1 -0
  137. package/dist/video/artifact-store.d.ts +8 -0
  138. package/dist/video/artifact-store.d.ts.map +1 -0
  139. package/dist/video/artifact-store.js +37 -0
  140. package/dist/video/artifact-store.js.map +1 -0
  141. package/dist/video/artifact-validation.d.ts +15 -0
  142. package/dist/video/artifact-validation.d.ts.map +1 -0
  143. package/dist/video/artifact-validation.js +287 -0
  144. package/dist/video/artifact-validation.js.map +1 -0
  145. package/dist/video/artifacts.d.ts +112 -0
  146. package/dist/video/artifacts.d.ts.map +1 -0
  147. package/dist/video/artifacts.js +31 -0
  148. package/dist/video/artifacts.js.map +1 -0
  149. package/dist/video/assemble/animate-slides.d.ts +108 -0
  150. package/dist/video/assemble/animate-slides.d.ts.map +1 -0
  151. package/dist/video/assemble/animate-slides.js +152 -0
  152. package/dist/video/assemble/animate-slides.js.map +1 -0
  153. package/dist/video/assemble/animation-styles.d.ts +21 -0
  154. package/dist/video/assemble/animation-styles.d.ts.map +1 -0
  155. package/dist/video/assemble/animation-styles.js +32 -0
  156. package/dist/video/assemble/animation-styles.js.map +1 -0
  157. package/dist/video/assemble/animation-styles.json +97 -0
  158. package/dist/video/assemble/assemble.d.ts +24 -0
  159. package/dist/video/assemble/assemble.d.ts.map +1 -0
  160. package/dist/video/assemble/assemble.js +457 -0
  161. package/dist/video/assemble/assemble.js.map +1 -0
  162. package/dist/video/assemble/audio-concat.d.ts +61 -0
  163. package/dist/video/assemble/audio-concat.d.ts.map +1 -0
  164. package/dist/video/assemble/audio-concat.js +108 -0
  165. package/dist/video/assemble/audio-concat.js.map +1 -0
  166. package/dist/video/assemble/audio-mix-plan.d.ts +84 -0
  167. package/dist/video/assemble/audio-mix-plan.d.ts.map +1 -0
  168. package/dist/video/assemble/audio-mix-plan.js +74 -0
  169. package/dist/video/assemble/audio-mix-plan.js.map +1 -0
  170. package/dist/video/assemble/audio-utils.d.ts +32 -0
  171. package/dist/video/assemble/audio-utils.d.ts.map +1 -0
  172. package/dist/video/assemble/audio-utils.js +91 -0
  173. package/dist/video/assemble/audio-utils.js.map +1 -0
  174. package/dist/video/assemble/cut-segment.d.ts +40 -0
  175. package/dist/video/assemble/cut-segment.d.ts.map +1 -0
  176. package/dist/video/assemble/cut-segment.js +77 -0
  177. package/dist/video/assemble/cut-segment.js.map +1 -0
  178. package/dist/video/assemble/ffmpeg.d.ts +91 -0
  179. package/dist/video/assemble/ffmpeg.d.ts.map +1 -0
  180. package/dist/video/assemble/ffmpeg.js +251 -0
  181. package/dist/video/assemble/ffmpeg.js.map +1 -0
  182. package/dist/video/assemble/gemini-vision-classify.d.ts +37 -0
  183. package/dist/video/assemble/gemini-vision-classify.d.ts.map +1 -0
  184. package/dist/video/assemble/gemini-vision-classify.js +123 -0
  185. package/dist/video/assemble/gemini-vision-classify.js.map +1 -0
  186. package/dist/video/assemble/index.d.ts +47 -0
  187. package/dist/video/assemble/index.d.ts.map +1 -0
  188. package/dist/video/assemble/index.js +40 -0
  189. package/dist/video/assemble/index.js.map +1 -0
  190. package/dist/video/assemble/media-qc.d.ts +44 -0
  191. package/dist/video/assemble/media-qc.d.ts.map +1 -0
  192. package/dist/video/assemble/media-qc.js +181 -0
  193. package/dist/video/assemble/media-qc.js.map +1 -0
  194. package/dist/video/assemble/music.d.ts +51 -0
  195. package/dist/video/assemble/music.d.ts.map +1 -0
  196. package/dist/video/assemble/music.js +171 -0
  197. package/dist/video/assemble/music.js.map +1 -0
  198. package/dist/video/assemble/narration-fit.d.ts +32 -0
  199. package/dist/video/assemble/narration-fit.d.ts.map +1 -0
  200. package/dist/video/assemble/narration-fit.js +59 -0
  201. package/dist/video/assemble/narration-fit.js.map +1 -0
  202. package/dist/video/assemble/overlay.d.ts +81 -0
  203. package/dist/video/assemble/overlay.d.ts.map +1 -0
  204. package/dist/video/assemble/overlay.js +141 -0
  205. package/dist/video/assemble/overlay.js.map +1 -0
  206. package/dist/video/assemble/pdf.d.ts +46 -0
  207. package/dist/video/assemble/pdf.d.ts.map +1 -0
  208. package/dist/video/assemble/pdf.js +111 -0
  209. package/dist/video/assemble/pdf.js.map +1 -0
  210. package/dist/video/assemble/qa-dialogue-lint.d.ts +56 -0
  211. package/dist/video/assemble/qa-dialogue-lint.d.ts.map +1 -0
  212. package/dist/video/assemble/qa-dialogue-lint.js +85 -0
  213. package/dist/video/assemble/qa-dialogue-lint.js.map +1 -0
  214. package/dist/video/assemble/qa-image-filter.d.ts +52 -0
  215. package/dist/video/assemble/qa-image-filter.d.ts.map +1 -0
  216. package/dist/video/assemble/qa-image-filter.js +102 -0
  217. package/dist/video/assemble/qa-image-filter.js.map +1 -0
  218. package/dist/video/assemble/qa-image-vision.d.ts +57 -0
  219. package/dist/video/assemble/qa-image-vision.d.ts.map +1 -0
  220. package/dist/video/assemble/qa-image-vision.js +96 -0
  221. package/dist/video/assemble/qa-image-vision.js.map +1 -0
  222. package/dist/video/assemble/qa-narration-vision.d.ts +53 -0
  223. package/dist/video/assemble/qa-narration-vision.d.ts.map +1 -0
  224. package/dist/video/assemble/qa-narration-vision.js +87 -0
  225. package/dist/video/assemble/qa-narration-vision.js.map +1 -0
  226. package/dist/video/assemble/qa-narration.d.ts +53 -0
  227. package/dist/video/assemble/qa-narration.d.ts.map +1 -0
  228. package/dist/video/assemble/qa-narration.js +85 -0
  229. package/dist/video/assemble/qa-narration.js.map +1 -0
  230. package/dist/video/assemble/stitch-ad.d.ts +88 -0
  231. package/dist/video/assemble/stitch-ad.d.ts.map +1 -0
  232. package/dist/video/assemble/stitch-ad.js +161 -0
  233. package/dist/video/assemble/stitch-ad.js.map +1 -0
  234. package/dist/video/assemble/stitch.d.ts +341 -0
  235. package/dist/video/assemble/stitch.d.ts.map +1 -0
  236. package/dist/video/assemble/stitch.js +607 -0
  237. package/dist/video/assemble/stitch.js.map +1 -0
  238. package/dist/video/assemble/text-card.d.ts +78 -0
  239. package/dist/video/assemble/text-card.d.ts.map +1 -0
  240. package/dist/video/assemble/text-card.js +210 -0
  241. package/dist/video/assemble/text-card.js.map +1 -0
  242. package/dist/video/assemble/title-card.d.ts +39 -0
  243. package/dist/video/assemble/title-card.d.ts.map +1 -0
  244. package/dist/video/assemble/title-card.js +127 -0
  245. package/dist/video/assemble/title-card.js.map +1 -0
  246. package/dist/video/assemble/transcript.d.ts +66 -0
  247. package/dist/video/assemble/transcript.d.ts.map +1 -0
  248. package/dist/video/assemble/transcript.js +200 -0
  249. package/dist/video/assemble/transcript.js.map +1 -0
  250. package/dist/video/assemble/tts-elevenlabs.d.ts +52 -0
  251. package/dist/video/assemble/tts-elevenlabs.d.ts.map +1 -0
  252. package/dist/video/assemble/tts-elevenlabs.js +118 -0
  253. package/dist/video/assemble/tts-elevenlabs.js.map +1 -0
  254. package/dist/video/assemble/tts.d.ts +79 -0
  255. package/dist/video/assemble/tts.d.ts.map +1 -0
  256. package/dist/video/assemble/tts.js +131 -0
  257. package/dist/video/assemble/tts.js.map +1 -0
  258. package/dist/video/assemble/types.d.ts +43 -0
  259. package/dist/video/assemble/types.d.ts.map +1 -0
  260. package/dist/video/assemble/types.js +2 -0
  261. package/dist/video/assemble/types.js.map +1 -0
  262. package/dist/video/assemble/upscale.d.ts +43 -0
  263. package/dist/video/assemble/upscale.d.ts.map +1 -0
  264. package/dist/video/assemble/upscale.js +52 -0
  265. package/dist/video/assemble/upscale.js.map +1 -0
  266. package/dist/video/asset-spec.d.ts +15 -0
  267. package/dist/video/asset-spec.d.ts.map +1 -0
  268. package/dist/video/asset-spec.js +43 -0
  269. package/dist/video/asset-spec.js.map +1 -0
  270. package/dist/video/asset-tag-lookup.d.ts +31 -0
  271. package/dist/video/asset-tag-lookup.d.ts.map +1 -0
  272. package/dist/video/asset-tag-lookup.js +45 -0
  273. package/dist/video/asset-tag-lookup.js.map +1 -0
  274. package/dist/video/atomic-write.d.ts +2 -0
  275. package/dist/video/atomic-write.d.ts.map +1 -0
  276. package/dist/video/atomic-write.js +7 -0
  277. package/dist/video/atomic-write.js.map +1 -0
  278. package/dist/video/audio-platform/native-elevenlabs-sfx.d.ts +29 -0
  279. package/dist/video/audio-platform/native-elevenlabs-sfx.d.ts.map +1 -0
  280. package/dist/video/audio-platform/native-elevenlabs-sfx.js +95 -0
  281. package/dist/video/audio-platform/native-elevenlabs-sfx.js.map +1 -0
  282. package/dist/video/audio-platform/native-elevenlabs-tts.d.ts +30 -0
  283. package/dist/video/audio-platform/native-elevenlabs-tts.d.ts.map +1 -0
  284. package/dist/video/audio-platform/native-elevenlabs-tts.js +95 -0
  285. package/dist/video/audio-platform/native-elevenlabs-tts.js.map +1 -0
  286. package/dist/video/audio-platform/native-flowmusic.d.ts +21 -0
  287. package/dist/video/audio-platform/native-flowmusic.d.ts.map +1 -0
  288. package/dist/video/audio-platform/native-flowmusic.js +137 -0
  289. package/dist/video/audio-platform/native-flowmusic.js.map +1 -0
  290. package/dist/video/audio-platform/native-gemini-tts.d.ts +26 -0
  291. package/dist/video/audio-platform/native-gemini-tts.d.ts.map +1 -0
  292. package/dist/video/audio-platform/native-gemini-tts.js +202 -0
  293. package/dist/video/audio-platform/native-gemini-tts.js.map +1 -0
  294. package/dist/video/audio-platform/native-lyria.d.ts +35 -0
  295. package/dist/video/audio-platform/native-lyria.d.ts.map +1 -0
  296. package/dist/video/audio-platform/native-lyria.js +189 -0
  297. package/dist/video/audio-platform/native-lyria.js.map +1 -0
  298. package/dist/video/audio-platform/native-lyria3.d.ts +36 -0
  299. package/dist/video/audio-platform/native-lyria3.d.ts.map +1 -0
  300. package/dist/video/audio-platform/native-lyria3.js +210 -0
  301. package/dist/video/audio-platform/native-lyria3.js.map +1 -0
  302. package/dist/video/audio-platform/registry.d.ts +82 -0
  303. package/dist/video/audio-platform/registry.d.ts.map +1 -0
  304. package/dist/video/audio-platform/registry.js +189 -0
  305. package/dist/video/audio-platform/registry.js.map +1 -0
  306. package/dist/video/audio-platform/suno-backend.d.ts +11 -0
  307. package/dist/video/audio-platform/suno-backend.d.ts.map +1 -0
  308. package/dist/video/audio-platform/suno-backend.js +33 -0
  309. package/dist/video/audio-platform/suno-backend.js.map +1 -0
  310. package/dist/video/audio-platform/types.d.ts +128 -0
  311. package/dist/video/audio-platform/types.d.ts.map +1 -0
  312. package/dist/video/audio-platform/types.js +9 -0
  313. package/dist/video/audio-platform/types.js.map +1 -0
  314. package/dist/video/batch-queue.d.ts +276 -0
  315. package/dist/video/batch-queue.d.ts.map +1 -0
  316. package/dist/video/batch-queue.js +519 -0
  317. package/dist/video/batch-queue.js.map +1 -0
  318. package/dist/video/blueprint-prompt.d.ts +37 -0
  319. package/dist/video/blueprint-prompt.d.ts.map +1 -0
  320. package/dist/video/blueprint-prompt.js +107 -0
  321. package/dist/video/blueprint-prompt.js.map +1 -0
  322. package/dist/video/brand-definition.d.ts +97 -0
  323. package/dist/video/brand-definition.d.ts.map +1 -0
  324. package/dist/video/brand-definition.js +206 -0
  325. package/dist/video/brand-definition.js.map +1 -0
  326. package/dist/video/brand-dna.d.ts +101 -0
  327. package/dist/video/brand-dna.d.ts.map +1 -0
  328. package/dist/video/brand-dna.js +370 -0
  329. package/dist/video/brand-dna.js.map +1 -0
  330. package/dist/video/brand-prompt.d.ts +13 -0
  331. package/dist/video/brand-prompt.d.ts.map +1 -0
  332. package/dist/video/brand-prompt.js +26 -0
  333. package/dist/video/brand-prompt.js.map +1 -0
  334. package/dist/video/candidate-migrate.d.ts +27 -0
  335. package/dist/video/candidate-migrate.d.ts.map +1 -0
  336. package/dist/video/candidate-migrate.js +119 -0
  337. package/dist/video/candidate-migrate.js.map +1 -0
  338. package/dist/video/category-registry.d.ts +38 -0
  339. package/dist/video/category-registry.d.ts.map +1 -0
  340. package/dist/video/category-registry.js +188 -0
  341. package/dist/video/category-registry.js.map +1 -0
  342. package/dist/video/chain-fallback.d.ts +39 -0
  343. package/dist/video/chain-fallback.d.ts.map +1 -0
  344. package/dist/video/chain-fallback.js +43 -0
  345. package/dist/video/chain-fallback.js.map +1 -0
  346. package/dist/video/character-auto-create.d.ts +72 -0
  347. package/dist/video/character-auto-create.d.ts.map +1 -0
  348. package/dist/video/character-auto-create.js +392 -0
  349. package/dist/video/character-auto-create.js.map +1 -0
  350. package/dist/video/character-consistency.d.ts +3 -0
  351. package/dist/video/character-consistency.d.ts.map +1 -0
  352. package/dist/video/character-consistency.js +69 -0
  353. package/dist/video/character-consistency.js.map +1 -0
  354. package/dist/video/characters.d.ts +44 -0
  355. package/dist/video/characters.d.ts.map +1 -0
  356. package/dist/video/characters.js +96 -0
  357. package/dist/video/characters.js.map +1 -0
  358. package/dist/video/checkpoints.d.ts +16 -0
  359. package/dist/video/checkpoints.d.ts.map +1 -0
  360. package/dist/video/checkpoints.js +29 -0
  361. package/dist/video/checkpoints.js.map +1 -0
  362. package/dist/video/cinema-profile.d.ts +57 -0
  363. package/dist/video/cinema-profile.d.ts.map +1 -0
  364. package/dist/video/cinema-profile.js +89 -0
  365. package/dist/video/cinema-profile.js.map +1 -0
  366. package/dist/video/cinematography.d.ts +299 -0
  367. package/dist/video/cinematography.d.ts.map +1 -0
  368. package/dist/video/cinematography.js +914 -0
  369. package/dist/video/cinematography.js.map +1 -0
  370. package/dist/video/cli-output.d.ts +52 -0
  371. package/dist/video/cli-output.d.ts.map +1 -0
  372. package/dist/video/cli-output.js +81 -0
  373. package/dist/video/cli-output.js.map +1 -0
  374. package/dist/video/cli-schema.d.ts +40 -0
  375. package/dist/video/cli-schema.d.ts.map +1 -0
  376. package/dist/video/cli-schema.js +447 -0
  377. package/dist/video/cli-schema.js.map +1 -0
  378. package/dist/video/cost-estimate.d.ts +37 -0
  379. package/dist/video/cost-estimate.d.ts.map +1 -0
  380. package/dist/video/cost-estimate.js +103 -0
  381. package/dist/video/cost-estimate.js.map +1 -0
  382. package/dist/video/csv-export.d.ts +8 -0
  383. package/dist/video/csv-export.d.ts.map +1 -0
  384. package/dist/video/csv-export.js +133 -0
  385. package/dist/video/csv-export.js.map +1 -0
  386. package/dist/video/dependencies.d.ts +23 -0
  387. package/dist/video/dependencies.d.ts.map +1 -0
  388. package/dist/video/dependencies.js +36 -0
  389. package/dist/video/dependencies.js.map +1 -0
  390. package/dist/video/dialogue-fit.d.ts +23 -0
  391. package/dist/video/dialogue-fit.d.ts.map +1 -0
  392. package/dist/video/dialogue-fit.js +38 -0
  393. package/dist/video/dialogue-fit.js.map +1 -0
  394. package/dist/video/dialogue.d.ts +77 -0
  395. package/dist/video/dialogue.d.ts.map +1 -0
  396. package/dist/video/dialogue.js +143 -0
  397. package/dist/video/dialogue.js.map +1 -0
  398. package/dist/video/director-defaults.d.ts +37 -0
  399. package/dist/video/director-defaults.d.ts.map +1 -0
  400. package/dist/video/director-defaults.js +383 -0
  401. package/dist/video/director-defaults.js.map +1 -0
  402. package/dist/video/director-preflight.d.ts +53 -0
  403. package/dist/video/director-preflight.d.ts.map +1 -0
  404. package/dist/video/director-preflight.js +462 -0
  405. package/dist/video/director-preflight.js.map +1 -0
  406. package/dist/video/doctor-portfolio.d.ts +43 -0
  407. package/dist/video/doctor-portfolio.d.ts.map +1 -0
  408. package/dist/video/doctor-portfolio.js +137 -0
  409. package/dist/video/doctor-portfolio.js.map +1 -0
  410. package/dist/video/doctor.d.ts +13 -0
  411. package/dist/video/doctor.d.ts.map +1 -0
  412. package/dist/video/doctor.js +449 -0
  413. package/dist/video/doctor.js.map +1 -0
  414. package/dist/video/emotion-cues.d.ts +15 -0
  415. package/dist/video/emotion-cues.d.ts.map +1 -0
  416. package/dist/video/emotion-cues.js +37 -0
  417. package/dist/video/emotion-cues.js.map +1 -0
  418. package/dist/video/environment-assets.d.ts +28 -0
  419. package/dist/video/environment-assets.d.ts.map +1 -0
  420. package/dist/video/environment-assets.js +43 -0
  421. package/dist/video/environment-assets.js.map +1 -0
  422. package/dist/video/environment-auto-create.d.ts +20 -0
  423. package/dist/video/environment-auto-create.d.ts.map +1 -0
  424. package/dist/video/environment-auto-create.js +88 -0
  425. package/dist/video/environment-auto-create.js.map +1 -0
  426. package/dist/video/errors.d.ts +35 -0
  427. package/dist/video/errors.d.ts.map +1 -0
  428. package/dist/video/errors.js +117 -0
  429. package/dist/video/errors.js.map +1 -0
  430. package/dist/video/events.d.ts +11 -0
  431. package/dist/video/events.d.ts.map +1 -0
  432. package/dist/video/events.js +32 -0
  433. package/dist/video/events.js.map +1 -0
  434. package/dist/video/execute-autochain.d.ts +65 -0
  435. package/dist/video/execute-autochain.d.ts.map +1 -0
  436. package/dist/video/execute-autochain.js +165 -0
  437. package/dist/video/execute-autochain.js.map +1 -0
  438. package/dist/video/execute.d.ts +27 -0
  439. package/dist/video/execute.d.ts.map +1 -0
  440. package/dist/video/execute.js +477 -0
  441. package/dist/video/execute.js.map +1 -0
  442. package/dist/video/execution-cancel.d.ts +11 -0
  443. package/dist/video/execution-cancel.d.ts.map +1 -0
  444. package/dist/video/execution-cancel.js +85 -0
  445. package/dist/video/execution-cancel.js.map +1 -0
  446. package/dist/video/execution-plan.d.ts +5 -0
  447. package/dist/video/execution-plan.d.ts.map +1 -0
  448. package/dist/video/execution-plan.js +145 -0
  449. package/dist/video/execution-plan.js.map +1 -0
  450. package/dist/video/execution-profile.d.ts +25 -0
  451. package/dist/video/execution-profile.d.ts.map +1 -0
  452. package/dist/video/execution-profile.js +124 -0
  453. package/dist/video/execution-profile.js.map +1 -0
  454. package/dist/video/execution-runtime.d.ts +104 -0
  455. package/dist/video/execution-runtime.d.ts.map +1 -0
  456. package/dist/video/execution-runtime.js +750 -0
  457. package/dist/video/execution-runtime.js.map +1 -0
  458. package/dist/video/execution-seed.d.ts +14 -0
  459. package/dist/video/execution-seed.d.ts.map +1 -0
  460. package/dist/video/execution-seed.js +29 -0
  461. package/dist/video/execution-seed.js.map +1 -0
  462. package/dist/video/execution-status.d.ts +12 -0
  463. package/dist/video/execution-status.d.ts.map +1 -0
  464. package/dist/video/execution-status.js +666 -0
  465. package/dist/video/execution-status.js.map +1 -0
  466. package/dist/video/filmmaking-prompts.d.ts +356 -0
  467. package/dist/video/filmmaking-prompts.d.ts.map +1 -0
  468. package/dist/video/filmmaking-prompts.js +1212 -0
  469. package/dist/video/filmmaking-prompts.js.map +1 -0
  470. package/dist/video/final-media.d.ts +17 -0
  471. package/dist/video/final-media.d.ts.map +1 -0
  472. package/dist/video/final-media.js +78 -0
  473. package/dist/video/final-media.js.map +1 -0
  474. package/dist/video/finish.d.ts +99 -0
  475. package/dist/video/finish.d.ts.map +1 -0
  476. package/dist/video/finish.js +159 -0
  477. package/dist/video/finish.js.map +1 -0
  478. package/dist/video/flow-character-library.d.ts +143 -0
  479. package/dist/video/flow-character-library.d.ts.map +1 -0
  480. package/dist/video/flow-character-library.js +211 -0
  481. package/dist/video/flow-character-library.js.map +1 -0
  482. package/dist/video/flow-markers.d.ts +107 -0
  483. package/dist/video/flow-markers.d.ts.map +1 -0
  484. package/dist/video/flow-markers.js +278 -0
  485. package/dist/video/flow-markers.js.map +1 -0
  486. package/dist/video/gemini-analyze.d.ts +44 -0
  487. package/dist/video/gemini-analyze.d.ts.map +1 -0
  488. package/dist/video/gemini-analyze.js +323 -0
  489. package/dist/video/gemini-analyze.js.map +1 -0
  490. package/dist/video/gemini-continuity.d.ts +40 -0
  491. package/dist/video/gemini-continuity.d.ts.map +1 -0
  492. package/dist/video/gemini-continuity.js +145 -0
  493. package/dist/video/gemini-continuity.js.map +1 -0
  494. package/dist/video/gemini-judge.d.ts +33 -0
  495. package/dist/video/gemini-judge.d.ts.map +1 -0
  496. package/dist/video/gemini-judge.js +218 -0
  497. package/dist/video/gemini-judge.js.map +1 -0
  498. package/dist/video/gemini-key-pool.d.ts +23 -0
  499. package/dist/video/gemini-key-pool.d.ts.map +1 -0
  500. package/dist/video/gemini-key-pool.js +107 -0
  501. package/dist/video/gemini-key-pool.js.map +1 -0
  502. package/dist/video/gen-image-flow.d.ts +187 -0
  503. package/dist/video/gen-image-flow.d.ts.map +1 -0
  504. package/dist/video/gen-image-flow.js +331 -0
  505. package/dist/video/gen-image-flow.js.map +1 -0
  506. package/dist/video/gen-image.d.ts +153 -0
  507. package/dist/video/gen-image.d.ts.map +1 -0
  508. package/dist/video/gen-image.js +263 -0
  509. package/dist/video/gen-image.js.map +1 -0
  510. package/dist/video/generation-telemetry.d.ts +60 -0
  511. package/dist/video/generation-telemetry.d.ts.map +1 -0
  512. package/dist/video/generation-telemetry.js +214 -0
  513. package/dist/video/generation-telemetry.js.map +1 -0
  514. package/dist/video/http-error-safety.d.ts +9 -0
  515. package/dist/video/http-error-safety.d.ts.map +1 -0
  516. package/dist/video/http-error-safety.js +14 -0
  517. package/dist/video/http-error-safety.js.map +1 -0
  518. package/dist/video/image-dimensions.d.ts +7 -0
  519. package/dist/video/image-dimensions.d.ts.map +1 -0
  520. package/dist/video/image-dimensions.js +54 -0
  521. package/dist/video/image-dimensions.js.map +1 -0
  522. package/dist/video/legacy-import.d.ts +11 -0
  523. package/dist/video/legacy-import.d.ts.map +1 -0
  524. package/dist/video/legacy-import.js +181 -0
  525. package/dist/video/legacy-import.js.map +1 -0
  526. package/dist/video/library-clean.d.ts +34 -0
  527. package/dist/video/library-clean.d.ts.map +1 -0
  528. package/dist/video/library-clean.js +340 -0
  529. package/dist/video/library-clean.js.map +1 -0
  530. package/dist/video/lipsync.d.ts +72 -0
  531. package/dist/video/lipsync.d.ts.map +1 -0
  532. package/dist/video/lipsync.js +136 -0
  533. package/dist/video/lipsync.js.map +1 -0
  534. package/dist/video/media-host.d.ts +41 -0
  535. package/dist/video/media-host.d.ts.map +1 -0
  536. package/dist/video/media-host.js +106 -0
  537. package/dist/video/media-host.js.map +1 -0
  538. package/dist/video/metrics.d.ts +30 -0
  539. package/dist/video/metrics.d.ts.map +1 -0
  540. package/dist/video/metrics.js +81 -0
  541. package/dist/video/metrics.js.map +1 -0
  542. package/dist/video/motion-overlay/analyze-reel.d.ts +38 -0
  543. package/dist/video/motion-overlay/analyze-reel.d.ts.map +1 -0
  544. package/dist/video/motion-overlay/analyze-reel.js +185 -0
  545. package/dist/video/motion-overlay/analyze-reel.js.map +1 -0
  546. package/dist/video/motion-overlay/animate-render.d.ts +63 -0
  547. package/dist/video/motion-overlay/animate-render.d.ts.map +1 -0
  548. package/dist/video/motion-overlay/animate-render.js +128 -0
  549. package/dist/video/motion-overlay/animate-render.js.map +1 -0
  550. package/dist/video/motion-overlay/animate.d.ts +37 -0
  551. package/dist/video/motion-overlay/animate.d.ts.map +1 -0
  552. package/dist/video/motion-overlay/animate.js +97 -0
  553. package/dist/video/motion-overlay/animate.js.map +1 -0
  554. package/dist/video/motion-overlay/avatar-host-transport.d.ts +161 -0
  555. package/dist/video/motion-overlay/avatar-host-transport.d.ts.map +1 -0
  556. package/dist/video/motion-overlay/avatar-host-transport.js +325 -0
  557. package/dist/video/motion-overlay/avatar-host-transport.js.map +1 -0
  558. package/dist/video/motion-overlay/avatar-host.d.ts +131 -0
  559. package/dist/video/motion-overlay/avatar-host.d.ts.map +1 -0
  560. package/dist/video/motion-overlay/avatar-host.js +131 -0
  561. package/dist/video/motion-overlay/avatar-host.js.map +1 -0
  562. package/dist/video/motion-overlay/compose-prompt.d.ts +48 -0
  563. package/dist/video/motion-overlay/compose-prompt.d.ts.map +1 -0
  564. package/dist/video/motion-overlay/compose-prompt.js +217 -0
  565. package/dist/video/motion-overlay/compose-prompt.js.map +1 -0
  566. package/dist/video/motion-overlay/execute.d.ts +190 -0
  567. package/dist/video/motion-overlay/execute.d.ts.map +1 -0
  568. package/dist/video/motion-overlay/execute.js +205 -0
  569. package/dist/video/motion-overlay/execute.js.map +1 -0
  570. package/dist/video/motion-overlay/flow-pack.d.ts +65 -0
  571. package/dist/video/motion-overlay/flow-pack.d.ts.map +1 -0
  572. package/dist/video/motion-overlay/flow-pack.js +178 -0
  573. package/dist/video/motion-overlay/flow-pack.js.map +1 -0
  574. package/dist/video/motion-overlay/ingest.d.ts +74 -0
  575. package/dist/video/motion-overlay/ingest.d.ts.map +1 -0
  576. package/dist/video/motion-overlay/ingest.js +172 -0
  577. package/dist/video/motion-overlay/ingest.js.map +1 -0
  578. package/dist/video/motion-overlay/motifs.d.ts +63 -0
  579. package/dist/video/motion-overlay/motifs.d.ts.map +1 -0
  580. package/dist/video/motion-overlay/motifs.js +192 -0
  581. package/dist/video/motion-overlay/motifs.js.map +1 -0
  582. package/dist/video/motion-overlay/motion-style.d.ts +56 -0
  583. package/dist/video/motion-overlay/motion-style.d.ts.map +1 -0
  584. package/dist/video/motion-overlay/motion-style.js +130 -0
  585. package/dist/video/motion-overlay/motion-style.js.map +1 -0
  586. package/dist/video/motion-overlay/plan.d.ts +49 -0
  587. package/dist/video/motion-overlay/plan.d.ts.map +1 -0
  588. package/dist/video/motion-overlay/plan.js +79 -0
  589. package/dist/video/motion-overlay/plan.js.map +1 -0
  590. package/dist/video/motion-overlay/preview.d.ts +36 -0
  591. package/dist/video/motion-overlay/preview.d.ts.map +1 -0
  592. package/dist/video/motion-overlay/preview.js +169 -0
  593. package/dist/video/motion-overlay/preview.js.map +1 -0
  594. package/dist/video/motion-overlay/render-local.d.ts +118 -0
  595. package/dist/video/motion-overlay/render-local.d.ts.map +1 -0
  596. package/dist/video/motion-overlay/render-local.js +298 -0
  597. package/dist/video/motion-overlay/render-local.js.map +1 -0
  598. package/dist/video/motion-overlay/run.d.ts +58 -0
  599. package/dist/video/motion-overlay/run.d.ts.map +1 -0
  600. package/dist/video/motion-overlay/run.js +147 -0
  601. package/dist/video/motion-overlay/run.js.map +1 -0
  602. package/dist/video/motion-overlay/slice.d.ts +29 -0
  603. package/dist/video/motion-overlay/slice.d.ts.map +1 -0
  604. package/dist/video/motion-overlay/slice.js +88 -0
  605. package/dist/video/motion-overlay/slice.js.map +1 -0
  606. package/dist/video/motion-overlay/transcribe.d.ts +49 -0
  607. package/dist/video/motion-overlay/transcribe.d.ts.map +1 -0
  608. package/dist/video/motion-overlay/transcribe.js +180 -0
  609. package/dist/video/motion-overlay/transcribe.js.map +1 -0
  610. package/dist/video/motion-overlay/types.d.ts +82 -0
  611. package/dist/video/motion-overlay/types.d.ts.map +1 -0
  612. package/dist/video/motion-overlay/types.js +12 -0
  613. package/dist/video/motion-overlay/types.js.map +1 -0
  614. package/dist/video/motion-overlay/v2v-transport.d.ts +37 -0
  615. package/dist/video/motion-overlay/v2v-transport.d.ts.map +1 -0
  616. package/dist/video/motion-overlay/v2v-transport.js +178 -0
  617. package/dist/video/motion-overlay/v2v-transport.js.map +1 -0
  618. package/dist/video/motion-overlay/write.d.ts +40 -0
  619. package/dist/video/motion-overlay/write.d.ts.map +1 -0
  620. package/dist/video/motion-overlay/write.js +113 -0
  621. package/dist/video/motion-overlay/write.js.map +1 -0
  622. package/dist/video/multi-shot-artifact.d.ts +19 -0
  623. package/dist/video/multi-shot-artifact.d.ts.map +1 -0
  624. package/dist/video/multi-shot-artifact.js +44 -0
  625. package/dist/video/multi-shot-artifact.js.map +1 -0
  626. package/dist/video/multi-shot-prompt.d.ts +99 -0
  627. package/dist/video/multi-shot-prompt.d.ts.map +1 -0
  628. package/dist/video/multi-shot-prompt.js +466 -0
  629. package/dist/video/multi-shot-prompt.js.map +1 -0
  630. package/dist/video/music-video.d.ts +110 -0
  631. package/dist/video/music-video.d.ts.map +1 -0
  632. package/dist/video/music-video.js +190 -0
  633. package/dist/video/music-video.js.map +1 -0
  634. package/dist/video/narrate.d.ts +58 -0
  635. package/dist/video/narrate.d.ts.map +1 -0
  636. package/dist/video/narrate.js +145 -0
  637. package/dist/video/narrate.js.map +1 -0
  638. package/dist/video/native-dreamina.d.ts +35 -0
  639. package/dist/video/native-dreamina.d.ts.map +1 -0
  640. package/dist/video/native-dreamina.js +532 -0
  641. package/dist/video/native-dreamina.js.map +1 -0
  642. package/dist/video/native-flow-r2v.d.ts +113 -0
  643. package/dist/video/native-flow-r2v.d.ts.map +1 -0
  644. package/dist/video/native-flow-r2v.js +223 -0
  645. package/dist/video/native-flow-r2v.js.map +1 -0
  646. package/dist/video/native-runway.d.ts +41 -0
  647. package/dist/video/native-runway.d.ts.map +1 -0
  648. package/dist/video/native-runway.js +523 -0
  649. package/dist/video/native-runway.js.map +1 -0
  650. package/dist/video/native-seedance.d.ts +74 -0
  651. package/dist/video/native-seedance.d.ts.map +1 -0
  652. package/dist/video/native-seedance.js +461 -0
  653. package/dist/video/native-seedance.js.map +1 -0
  654. package/dist/video/native-veo.d.ts +20 -0
  655. package/dist/video/native-veo.d.ts.map +1 -0
  656. package/dist/video/native-veo.js +390 -0
  657. package/dist/video/native-veo.js.map +1 -0
  658. package/dist/video/next-actions.d.ts +27 -0
  659. package/dist/video/next-actions.d.ts.map +1 -0
  660. package/dist/video/next-actions.js +201 -0
  661. package/dist/video/next-actions.js.map +1 -0
  662. package/dist/video/obsidian-export.d.ts +12 -0
  663. package/dist/video/obsidian-export.d.ts.map +1 -0
  664. package/dist/video/obsidian-export.js +285 -0
  665. package/dist/video/obsidian-export.js.map +1 -0
  666. package/dist/video/obsidian-sync.d.ts +13 -0
  667. package/dist/video/obsidian-sync.d.ts.map +1 -0
  668. package/dist/video/obsidian-sync.js +479 -0
  669. package/dist/video/obsidian-sync.js.map +1 -0
  670. package/dist/video/obsidian-vault.d.ts +8 -0
  671. package/dist/video/obsidian-vault.d.ts.map +1 -0
  672. package/dist/video/obsidian-vault.js +81 -0
  673. package/dist/video/obsidian-vault.js.map +1 -0
  674. package/dist/video/outfit-prompts.d.ts +10 -0
  675. package/dist/video/outfit-prompts.d.ts.map +1 -0
  676. package/dist/video/outfit-prompts.js +14 -0
  677. package/dist/video/outfit-prompts.js.map +1 -0
  678. package/dist/video/outpaint-keyframe.d.ts +95 -0
  679. package/dist/video/outpaint-keyframe.d.ts.map +1 -0
  680. package/dist/video/outpaint-keyframe.js +244 -0
  681. package/dist/video/outpaint-keyframe.js.map +1 -0
  682. package/dist/video/pipeline-manifest.d.ts +4 -0
  683. package/dist/video/pipeline-manifest.d.ts.map +1 -0
  684. package/dist/video/pipeline-manifest.js +13 -0
  685. package/dist/video/pipeline-manifest.js.map +1 -0
  686. package/dist/video/pipeline-manifests/director.json +46 -0
  687. package/dist/video/pipeline-manifests/storyboard.json +46 -0
  688. package/dist/video/platform-specs.d.ts +26 -0
  689. package/dist/video/platform-specs.d.ts.map +1 -0
  690. package/dist/video/platform-specs.js +34 -0
  691. package/dist/video/platform-specs.js.map +1 -0
  692. package/dist/video/playbooks.d.ts +11 -0
  693. package/dist/video/playbooks.d.ts.map +1 -0
  694. package/dist/video/playbooks.js +22 -0
  695. package/dist/video/playbooks.js.map +1 -0
  696. package/dist/video/post-production.d.ts +57 -0
  697. package/dist/video/post-production.d.ts.map +1 -0
  698. package/dist/video/post-production.js +210 -0
  699. package/dist/video/post-production.js.map +1 -0
  700. package/dist/video/preview-portal/audit.d.ts +4 -0
  701. package/dist/video/preview-portal/audit.d.ts.map +1 -0
  702. package/dist/video/preview-portal/audit.js +16 -0
  703. package/dist/video/preview-portal/audit.js.map +1 -0
  704. package/dist/video/preview-portal/discovery.d.ts +23 -0
  705. package/dist/video/preview-portal/discovery.d.ts.map +1 -0
  706. package/dist/video/preview-portal/discovery.js +733 -0
  707. package/dist/video/preview-portal/discovery.js.map +1 -0
  708. package/dist/video/preview-portal/generate.d.ts +42 -0
  709. package/dist/video/preview-portal/generate.d.ts.map +1 -0
  710. package/dist/video/preview-portal/generate.js +101 -0
  711. package/dist/video/preview-portal/generate.js.map +1 -0
  712. package/dist/video/preview-portal/index.d.ts +12 -0
  713. package/dist/video/preview-portal/index.d.ts.map +1 -0
  714. package/dist/video/preview-portal/index.js +8 -0
  715. package/dist/video/preview-portal/index.js.map +1 -0
  716. package/dist/video/preview-portal/publish.d.ts +48 -0
  717. package/dist/video/preview-portal/publish.d.ts.map +1 -0
  718. package/dist/video/preview-portal/publish.js +169 -0
  719. package/dist/video/preview-portal/publish.js.map +1 -0
  720. package/dist/video/preview-portal/render.d.ts +4 -0
  721. package/dist/video/preview-portal/render.d.ts.map +1 -0
  722. package/dist/video/preview-portal/render.js +822 -0
  723. package/dist/video/preview-portal/render.js.map +1 -0
  724. package/dist/video/preview-portal/shared-assets.d.ts +6 -0
  725. package/dist/video/preview-portal/shared-assets.d.ts.map +1 -0
  726. package/dist/video/preview-portal/shared-assets.js +452 -0
  727. package/dist/video/preview-portal/shared-assets.js.map +1 -0
  728. package/dist/video/preview-portal/templates.d.ts +4 -0
  729. package/dist/video/preview-portal/templates.d.ts.map +1 -0
  730. package/dist/video/preview-portal/templates.js +109 -0
  731. package/dist/video/preview-portal/templates.js.map +1 -0
  732. package/dist/video/preview-portal/types.d.ts +246 -0
  733. package/dist/video/preview-portal/types.d.ts.map +1 -0
  734. package/dist/video/preview-portal/types.js +28 -0
  735. package/dist/video/preview-portal/types.js.map +1 -0
  736. package/dist/video/product-references.d.ts +21 -0
  737. package/dist/video/product-references.d.ts.map +1 -0
  738. package/dist/video/product-references.js +25 -0
  739. package/dist/video/product-references.js.map +1 -0
  740. package/dist/video/project-blueprint.d.ts +178 -0
  741. package/dist/video/project-blueprint.d.ts.map +1 -0
  742. package/dist/video/project-blueprint.js +286 -0
  743. package/dist/video/project-blueprint.js.map +1 -0
  744. package/dist/video/project-index.d.ts +82 -0
  745. package/dist/video/project-index.d.ts.map +1 -0
  746. package/dist/video/project-index.js +89 -0
  747. package/dist/video/project-index.js.map +1 -0
  748. package/dist/video/projects.d.ts +3 -0
  749. package/dist/video/projects.d.ts.map +1 -0
  750. package/dist/video/projects.js +18 -0
  751. package/dist/video/projects.js.map +1 -0
  752. package/dist/video/prompt-guidance.d.ts +14 -0
  753. package/dist/video/prompt-guidance.d.ts.map +1 -0
  754. package/dist/video/prompt-guidance.js +44 -0
  755. package/dist/video/prompt-guidance.js.map +1 -0
  756. package/dist/video/prompt-library.d.ts +11 -0
  757. package/dist/video/prompt-library.d.ts.map +1 -0
  758. package/dist/video/prompt-library.js +85 -0
  759. package/dist/video/prompt-library.js.map +1 -0
  760. package/dist/video/prompt-lint.d.ts +146 -0
  761. package/dist/video/prompt-lint.d.ts.map +1 -0
  762. package/dist/video/prompt-lint.js +433 -0
  763. package/dist/video/prompt-lint.js.map +1 -0
  764. package/dist/video/prompt-quality.d.ts +29 -0
  765. package/dist/video/prompt-quality.d.ts.map +1 -0
  766. package/dist/video/prompt-quality.js +532 -0
  767. package/dist/video/prompt-quality.js.map +1 -0
  768. package/dist/video/prompt-rules.d.ts +94 -0
  769. package/dist/video/prompt-rules.d.ts.map +1 -0
  770. package/dist/video/prompt-rules.js +169 -0
  771. package/dist/video/prompt-rules.js.map +1 -0
  772. package/dist/video/provider-adapter-runner.d.ts +5 -0
  773. package/dist/video/provider-adapter-runner.d.ts.map +1 -0
  774. package/dist/video/provider-adapter-runner.js +92 -0
  775. package/dist/video/provider-adapter-runner.js.map +1 -0
  776. package/dist/video/provider-platform/index.d.ts +6 -0
  777. package/dist/video/provider-platform/index.d.ts.map +1 -0
  778. package/dist/video/provider-platform/index.js +6 -0
  779. package/dist/video/provider-platform/index.js.map +1 -0
  780. package/dist/video/provider-platform/registry.d.ts +5 -0
  781. package/dist/video/provider-platform/registry.d.ts.map +1 -0
  782. package/dist/video/provider-platform/registry.js +279 -0
  783. package/dist/video/provider-platform/registry.js.map +1 -0
  784. package/dist/video/provider-platform/route-capabilities.d.ts +133 -0
  785. package/dist/video/provider-platform/route-capabilities.d.ts.map +1 -0
  786. package/dist/video/provider-platform/route-capabilities.js +192 -0
  787. package/dist/video/provider-platform/route-capabilities.js.map +1 -0
  788. package/dist/video/provider-platform/router.d.ts +7 -0
  789. package/dist/video/provider-platform/router.d.ts.map +1 -0
  790. package/dist/video/provider-platform/router.js +150 -0
  791. package/dist/video/provider-platform/router.js.map +1 -0
  792. package/dist/video/provider-platform/security.d.ts +8 -0
  793. package/dist/video/provider-platform/security.d.ts.map +1 -0
  794. package/dist/video/provider-platform/security.js +21 -0
  795. package/dist/video/provider-platform/security.js.map +1 -0
  796. package/dist/video/provider-platform/telemetry.d.ts +24 -0
  797. package/dist/video/provider-platform/telemetry.d.ts.map +1 -0
  798. package/dist/video/provider-platform/telemetry.js +88 -0
  799. package/dist/video/provider-platform/telemetry.js.map +1 -0
  800. package/dist/video/provider-platform/types.d.ts +139 -0
  801. package/dist/video/provider-platform/types.d.ts.map +1 -0
  802. package/dist/video/provider-platform/types.js +17 -0
  803. package/dist/video/provider-platform/types.js.map +1 -0
  804. package/dist/video/provider-status.d.ts +12 -0
  805. package/dist/video/provider-status.d.ts.map +1 -0
  806. package/dist/video/provider-status.js +169 -0
  807. package/dist/video/provider-status.js.map +1 -0
  808. package/dist/video/providers/dreamina-useapi.d.ts +170 -0
  809. package/dist/video/providers/dreamina-useapi.d.ts.map +1 -0
  810. package/dist/video/providers/dreamina-useapi.js +176 -0
  811. package/dist/video/providers/dreamina-useapi.js.map +1 -0
  812. package/dist/video/providers/flowmusic-useapi.d.ts +114 -0
  813. package/dist/video/providers/flowmusic-useapi.d.ts.map +1 -0
  814. package/dist/video/providers/flowmusic-useapi.js +159 -0
  815. package/dist/video/providers/flowmusic-useapi.js.map +1 -0
  816. package/dist/video/providers/google-flow.d.ts +57 -0
  817. package/dist/video/providers/google-flow.d.ts.map +1 -0
  818. package/dist/video/providers/google-flow.js +63 -0
  819. package/dist/video/providers/google-flow.js.map +1 -0
  820. package/dist/video/providers/public-host.d.ts +40 -0
  821. package/dist/video/providers/public-host.d.ts.map +1 -0
  822. package/dist/video/providers/public-host.js +91 -0
  823. package/dist/video/providers/public-host.js.map +1 -0
  824. package/dist/video/providers/runway-useapi.d.ts +159 -0
  825. package/dist/video/providers/runway-useapi.d.ts.map +1 -0
  826. package/dist/video/providers/runway-useapi.js +200 -0
  827. package/dist/video/providers/runway-useapi.js.map +1 -0
  828. package/dist/video/providers/xskill.d.ts +108 -0
  829. package/dist/video/providers/xskill.d.ts.map +1 -0
  830. package/dist/video/providers/xskill.js +233 -0
  831. package/dist/video/providers/xskill.js.map +1 -0
  832. package/dist/video/readiness.d.ts +18 -0
  833. package/dist/video/readiness.d.ts.map +1 -0
  834. package/dist/video/readiness.js +221 -0
  835. package/dist/video/readiness.js.map +1 -0
  836. package/dist/video/reference-sheet-store.d.ts +5 -0
  837. package/dist/video/reference-sheet-store.d.ts.map +1 -0
  838. package/dist/video/reference-sheet-store.js +28 -0
  839. package/dist/video/reference-sheet-store.js.map +1 -0
  840. package/dist/video/reference-sheets.d.ts +40 -0
  841. package/dist/video/reference-sheets.d.ts.map +1 -0
  842. package/dist/video/reference-sheets.js +160 -0
  843. package/dist/video/reference-sheets.js.map +1 -0
  844. package/dist/video/remix-narrated.d.ts +12 -0
  845. package/dist/video/remix-narrated.d.ts.map +1 -0
  846. package/dist/video/remix-narrated.js +73 -0
  847. package/dist/video/remix-narrated.js.map +1 -0
  848. package/dist/video/report-diff.d.ts +89 -0
  849. package/dist/video/report-diff.d.ts.map +1 -0
  850. package/dist/video/report-diff.js +176 -0
  851. package/dist/video/report-diff.js.map +1 -0
  852. package/dist/video/report-history.d.ts +40 -0
  853. package/dist/video/report-history.d.ts.map +1 -0
  854. package/dist/video/report-history.js +74 -0
  855. package/dist/video/report-history.js.map +1 -0
  856. package/dist/video/report.d.ts +15 -0
  857. package/dist/video/report.d.ts.map +1 -0
  858. package/dist/video/report.js +21 -0
  859. package/dist/video/report.js.map +1 -0
  860. package/dist/video/review-ui.d.ts +180 -0
  861. package/dist/video/review-ui.d.ts.map +1 -0
  862. package/dist/video/review-ui.js +2008 -0
  863. package/dist/video/review-ui.js.map +1 -0
  864. package/dist/video/scene-candidate-store.d.ts +12 -0
  865. package/dist/video/scene-candidate-store.d.ts.map +1 -0
  866. package/dist/video/scene-candidate-store.js +134 -0
  867. package/dist/video/scene-candidate-store.js.map +1 -0
  868. package/dist/video/scene-candidates.d.ts +57 -0
  869. package/dist/video/scene-candidates.d.ts.map +1 -0
  870. package/dist/video/scene-candidates.js +149 -0
  871. package/dist/video/scene-candidates.js.map +1 -0
  872. package/dist/video/scene-selection-store.d.ts +5 -0
  873. package/dist/video/scene-selection-store.d.ts.map +1 -0
  874. package/dist/video/scene-selection-store.js +22 -0
  875. package/dist/video/scene-selection-store.js.map +1 -0
  876. package/dist/video/scene-selection.d.ts +83 -0
  877. package/dist/video/scene-selection.d.ts.map +1 -0
  878. package/dist/video/scene-selection.js +225 -0
  879. package/dist/video/scene-selection.js.map +1 -0
  880. package/dist/video/scheduling.d.ts +3 -0
  881. package/dist/video/scheduling.d.ts.map +1 -0
  882. package/dist/video/scheduling.js +16 -0
  883. package/dist/video/scheduling.js.map +1 -0
  884. package/dist/video/scorecard.d.ts +12 -0
  885. package/dist/video/scorecard.d.ts.map +1 -0
  886. package/dist/video/scorecard.js +68 -0
  887. package/dist/video/scorecard.js.map +1 -0
  888. package/dist/video/seedance-asset-library.d.ts +82 -0
  889. package/dist/video/seedance-asset-library.d.ts.map +1 -0
  890. package/dist/video/seedance-asset-library.js +146 -0
  891. package/dist/video/seedance-asset-library.js.map +1 -0
  892. package/dist/video/seedance-blocks.d.ts +56 -0
  893. package/dist/video/seedance-blocks.d.ts.map +1 -0
  894. package/dist/video/seedance-blocks.js +97 -0
  895. package/dist/video/seedance-blocks.js.map +1 -0
  896. package/dist/video/seedance-chain-host.d.ts +42 -0
  897. package/dist/video/seedance-chain-host.d.ts.map +1 -0
  898. package/dist/video/seedance-chain-host.js +128 -0
  899. package/dist/video/seedance-chain-host.js.map +1 -0
  900. package/dist/video/seedance-content-filter.d.ts +80 -0
  901. package/dist/video/seedance-content-filter.d.ts.map +1 -0
  902. package/dist/video/seedance-content-filter.js +293 -0
  903. package/dist/video/seedance-content-filter.js.map +1 -0
  904. package/dist/video/seedance-skill-loader.d.ts +40 -0
  905. package/dist/video/seedance-skill-loader.d.ts.map +1 -0
  906. package/dist/video/seedance-skill-loader.js +53 -0
  907. package/dist/video/seedance-skill-loader.js.map +1 -0
  908. package/dist/video/sfx.d.ts +49 -0
  909. package/dist/video/sfx.d.ts.map +1 -0
  910. package/dist/video/sfx.js +104 -0
  911. package/dist/video/sfx.js.map +1 -0
  912. package/dist/video/shot-grammar.d.ts +103 -0
  913. package/dist/video/shot-grammar.d.ts.map +1 -0
  914. package/dist/video/shot-grammar.js +286 -0
  915. package/dist/video/shot-grammar.js.map +1 -0
  916. package/dist/video/show-bible.d.ts +113 -0
  917. package/dist/video/show-bible.d.ts.map +1 -0
  918. package/dist/video/show-bible.js +140 -0
  919. package/dist/video/show-bible.js.map +1 -0
  920. package/dist/video/soundtrack.d.ts +72 -0
  921. package/dist/video/soundtrack.d.ts.map +1 -0
  922. package/dist/video/soundtrack.js +171 -0
  923. package/dist/video/soundtrack.js.map +1 -0
  924. package/dist/video/stage-guards.d.ts +4 -0
  925. package/dist/video/stage-guards.d.ts.map +1 -0
  926. package/dist/video/stage-guards.js +94 -0
  927. package/dist/video/stage-guards.js.map +1 -0
  928. package/dist/video/status.d.ts +69 -0
  929. package/dist/video/status.d.ts.map +1 -0
  930. package/dist/video/status.js +245 -0
  931. package/dist/video/status.js.map +1 -0
  932. package/dist/video/story-bible.d.ts +70 -0
  933. package/dist/video/story-bible.d.ts.map +1 -0
  934. package/dist/video/story-bible.js +181 -0
  935. package/dist/video/story-bible.js.map +1 -0
  936. package/dist/video/storyboard-grid.d.ts +28 -0
  937. package/dist/video/storyboard-grid.d.ts.map +1 -0
  938. package/dist/video/storyboard-grid.js +231 -0
  939. package/dist/video/storyboard-grid.js.map +1 -0
  940. package/dist/video/storyboard-markdown.d.ts +62 -0
  941. package/dist/video/storyboard-markdown.d.ts.map +1 -0
  942. package/dist/video/storyboard-markdown.js +332 -0
  943. package/dist/video/storyboard-markdown.js.map +1 -0
  944. package/dist/video/storyboard-still-candidates.d.ts +31 -0
  945. package/dist/video/storyboard-still-candidates.d.ts.map +1 -0
  946. package/dist/video/storyboard-still-candidates.js +57 -0
  947. package/dist/video/storyboard-still-candidates.js.map +1 -0
  948. package/dist/video/storyboard-templates.d.ts +28 -0
  949. package/dist/video/storyboard-templates.d.ts.map +1 -0
  950. package/dist/video/storyboard-templates.js +215 -0
  951. package/dist/video/storyboard-templates.js.map +1 -0
  952. package/dist/video/studio/execute.d.ts +142 -0
  953. package/dist/video/studio/execute.d.ts.map +1 -0
  954. package/dist/video/studio/execute.js +270 -0
  955. package/dist/video/studio/execute.js.map +1 -0
  956. package/dist/video/studio/planner.d.ts +3 -0
  957. package/dist/video/studio/planner.d.ts.map +1 -0
  958. package/dist/video/studio/planner.js +110 -0
  959. package/dist/video/studio/planner.js.map +1 -0
  960. package/dist/video/studio/project-context.d.ts +4 -0
  961. package/dist/video/studio/project-context.d.ts.map +1 -0
  962. package/dist/video/studio/project-context.js +20 -0
  963. package/dist/video/studio/project-context.js.map +1 -0
  964. package/dist/video/studio/recipes.d.ts +4 -0
  965. package/dist/video/studio/recipes.d.ts.map +1 -0
  966. package/dist/video/studio/recipes.js +343 -0
  967. package/dist/video/studio/recipes.js.map +1 -0
  968. package/dist/video/studio/session.d.ts +8 -0
  969. package/dist/video/studio/session.d.ts.map +1 -0
  970. package/dist/video/studio/session.js +17 -0
  971. package/dist/video/studio/session.js.map +1 -0
  972. package/dist/video/studio/types.d.ts +59 -0
  973. package/dist/video/studio/types.d.ts.map +1 -0
  974. package/dist/video/studio/types.js +2 -0
  975. package/dist/video/studio/types.js.map +1 -0
  976. package/dist/video/template-store.d.ts +65 -0
  977. package/dist/video/template-store.d.ts.map +1 -0
  978. package/dist/video/template-store.js +168 -0
  979. package/dist/video/template-store.js.map +1 -0
  980. package/dist/video/timeline.d.ts +8 -0
  981. package/dist/video/timeline.d.ts.map +1 -0
  982. package/dist/video/timeline.js +21 -0
  983. package/dist/video/timeline.js.map +1 -0
  984. package/dist/video/title-overlay.d.ts +85 -0
  985. package/dist/video/title-overlay.d.ts.map +1 -0
  986. package/dist/video/title-overlay.js +163 -0
  987. package/dist/video/title-overlay.js.map +1 -0
  988. package/dist/video/types.d.ts +403 -0
  989. package/dist/video/types.d.ts.map +1 -0
  990. package/dist/video/types.js +2 -0
  991. package/dist/video/types.js.map +1 -0
  992. package/dist/video/veo-subprocess.d.ts +27 -0
  993. package/dist/video/veo-subprocess.d.ts.map +1 -0
  994. package/dist/video/veo-subprocess.js +52 -0
  995. package/dist/video/veo-subprocess.js.map +1 -0
  996. package/dist/video/verify-env.d.ts +43 -0
  997. package/dist/video/verify-env.d.ts.map +1 -0
  998. package/dist/video/verify-env.js +156 -0
  999. package/dist/video/verify-env.js.map +1 -0
  1000. package/dist/video/verify-final.d.ts +20 -0
  1001. package/dist/video/verify-final.d.ts.map +1 -0
  1002. package/dist/video/verify-final.js +39 -0
  1003. package/dist/video/verify-final.js.map +1 -0
  1004. package/dist/video/video-context.d.ts +10 -0
  1005. package/dist/video/video-context.d.ts.map +1 -0
  1006. package/dist/video/video-context.js +74 -0
  1007. package/dist/video/video-context.js.map +1 -0
  1008. package/dist/video/vocal-map.d.ts +55 -0
  1009. package/dist/video/vocal-map.d.ts.map +1 -0
  1010. package/dist/video/vocal-map.js +107 -0
  1011. package/dist/video/vocal-map.js.map +1 -0
  1012. package/dist/video/vocal-sync-plan.d.ts +76 -0
  1013. package/dist/video/vocal-sync-plan.d.ts.map +1 -0
  1014. package/dist/video/vocal-sync-plan.js +166 -0
  1015. package/dist/video/vocal-sync-plan.js.map +1 -0
  1016. package/dist/video/voice-clone.d.ts +152 -0
  1017. package/dist/video/voice-clone.d.ts.map +1 -0
  1018. package/dist/video/voice-clone.js +267 -0
  1019. package/dist/video/voice-clone.js.map +1 -0
  1020. package/dist/video/with-retry.d.ts +67 -0
  1021. package/dist/video/with-retry.d.ts.map +1 -0
  1022. package/dist/video/with-retry.js +105 -0
  1023. package/dist/video/with-retry.js.map +1 -0
  1024. package/dist/video/workload.d.ts +20 -0
  1025. package/dist/video/workload.d.ts.map +1 -0
  1026. package/dist/video/workload.js +55 -0
  1027. package/dist/video/workload.js.map +1 -0
  1028. package/dist/video/workspace.d.ts +109 -0
  1029. package/dist/video/workspace.d.ts.map +1 -0
  1030. package/dist/video/workspace.js +132 -0
  1031. package/dist/video/workspace.js.map +1 -0
  1032. package/docs/AGENT_INTEGRATION_RESEARCH.md +123 -0
  1033. package/docs/AI_FILMMAKING_PROMPTS.md +195 -0
  1034. package/docs/ARCHITECTURE.md +257 -0
  1035. package/docs/ASSEMBLE.md +376 -0
  1036. package/docs/BRAND_AGENCY.md +63 -0
  1037. package/docs/CLAWBOT_COVERAGE_PROMPT.md +78 -0
  1038. package/docs/CLI_REFERENCE.md +2638 -0
  1039. package/docs/CONCIERGE_PROMPT.md +139 -0
  1040. package/docs/Claude Code + Higgsfield MCP = FULL Creative Agency.md +179 -0
  1041. package/docs/DEMO_RECORDING.md +61 -0
  1042. package/docs/DEPRECATION.md +79 -0
  1043. package/docs/DIAGRAMS_SOURCE.md +347 -0
  1044. package/docs/DIRECTOR_BLUEPRINT.md +94 -0
  1045. package/docs/GENERATION_TELEMETRY.md +81 -0
  1046. package/docs/IMPROVE_PROMPT.md +76 -0
  1047. package/docs/INFOGRAPHICS_PROMPT.md +83 -0
  1048. package/docs/MASTER_PLAN_ALIGNMENT.md +563 -0
  1049. package/docs/MIGRATION.md +213 -0
  1050. package/docs/MOTION_OVERLAY.md +355 -0
  1051. package/docs/OBSIDIAN.md +300 -0
  1052. package/docs/OPERATIONS.md +142 -0
  1053. package/docs/OPERATOR_HANDOFF.md +108 -0
  1054. package/docs/PRODUCTION_WORKFLOW.md +148 -0
  1055. package/docs/PROJECT_LAYOUT.md +262 -0
  1056. package/docs/PROMPT_QUALITY.md +214 -0
  1057. package/docs/PROVIDER_PLATFORM.md +195 -0
  1058. package/docs/PUBLISHING.md +175 -0
  1059. package/docs/PYTHON_PIPELINE.md +139 -0
  1060. package/docs/REFERENCE_SHEETS.md +469 -0
  1061. package/docs/REFERENCE_VIDEO_SEEDANCE_MOTION_DESIGN_WORKFLOW.md +465 -0
  1062. package/docs/RELEASE_READINESS.md +573 -0
  1063. package/docs/REVIEW_UI_STORYBOARD_WORKFLOW.md +200 -0
  1064. package/docs/SCENE_CANDIDATES.md +518 -0
  1065. package/docs/SKILLS.md +477 -0
  1066. package/docs/SKILL_COHESION_PROMPT.md +88 -0
  1067. package/docs/STORY_BIBLE.md +288 -0
  1068. package/docs/STUDIO.md +96 -0
  1069. package/docs/TEMPLATES.md +143 -0
  1070. package/docs/UNIFICATION_AUDIT.md +321 -0
  1071. package/docs/UPDATE_DOCS_PROMPT.md +78 -0
  1072. package/docs/assets/demo-quickstart.cast +13 -0
  1073. package/docs/assets/demo-quickstart.gif +0 -0
  1074. package/docs/assets/diagram-architecture.jpg +0 -0
  1075. package/docs/assets/diagram-assemble.jpg +0 -0
  1076. package/docs/assets/diagram-lifecycle.jpg +0 -0
  1077. package/docs/assets/diagram-obsidian-loop.jpg +0 -0
  1078. package/docs/assets/diagram-obsidian-vault.jpg +0 -0
  1079. package/docs/assets/diagram-routing.jpg +0 -0
  1080. package/docs/assets/diagram-skills-ecosystem.jpg +0 -0
  1081. package/docs/assets/diagram-story-bible.jpg +0 -0
  1082. package/docs/assets/diagram-studio-goals.jpg +0 -0
  1083. package/docs/assets/docsite-preview.png +0 -0
  1084. package/docs/assets/logo.jpg +0 -0
  1085. package/docs/assets/logo.png +0 -0
  1086. package/docs/preview-portal-audit.md +151 -0
  1087. package/package.json +119 -0
  1088. package/playbooks/seedance-ugc.json +32 -0
  1089. package/playbooks/veo-generic.json +26 -0
  1090. package/references/video/.fixtures/multi-shot-runway-10s.txt +9 -0
  1091. package/references/video/.fixtures/multi-shot-seedance-10s.txt +9 -0
  1092. package/references/video/.fixtures/multi-shot-valid.txt +13 -0
  1093. package/references/video/.fixtures/multi-shot-veo-8s.txt +9 -0
  1094. package/references/video/ai-director-blueprint.md +614 -0
  1095. package/references/video/camera-bible.md +294 -0
  1096. package/references/video/character-reference-sheet.md +14 -0
  1097. package/references/video/checkpoint-protocol.md +26 -0
  1098. package/references/video/clone-ad-template-workflow.md +14 -0
  1099. package/references/video/dialogue-duration-preflight.md +17 -0
  1100. package/references/video/generation-telemetry.md +18 -0
  1101. package/references/video/multi-shot-framework.md +455 -0
  1102. package/references/video/seedance-skills/01-cinematic.md +1329 -0
  1103. package/references/video/seedance-skills/02-3d-cgi.md +884 -0
  1104. package/references/video/seedance-skills/03-cartoon.md +1668 -0
  1105. package/references/video/seedance-skills/04-comic-to-video.md +1810 -0
  1106. package/references/video/seedance-skills/05-fight-scenes.md +741 -0
  1107. package/references/video/seedance-skills/06-motion-design-ad.md +1145 -0
  1108. package/references/video/seedance-skills/07-ecommerce-ad.md +928 -0
  1109. package/references/video/seedance-skills/08-anime-action.md +1145 -0
  1110. package/references/video/seedance-skills/09-product-360.md +988 -0
  1111. package/references/video/seedance-skills/10-music-video.md +1662 -0
  1112. package/references/video/seedance-skills/11-social-hook.md +1874 -0
  1113. package/references/video/seedance-skills/12-brand-story.md +1151 -0
  1114. package/references/video/seedance-skills/13-fashion-lookbook.md +1236 -0
  1115. package/references/video/seedance-skills/14-food-beverage.md +1110 -0
  1116. package/references/video/seedance-skills/15-real-estate.md +2344 -0
  1117. package/references/video/seedance-transport-payloads.md +144 -0
  1118. package/references/video/seedance-ugc-formulas.md +104 -0
  1119. package/references/video/stage-directors.md +47 -0
  1120. package/references/video/style-template-schema.md +40 -0
  1121. package/references/video/veo-prompting-guide.md +42 -0
  1122. package/schemas/video/artifacts/analyze-output.schema.json +60 -0
  1123. package/schemas/video/artifacts/assemble-report.schema.json +116 -0
  1124. package/schemas/video/artifacts/asset-manifest.schema.json +23 -0
  1125. package/schemas/video/artifacts/batch-queue-manifest.schema.json +59 -0
  1126. package/schemas/video/artifacts/brand-definition.schema.json +93 -0
  1127. package/schemas/video/artifacts/brand-dna.schema.json +55 -0
  1128. package/schemas/video/artifacts/brief.schema.json +13 -0
  1129. package/schemas/video/artifacts/clone-plan.schema.json +44 -0
  1130. package/schemas/video/artifacts/dialogue.schema.json +52 -0
  1131. package/schemas/video/artifacts/environment-assets.schema.json +30 -0
  1132. package/schemas/video/artifacts/execution-plan.schema.json +64 -0
  1133. package/schemas/video/artifacts/execution-report.schema.json +37 -0
  1134. package/schemas/video/artifacts/filmmaking-prompts.schema.json +129 -0
  1135. package/schemas/video/artifacts/flow-characters.schema.json +31 -0
  1136. package/schemas/video/artifacts/flow-voices.schema.json +30 -0
  1137. package/schemas/video/artifacts/motion-overlay-plan.schema.json +50 -0
  1138. package/schemas/video/artifacts/multi-shot-prompt.schema.json +90 -0
  1139. package/schemas/video/artifacts/music-video-config.schema.json +108 -0
  1140. package/schemas/video/artifacts/narration.schema.json +54 -0
  1141. package/schemas/video/artifacts/product-references.schema.json +29 -0
  1142. package/schemas/video/artifacts/project-blueprint.schema.json +236 -0
  1143. package/schemas/video/artifacts/publish-report.schema.json +12 -0
  1144. package/schemas/video/artifacts/reference-sheets.schema.json +85 -0
  1145. package/schemas/video/artifacts/review-report.schema.json +12 -0
  1146. package/schemas/video/artifacts/scene-candidates.schema.json +86 -0
  1147. package/schemas/video/artifacts/scene-selection.schema.json +50 -0
  1148. package/schemas/video/artifacts/seedance-assets.schema.json +31 -0
  1149. package/schemas/video/artifacts/sfx.schema.json +31 -0
  1150. package/schemas/video/artifacts/show-bible.schema.json +66 -0
  1151. package/schemas/video/artifacts/soundtrack.schema.json +39 -0
  1152. package/schemas/video/artifacts/story-bible.schema.json +131 -0
  1153. package/schemas/video/artifacts/storyboard.schema.json +58 -0
  1154. package/schemas/video/artifacts/voice-clones.schema.json +37 -0
  1155. package/schemas/video/brand-profile.schema.json +20 -0
  1156. package/schemas/video/errors.json +48 -0
  1157. package/schemas/video/pipeline-manifest.schema.json +51 -0
  1158. package/skills/README.md +113 -0
  1159. package/skills/ai-director/SKILL.md +140 -0
  1160. package/skills/ai-filmmaking/SKILL.md +239 -0
  1161. package/skills/ai-slop-cleaner/SKILL.md +114 -0
  1162. package/skills/brand-agency/SKILL.md +181 -0
  1163. package/skills/brand-presenter/SKILL.md +114 -0
  1164. package/skills/build-fix/SKILL.md +145 -0
  1165. package/skills/bunty/SKILL.md +41 -0
  1166. package/skills/catalog.json +326 -0
  1167. package/skills/character-ad/SKILL.md +154 -0
  1168. package/skills/character-creator/SKILL.md +303 -0
  1169. package/skills/character-library/README.md +12 -0
  1170. package/skills/character-library/SKILL.md +110 -0
  1171. package/skills/clawbot/SKILL.md +26 -0
  1172. package/skills/concierge/SKILL.md +183 -0
  1173. package/skills/configure-notifications/SKILL.md +286 -0
  1174. package/skills/davendra-presenter/SKILL.md +27 -0
  1175. package/skills/davendra-presenter/assets/davendra_intro_1.jpg +0 -0
  1176. package/skills/davendra-presenter/assets/davendra_intro_2.jpg +0 -0
  1177. package/skills/davendra-presenter/assets/davendra_outro_1.jpg +0 -0
  1178. package/skills/davendra-presenter/assets/davendra_outro_2.jpg +0 -0
  1179. package/skills/deep-interview/SKILL.md +358 -0
  1180. package/skills/deepsearch/SKILL.md +38 -0
  1181. package/skills/doctor/SKILL.md +200 -0
  1182. package/skills/graphify/SKILL.md +616 -0
  1183. package/skills/help/SKILL.md +200 -0
  1184. package/skills/higgsfield-generate/SKILL.md +171 -0
  1185. package/skills/improvement-run/SKILL.md +47 -0
  1186. package/skills/motion-reel/SKILL.md +102 -0
  1187. package/skills/movie-director/SKILL.md +338 -0
  1188. package/skills/multi-shot-prompt/SKILL.md +77 -0
  1189. package/skills/nex-presenter/SKILL.md +25 -0
  1190. package/skills/nex-presenter/assets/logo_manifest.json +26 -0
  1191. package/skills/nex-presenter/assets/logo_source.jpg +0 -0
  1192. package/skills/nex-presenter/assets/nex_brief_intro.mp4 +0 -0
  1193. package/skills/nex-presenter/assets/nex_intro_1.jpg +0 -0
  1194. package/skills/nex-presenter/assets/nex_intro_2.jpg +0 -0
  1195. package/skills/nex-presenter/assets/nex_outro_1.jpg +0 -0
  1196. package/skills/nex-presenter/assets/nex_outro_2.jpg +0 -0
  1197. package/skills/nex-presenter/assets/text_overlay.png +0 -0
  1198. package/skills/note/SKILL.md +62 -0
  1199. package/skills/pipeline/SKILL.md +86 -0
  1200. package/skills/review/SKILL.md +38 -0
  1201. package/skills/runway/SKILL.md +131 -0
  1202. package/skills/seedance-prompts/SKILL.md +194 -0
  1203. package/skills/skill/SKILL.md +835 -0
  1204. package/skills/skills-auditor/SKILL.md +73 -0
  1205. package/skills/studio-mode/SKILL.md +148 -0
  1206. package/skills/ugc/SKILL.md +386 -0
  1207. package/skills/ui-ux-pro-max/SKILL.md +386 -0
  1208. package/skills/video-analyze-template/SKILL.md +77 -0
  1209. package/skills/video-clone-ad/SKILL.md +67 -0
  1210. package/skills/video-framework/SKILL.md +201 -0
  1211. package/skills/video-portfolio-ops/SKILL.md +84 -0
  1212. package/skills/video-post/SKILL.md +97 -0
  1213. package/skills/video-production-handoff/SKILL.md +130 -0
  1214. package/skills/video-release-readiness/SKILL.md +93 -0
  1215. package/skills/video-replicator/SKILL.md +368 -0
  1216. package/skills/video-review-ui-qa/SKILL.md +103 -0
  1217. package/skills/video-storyboard/SKILL.md +71 -0
  1218. package/skills/video-thumbnail-lab/SKILL.md +67 -0
  1219. package/skills/web-clone/SKILL.md +366 -0
  1220. package/skills/worker/SKILL.md +106 -0
  1221. package/skills/youtube-audio/SKILL.md +114 -0
  1222. package/src/video/analyze-output.ts +10 -0
  1223. package/src/video/archive-project.ts +65 -0
  1224. package/src/video/artifact-history.ts +23 -0
  1225. package/src/video/artifact-store.ts +59 -0
  1226. package/src/video/artifact-validation.ts +268 -0
  1227. package/src/video/artifacts.ts +156 -0
  1228. package/src/video/assemble/animate-slides.ts +199 -0
  1229. package/src/video/assemble/animation-styles.json +97 -0
  1230. package/src/video/assemble/animation-styles.ts +54 -0
  1231. package/src/video/assemble/assemble.ts +547 -0
  1232. package/src/video/assemble/audio-concat.ts +165 -0
  1233. package/src/video/assemble/audio-mix-plan.ts +150 -0
  1234. package/src/video/assemble/audio-utils.ts +101 -0
  1235. package/src/video/assemble/cut-segment.ts +121 -0
  1236. package/src/video/assemble/ffmpeg.ts +333 -0
  1237. package/src/video/assemble/gemini-vision-classify.ts +153 -0
  1238. package/src/video/assemble/index.ts +247 -0
  1239. package/src/video/assemble/media-qc.ts +252 -0
  1240. package/src/video/assemble/music.ts +261 -0
  1241. package/src/video/assemble/narration-fit.ts +90 -0
  1242. package/src/video/assemble/overlay.ts +225 -0
  1243. package/src/video/assemble/pdf.ts +169 -0
  1244. package/src/video/assemble/qa-dialogue-lint.ts +129 -0
  1245. package/src/video/assemble/qa-image-filter.ts +147 -0
  1246. package/src/video/assemble/qa-image-vision.ts +154 -0
  1247. package/src/video/assemble/qa-narration-vision.ts +135 -0
  1248. package/src/video/assemble/qa-narration.ts +139 -0
  1249. package/src/video/assemble/stitch-ad.ts +222 -0
  1250. package/src/video/assemble/stitch.ts +918 -0
  1251. package/src/video/assemble/text-card.ts +285 -0
  1252. package/src/video/assemble/title-card.ts +186 -0
  1253. package/src/video/assemble/transcript.ts +240 -0
  1254. package/src/video/assemble/tts-elevenlabs.ts +182 -0
  1255. package/src/video/assemble/tts.ts +227 -0
  1256. package/src/video/assemble/types.ts +45 -0
  1257. package/src/video/assemble/upscale.ts +80 -0
  1258. package/src/video/asset-spec.ts +53 -0
  1259. package/src/video/asset-tag-lookup.ts +51 -0
  1260. package/src/video/atomic-write.ts +7 -0
  1261. package/src/video/audio-platform/native-elevenlabs-sfx.ts +128 -0
  1262. package/src/video/audio-platform/native-elevenlabs-tts.ts +119 -0
  1263. package/src/video/audio-platform/native-flowmusic.ts +167 -0
  1264. package/src/video/audio-platform/native-gemini-tts.ts +226 -0
  1265. package/src/video/audio-platform/native-lyria.ts +231 -0
  1266. package/src/video/audio-platform/native-lyria3.ts +244 -0
  1267. package/src/video/audio-platform/registry.ts +220 -0
  1268. package/src/video/audio-platform/suno-backend.ts +36 -0
  1269. package/src/video/audio-platform/types.ts +138 -0
  1270. package/src/video/batch-queue.ts +685 -0
  1271. package/src/video/blueprint-prompt.ts +118 -0
  1272. package/src/video/brand-definition.ts +287 -0
  1273. package/src/video/brand-dna.ts +478 -0
  1274. package/src/video/brand-prompt.ts +28 -0
  1275. package/src/video/candidate-migrate.ts +171 -0
  1276. package/src/video/category-registry.ts +224 -0
  1277. package/src/video/chain-fallback.ts +53 -0
  1278. package/src/video/character-auto-create.ts +547 -0
  1279. package/src/video/character-consistency.ts +85 -0
  1280. package/src/video/characters.ts +145 -0
  1281. package/src/video/checkpoints.ts +60 -0
  1282. package/src/video/cinema-profile.ts +120 -0
  1283. package/src/video/cinematography.ts +1229 -0
  1284. package/src/video/cli-output.ts +94 -0
  1285. package/src/video/cli-schema.ts +505 -0
  1286. package/src/video/cost-estimate.ts +150 -0
  1287. package/src/video/csv-export.ts +150 -0
  1288. package/src/video/dependencies.ts +64 -0
  1289. package/src/video/dialogue-fit.ts +62 -0
  1290. package/src/video/dialogue.ts +221 -0
  1291. package/src/video/director-defaults.ts +432 -0
  1292. package/src/video/director-preflight.ts +543 -0
  1293. package/src/video/doctor-portfolio.ts +197 -0
  1294. package/src/video/doctor.ts +499 -0
  1295. package/src/video/emotion-cues.ts +36 -0
  1296. package/src/video/environment-assets.ts +69 -0
  1297. package/src/video/environment-auto-create.ts +122 -0
  1298. package/src/video/errors.ts +133 -0
  1299. package/src/video/events.ts +42 -0
  1300. package/src/video/execute-autochain.ts +226 -0
  1301. package/src/video/execute.ts +569 -0
  1302. package/src/video/execution-cancel.ts +103 -0
  1303. package/src/video/execution-plan.ts +178 -0
  1304. package/src/video/execution-profile.ts +162 -0
  1305. package/src/video/execution-runtime.ts +975 -0
  1306. package/src/video/execution-seed.ts +46 -0
  1307. package/src/video/execution-status.ts +736 -0
  1308. package/src/video/filmmaking-prompts.ts +1789 -0
  1309. package/src/video/final-media.ts +118 -0
  1310. package/src/video/finish.ts +255 -0
  1311. package/src/video/flow-character-library.ts +342 -0
  1312. package/src/video/flow-markers.ts +330 -0
  1313. package/src/video/gemini-analyze.ts +387 -0
  1314. package/src/video/gemini-continuity.ts +186 -0
  1315. package/src/video/gemini-judge.ts +300 -0
  1316. package/src/video/gemini-key-pool.ts +153 -0
  1317. package/src/video/gen-image-flow.ts +491 -0
  1318. package/src/video/gen-image.ts +392 -0
  1319. package/src/video/generation-telemetry.ts +282 -0
  1320. package/src/video/http-error-safety.ts +13 -0
  1321. package/src/video/image-dimensions.ts +69 -0
  1322. package/src/video/legacy-import.ts +197 -0
  1323. package/src/video/library-clean.ts +399 -0
  1324. package/src/video/lipsync.ts +200 -0
  1325. package/src/video/media-host.ts +153 -0
  1326. package/src/video/metrics.ts +116 -0
  1327. package/src/video/motion-overlay/analyze-reel.ts +200 -0
  1328. package/src/video/motion-overlay/animate-render.ts +176 -0
  1329. package/src/video/motion-overlay/animate.ts +93 -0
  1330. package/src/video/motion-overlay/avatar-host-transport.ts +458 -0
  1331. package/src/video/motion-overlay/avatar-host.ts +238 -0
  1332. package/src/video/motion-overlay/compose-prompt.ts +247 -0
  1333. package/src/video/motion-overlay/execute.ts +355 -0
  1334. package/src/video/motion-overlay/flow-pack.ts +223 -0
  1335. package/src/video/motion-overlay/ingest.ts +210 -0
  1336. package/src/video/motion-overlay/motifs.ts +223 -0
  1337. package/src/video/motion-overlay/motion-style.ts +162 -0
  1338. package/src/video/motion-overlay/plan.ts +111 -0
  1339. package/src/video/motion-overlay/preview.ts +191 -0
  1340. package/src/video/motion-overlay/render-local.ts +441 -0
  1341. package/src/video/motion-overlay/run.ts +217 -0
  1342. package/src/video/motion-overlay/slice.ts +98 -0
  1343. package/src/video/motion-overlay/transcribe.ts +211 -0
  1344. package/src/video/motion-overlay/types.ts +89 -0
  1345. package/src/video/motion-overlay/v2v-transport.ts +209 -0
  1346. package/src/video/motion-overlay/write.ts +142 -0
  1347. package/src/video/multi-shot-artifact.ts +64 -0
  1348. package/src/video/multi-shot-prompt.ts +608 -0
  1349. package/src/video/music-video.ts +324 -0
  1350. package/src/video/narrate.ts +221 -0
  1351. package/src/video/native-dreamina.ts +638 -0
  1352. package/src/video/native-flow-r2v.ts +279 -0
  1353. package/src/video/native-runway.ts +647 -0
  1354. package/src/video/native-seedance.ts +615 -0
  1355. package/src/video/native-veo.ts +470 -0
  1356. package/src/video/next-actions.ts +247 -0
  1357. package/src/video/obsidian-export.ts +336 -0
  1358. package/src/video/obsidian-sync.ts +649 -0
  1359. package/src/video/obsidian-vault.ts +96 -0
  1360. package/src/video/outfit-prompts.ts +14 -0
  1361. package/src/video/outpaint-keyframe.ts +365 -0
  1362. package/src/video/pipeline-manifest.ts +16 -0
  1363. package/src/video/pipeline-manifests/director.json +46 -0
  1364. package/src/video/pipeline-manifests/storyboard.json +46 -0
  1365. package/src/video/platform-specs.ts +50 -0
  1366. package/src/video/playbooks.ts +34 -0
  1367. package/src/video/post-production.ts +277 -0
  1368. package/src/video/preview-portal/audit.ts +20 -0
  1369. package/src/video/preview-portal/discovery.ts +787 -0
  1370. package/src/video/preview-portal/generate.ts +138 -0
  1371. package/src/video/preview-portal/index.ts +41 -0
  1372. package/src/video/preview-portal/publish.ts +215 -0
  1373. package/src/video/preview-portal/render.ts +865 -0
  1374. package/src/video/preview-portal/shared-assets.ts +455 -0
  1375. package/src/video/preview-portal/templates.ts +112 -0
  1376. package/src/video/preview-portal/types.ts +288 -0
  1377. package/src/video/product-references.ts +43 -0
  1378. package/src/video/project-blueprint.ts +458 -0
  1379. package/src/video/project-index.ts +179 -0
  1380. package/src/video/projects.ts +18 -0
  1381. package/src/video/prompt-guidance.ts +64 -0
  1382. package/src/video/prompt-library.ts +96 -0
  1383. package/src/video/prompt-lint.ts +568 -0
  1384. package/src/video/prompt-quality.ts +635 -0
  1385. package/src/video/prompt-rules.ts +209 -0
  1386. package/src/video/provider-adapter-runner.ts +104 -0
  1387. package/src/video/provider-platform/index.ts +5 -0
  1388. package/src/video/provider-platform/registry.ts +286 -0
  1389. package/src/video/provider-platform/route-capabilities.ts +327 -0
  1390. package/src/video/provider-platform/router.ts +221 -0
  1391. package/src/video/provider-platform/security.ts +29 -0
  1392. package/src/video/provider-platform/telemetry.ts +116 -0
  1393. package/src/video/provider-platform/types.ts +192 -0
  1394. package/src/video/provider-status.ts +199 -0
  1395. package/src/video/providers/dreamina-useapi.ts +345 -0
  1396. package/src/video/providers/flowmusic-useapi.ts +252 -0
  1397. package/src/video/providers/google-flow.ts +110 -0
  1398. package/src/video/providers/public-host.ts +128 -0
  1399. package/src/video/providers/runway-useapi.ts +377 -0
  1400. package/src/video/providers/xskill.ts +304 -0
  1401. package/src/video/readiness.ts +284 -0
  1402. package/src/video/reference-sheet-store.ts +40 -0
  1403. package/src/video/reference-sheets.ts +224 -0
  1404. package/src/video/remix-narrated.ts +92 -0
  1405. package/src/video/report-diff.ts +299 -0
  1406. package/src/video/report-history.ts +122 -0
  1407. package/src/video/report.ts +35 -0
  1408. package/src/video/review-ui.ts +2495 -0
  1409. package/src/video/scene-candidate-store.ts +152 -0
  1410. package/src/video/scene-candidates.ts +195 -0
  1411. package/src/video/scene-selection-store.ts +36 -0
  1412. package/src/video/scene-selection.ts +306 -0
  1413. package/src/video/scheduling.ts +18 -0
  1414. package/src/video/scorecard.ts +90 -0
  1415. package/src/video/seedance-asset-library.ts +259 -0
  1416. package/src/video/seedance-blocks.ts +118 -0
  1417. package/src/video/seedance-chain-host.ts +152 -0
  1418. package/src/video/seedance-content-filter.ts +354 -0
  1419. package/src/video/seedance-skill-loader.ts +83 -0
  1420. package/src/video/sfx.ts +159 -0
  1421. package/src/video/shot-grammar.ts +349 -0
  1422. package/src/video/show-bible.ts +235 -0
  1423. package/src/video/soundtrack.ts +257 -0
  1424. package/src/video/stage-guards.ts +112 -0
  1425. package/src/video/status.ts +342 -0
  1426. package/src/video/story-bible.ts +302 -0
  1427. package/src/video/storyboard-grid.ts +320 -0
  1428. package/src/video/storyboard-markdown.ts +434 -0
  1429. package/src/video/storyboard-still-candidates.ts +82 -0
  1430. package/src/video/storyboard-templates.ts +249 -0
  1431. package/src/video/studio/execute.ts +401 -0
  1432. package/src/video/studio/planner.ts +114 -0
  1433. package/src/video/studio/project-context.ts +25 -0
  1434. package/src/video/studio/recipes.ts +346 -0
  1435. package/src/video/studio/session.ts +24 -0
  1436. package/src/video/studio/types.ts +76 -0
  1437. package/src/video/template-store.ts +241 -0
  1438. package/src/video/timeline.ts +32 -0
  1439. package/src/video/title-overlay.ts +234 -0
  1440. package/src/video/types.ts +465 -0
  1441. package/src/video/veo-subprocess.ts +77 -0
  1442. package/src/video/verify-env.ts +206 -0
  1443. package/src/video/verify-final.ts +65 -0
  1444. package/src/video/video-context.ts +92 -0
  1445. package/src/video/vocal-map.ts +155 -0
  1446. package/src/video/vocal-sync-plan.ts +224 -0
  1447. package/src/video/voice-clone.ts +410 -0
  1448. package/src/video/with-retry.ts +149 -0
  1449. package/src/video/workload.ts +87 -0
  1450. package/src/video/workspace.ts +255 -0
  1451. package/tmp/review-station/index.html +4194 -0
  1452. package/tsconfig.json +19 -0
@@ -0,0 +1,4194 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Video Claw Review Station</title>
7
+ <link rel="icon" href="data:,">
8
+ <style>
9
+ :root {
10
+ color-scheme: light;
11
+ --color-warm-canvas: #fbfaf9;
12
+ --color-stone-surface: #f2f0ed;
13
+ --color-parchment-card: #f8f7f4;
14
+ --color-graphite: #474645;
15
+ --color-charcoal-primary: #343433;
16
+ --color-midnight: #121212;
17
+ --color-ash: #848281;
18
+ --color-fog: #c6c6c6;
19
+ --color-smoke: #a7a7a7;
20
+ --color-ember-orange: #ff3e00;
21
+ --color-meadow-green: #00ca48;
22
+ --color-sky-blue: #0090ff;
23
+ --color-sunburst-yellow: #ffbb26;
24
+ --color-deep-amber: #d48f00;
25
+ --color-ice-blue: #64c6ff;
26
+ --color-flamingo: #ff58ae;
27
+ --shadow-subtle: color(display-p3 0.949 0.941 0.929) 0 0 0 1px inset;
28
+ --shadow-sm: rgba(0, 0, 0, 0.04) 0 1px 6px 0, rgba(0, 0, 0, 0.05) 0 0 24px 0;
29
+ --font-display: Fraunces, Georgia, "Times New Roman", serif;
30
+ --font-inter: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
31
+ --page-max-width: 1200px;
32
+ }
33
+
34
+ * { box-sizing: border-box; }
35
+
36
+ html,
37
+ body {
38
+ max-width: 100%;
39
+ overflow-x: hidden;
40
+ }
41
+
42
+ body {
43
+ margin: 0;
44
+ background: var(--color-warm-canvas);
45
+ color: var(--color-graphite);
46
+ font-family: var(--font-inter);
47
+ font-size: 15px;
48
+ line-height: 1.47;
49
+ letter-spacing: -0.2px;
50
+ }
51
+
52
+ button, input, select, textarea {
53
+ font: inherit;
54
+ }
55
+
56
+ button {
57
+ cursor: pointer;
58
+ }
59
+
60
+ code {
61
+ background: var(--color-stone-surface);
62
+ border-radius: 6px;
63
+ padding: 2px 5px;
64
+ overflow-wrap: anywhere;
65
+ word-break: break-word;
66
+ }
67
+
68
+ .shell {
69
+ width: min(var(--page-max-width), calc(100% - 40px));
70
+ margin: 0 auto;
71
+ }
72
+
73
+ .nav {
74
+ position: sticky;
75
+ top: 0;
76
+ z-index: 20;
77
+ background: rgba(251, 250, 249, 0.94);
78
+ backdrop-filter: blur(12px);
79
+ box-shadow: rgba(0, 0, 0, 0.04) 0 0 0 1px;
80
+ }
81
+
82
+ .nav-inner {
83
+ height: 64px;
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ gap: 20px;
88
+ }
89
+
90
+ .brand {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 10px;
94
+ color: var(--color-charcoal-primary);
95
+ font-weight: 600;
96
+ letter-spacing: -0.2px;
97
+ }
98
+
99
+ .brand-mark {
100
+ width: 34px;
101
+ height: 34px;
102
+ border-radius: 12px;
103
+ background: var(--color-sunburst-yellow);
104
+ box-shadow: var(--shadow-subtle);
105
+ display: grid;
106
+ place-items: center;
107
+ color: var(--color-midnight);
108
+ font-weight: 600;
109
+ }
110
+
111
+ .nav-links {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 18px;
115
+ color: var(--color-charcoal-primary);
116
+ font-size: 14px;
117
+ font-weight: 500;
118
+ letter-spacing: -0.18px;
119
+ }
120
+
121
+ .nav-link {
122
+ border: 0;
123
+ background: transparent;
124
+ color: inherit;
125
+ padding: 4px 0;
126
+ }
127
+
128
+ .nav-actions {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 8px;
132
+ }
133
+
134
+ .btn {
135
+ min-height: 38px;
136
+ border-radius: 32px;
137
+ border: 0;
138
+ padding: 0 15px;
139
+ font-size: 14px;
140
+ font-weight: 600;
141
+ letter-spacing: -0.18px;
142
+ transition: transform 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
143
+ }
144
+
145
+ .btn:hover {
146
+ transform: translateY(-1px);
147
+ }
148
+
149
+ .btn:disabled {
150
+ cursor: not-allowed;
151
+ opacity: 0.46;
152
+ transform: none;
153
+ }
154
+
155
+ .btn-primary {
156
+ background: var(--color-midnight);
157
+ color: #fff;
158
+ }
159
+
160
+ .btn-primary:hover {
161
+ background: var(--color-charcoal-primary);
162
+ }
163
+
164
+ .btn-secondary {
165
+ background: #f6f4ef;
166
+ color: var(--color-midnight);
167
+ }
168
+
169
+ .btn-ghost {
170
+ background: transparent;
171
+ color: var(--color-ember-orange);
172
+ padding: 0;
173
+ min-height: auto;
174
+ }
175
+
176
+ .btn-outline {
177
+ background: transparent;
178
+ border: 1px solid var(--color-graphite);
179
+ color: var(--color-graphite);
180
+ border-radius: 12px;
181
+ }
182
+
183
+ .hero {
184
+ position: relative;
185
+ padding: 28px 0 18px;
186
+ display: flex;
187
+ align-items: flex-end;
188
+ justify-content: space-between;
189
+ gap: 24px;
190
+ }
191
+
192
+ .hero h1 {
193
+ max-width: 640px;
194
+ margin: 0;
195
+ color: var(--color-charcoal-primary);
196
+ font-family: var(--font-display);
197
+ font-size: 38px;
198
+ font-weight: 500;
199
+ letter-spacing: 0;
200
+ line-height: 1.08;
201
+ }
202
+
203
+ .hero p {
204
+ max-width: 690px;
205
+ margin: 10px 0 0;
206
+ color: var(--color-graphite);
207
+ font-size: 15px;
208
+ letter-spacing: 0;
209
+ }
210
+
211
+ .hero-actions {
212
+ margin-top: 0;
213
+ display: flex;
214
+ justify-content: flex-end;
215
+ gap: 10px;
216
+ flex-wrap: wrap;
217
+ min-width: 360px;
218
+ }
219
+
220
+ .stage-rail {
221
+ display: grid;
222
+ grid-template-columns: repeat(6, minmax(0, 1fr));
223
+ gap: 10px;
224
+ margin: 8px 0 28px;
225
+ }
226
+
227
+ .stage-button {
228
+ text-align: left;
229
+ background: #fff;
230
+ color: var(--color-charcoal-primary);
231
+ box-shadow: var(--shadow-subtle);
232
+ border: 0;
233
+ border-radius: 10px;
234
+ padding: 14px;
235
+ min-height: 104px;
236
+ }
237
+
238
+ .stage-button.active {
239
+ background: var(--color-parchment-card);
240
+ box-shadow: var(--shadow-subtle), var(--shadow-sm);
241
+ }
242
+
243
+ .stage-button.done .stage-number {
244
+ background: var(--color-meadow-green);
245
+ color: #fff;
246
+ }
247
+
248
+ .stage-number {
249
+ display: inline-grid;
250
+ place-items: center;
251
+ width: 28px;
252
+ height: 28px;
253
+ border-radius: 40px;
254
+ background: var(--color-stone-surface);
255
+ color: var(--color-graphite);
256
+ font-weight: 600;
257
+ margin-bottom: 10px;
258
+ }
259
+
260
+ .stage-title {
261
+ display: block;
262
+ font-weight: 600;
263
+ font-size: 14px;
264
+ letter-spacing: -0.18px;
265
+ }
266
+
267
+ .stage-sub {
268
+ display: block;
269
+ color: var(--color-ash);
270
+ font-size: 12px;
271
+ line-height: 1.35;
272
+ margin-top: 3px;
273
+ }
274
+
275
+ .layout {
276
+ display: grid;
277
+ grid-template-columns: minmax(0, 1fr) 350px;
278
+ gap: 20px;
279
+ align-items: start;
280
+ padding-bottom: 80px;
281
+ }
282
+
283
+ .layout > *,
284
+ .main-card,
285
+ .side-card,
286
+ .item-card,
287
+ .stat-card,
288
+ .empty-state,
289
+ .quality-panel,
290
+ .details-panel,
291
+ .handoff,
292
+ .ledger,
293
+ .cards-grid,
294
+ .small-grid,
295
+ .role-grid,
296
+ .candidate-gallery,
297
+ .candidate-card,
298
+ .scene-row,
299
+ .still-form {
300
+ min-width: 0;
301
+ }
302
+
303
+ .main-card,
304
+ .side-card,
305
+ .item-card,
306
+ .stat-card,
307
+ .empty-state {
308
+ background: #fff;
309
+ box-shadow: var(--shadow-subtle);
310
+ border-radius: 10px;
311
+ }
312
+
313
+ .main-card,
314
+ .side-card {
315
+ padding: 32px;
316
+ }
317
+
318
+ .section-kicker {
319
+ color: var(--color-ember-orange);
320
+ font-size: 13px;
321
+ font-weight: 600;
322
+ letter-spacing: -0.17px;
323
+ margin-bottom: 8px;
324
+ }
325
+
326
+ .step-meta {
327
+ display: inline-flex;
328
+ align-items: center;
329
+ gap: 8px;
330
+ margin-bottom: 12px;
331
+ color: var(--color-ash);
332
+ font-size: 13px;
333
+ font-weight: 500;
334
+ }
335
+
336
+ .step-pill {
337
+ color: #fff;
338
+ background: var(--color-midnight);
339
+ border-radius: 32px;
340
+ padding: 5px 10px;
341
+ }
342
+
343
+ .section-title {
344
+ margin: 0;
345
+ color: var(--color-midnight);
346
+ font-size: 44px;
347
+ line-height: 1.09;
348
+ letter-spacing: -1.14px;
349
+ font-weight: 600;
350
+ }
351
+
352
+ .section-copy {
353
+ margin: 12px 0 0;
354
+ max-width: 760px;
355
+ color: var(--color-graphite);
356
+ font-size: 17px;
357
+ letter-spacing: -0.22px;
358
+ }
359
+
360
+ .now-box {
361
+ margin-top: 20px;
362
+ display: grid;
363
+ grid-template-columns: minmax(0, 1fr) auto;
364
+ gap: 18px;
365
+ align-items: center;
366
+ background: var(--color-parchment-card);
367
+ border-radius: 12px;
368
+ padding: 18px;
369
+ }
370
+
371
+ .now-box.blocked {
372
+ background: #fff4ec;
373
+ box-shadow: #ffd6c7 0 0 0 1px inset;
374
+ }
375
+
376
+ .now-box.ready {
377
+ background: #f0fff4;
378
+ box-shadow: #c7f4d2 0 0 0 1px inset;
379
+ }
380
+
381
+ .now-box strong {
382
+ display: block;
383
+ color: var(--color-midnight);
384
+ font-size: 19px;
385
+ letter-spacing: -0.25px;
386
+ }
387
+
388
+ .now-box span {
389
+ color: var(--color-graphite);
390
+ }
391
+
392
+ .action-feedback {
393
+ margin-top: 12px;
394
+ min-height: 42px;
395
+ display: flex;
396
+ align-items: center;
397
+ border-radius: 10px;
398
+ padding: 10px 14px;
399
+ background: #f6f4ef;
400
+ color: var(--color-graphite);
401
+ font-size: 14px;
402
+ font-weight: 500;
403
+ line-height: 1.35;
404
+ }
405
+
406
+ .action-feedback.ready {
407
+ background: #f0fff4;
408
+ box-shadow: #c7f4d2 0 0 0 1px inset;
409
+ color: var(--color-charcoal-primary);
410
+ }
411
+
412
+ .action-feedback.blocked {
413
+ background: #fff4ec;
414
+ box-shadow: #ffd6c7 0 0 0 1px inset;
415
+ color: var(--color-charcoal-primary);
416
+ }
417
+
418
+ .missing-list {
419
+ margin: 10px 0 0;
420
+ padding-left: 18px;
421
+ color: var(--color-graphite);
422
+ }
423
+
424
+ .missing-list li {
425
+ margin: 3px 0;
426
+ }
427
+
428
+ .controls {
429
+ display: grid;
430
+ grid-template-columns: minmax(0, 1fr) 180px 120px;
431
+ gap: 10px;
432
+ margin: 24px 0;
433
+ }
434
+
435
+ input,
436
+ select,
437
+ textarea {
438
+ width: 100%;
439
+ background: var(--color-parchment-card);
440
+ border: 0;
441
+ box-shadow: var(--shadow-subtle);
442
+ border-radius: 10px;
443
+ color: var(--color-charcoal-primary);
444
+ padding: 12px 13px;
445
+ outline: none;
446
+ }
447
+
448
+ textarea {
449
+ min-height: 108px;
450
+ resize: vertical;
451
+ }
452
+
453
+ .stats-grid,
454
+ .cards-grid,
455
+ .small-grid,
456
+ .role-grid {
457
+ display: grid;
458
+ gap: 12px;
459
+ }
460
+
461
+ .stats-grid {
462
+ grid-template-columns: repeat(4, minmax(0, 1fr));
463
+ margin-top: 20px;
464
+ }
465
+
466
+ .cards-grid {
467
+ grid-template-columns: repeat(3, minmax(0, 1fr));
468
+ }
469
+
470
+ .small-grid {
471
+ grid-template-columns: repeat(2, minmax(0, 1fr));
472
+ }
473
+
474
+ .role-grid {
475
+ grid-template-columns: repeat(2, minmax(0, 1fr));
476
+ }
477
+
478
+ .stat-card {
479
+ padding: 18px;
480
+ }
481
+
482
+ .stat-card strong {
483
+ display: block;
484
+ color: var(--color-midnight);
485
+ font-size: 32px;
486
+ line-height: 1;
487
+ letter-spacing: -0.9px;
488
+ }
489
+
490
+ .stat-card span {
491
+ display: block;
492
+ margin-top: 8px;
493
+ color: var(--color-ash);
494
+ font-size: 13px;
495
+ }
496
+
497
+ .item-card {
498
+ overflow: hidden;
499
+ }
500
+
501
+ .item-card.selected {
502
+ outline: 3px solid var(--color-meadow-green);
503
+ outline-offset: -3px;
504
+ }
505
+
506
+ .media {
507
+ height: 172px;
508
+ background: var(--color-parchment-card);
509
+ overflow: hidden;
510
+ display: grid;
511
+ place-items: center;
512
+ border-radius: 10px 10px 0 0;
513
+ }
514
+
515
+ .media img,
516
+ .media video {
517
+ width: 100%;
518
+ height: 100%;
519
+ object-fit: cover;
520
+ display: block;
521
+ }
522
+
523
+ .paper-illustration {
524
+ position: relative;
525
+ width: 100%;
526
+ height: 100%;
527
+ display: grid;
528
+ place-items: center;
529
+ background:
530
+ radial-gradient(circle at 22% 26%, rgba(255, 187, 38, .32), transparent 24%),
531
+ radial-gradient(circle at 76% 34%, rgba(0, 144, 255, .22), transparent 22%),
532
+ var(--color-parchment-card);
533
+ }
534
+
535
+ .mini-blob {
536
+ width: 76px;
537
+ height: 62px;
538
+ border-radius: 55% 45% 46% 54% / 62% 40% 60% 38%;
539
+ background: var(--fill, var(--color-sky-blue));
540
+ position: relative;
541
+ box-shadow: inset 0 -7px 0 rgba(0, 0, 0, 0.08);
542
+ }
543
+
544
+ .mini-blob::before,
545
+ .mini-blob::after {
546
+ content: "";
547
+ position: absolute;
548
+ background: var(--color-midnight);
549
+ }
550
+
551
+ .mini-blob::before {
552
+ width: 6px;
553
+ height: 6px;
554
+ border-radius: 999px;
555
+ left: 24px;
556
+ top: 22px;
557
+ box-shadow: 20px 0 0 var(--color-midnight);
558
+ }
559
+
560
+ .mini-blob::after {
561
+ width: 24px;
562
+ height: 10px;
563
+ border-radius: 0 0 999px 999px;
564
+ border: 3px solid var(--color-midnight);
565
+ border-top: 0;
566
+ background: transparent;
567
+ left: 26px;
568
+ top: 35px;
569
+ }
570
+
571
+ .item-body {
572
+ padding: 18px;
573
+ }
574
+
575
+ .item-body h3 {
576
+ margin: 0;
577
+ color: var(--color-charcoal-primary);
578
+ font-size: 19px;
579
+ line-height: 1.38;
580
+ letter-spacing: -0.25px;
581
+ }
582
+
583
+ .item-body p {
584
+ margin: 8px 0 14px;
585
+ color: var(--color-graphite);
586
+ font-size: 15px;
587
+ }
588
+
589
+ .chips {
590
+ display: flex;
591
+ gap: 6px;
592
+ flex-wrap: wrap;
593
+ margin-bottom: 14px;
594
+ }
595
+
596
+ .chip {
597
+ border-radius: 6px;
598
+ background: var(--color-stone-surface);
599
+ color: var(--color-graphite);
600
+ padding: 4px 8px;
601
+ font-size: 12px;
602
+ line-height: 1.3;
603
+ }
604
+
605
+ .card-actions {
606
+ display: flex;
607
+ gap: 8px;
608
+ flex-wrap: wrap;
609
+ }
610
+
611
+ .empty-state {
612
+ padding: 24px;
613
+ display: grid;
614
+ grid-template-columns: 86px minmax(0, 1fr);
615
+ gap: 18px;
616
+ align-items: center;
617
+ background: var(--color-parchment-card);
618
+ }
619
+
620
+ .empty-state strong {
621
+ display: block;
622
+ color: var(--color-midnight);
623
+ font-size: 23px;
624
+ line-height: 1.2;
625
+ letter-spacing: -0.44px;
626
+ }
627
+
628
+ .empty-state p {
629
+ margin: 6px 0 14px;
630
+ }
631
+
632
+ .scene-list {
633
+ display: grid;
634
+ gap: 10px;
635
+ }
636
+
637
+ .scene-row {
638
+ display: grid;
639
+ grid-template-columns: 86px minmax(0, 1fr) auto;
640
+ gap: 14px;
641
+ align-items: center;
642
+ padding: 16px;
643
+ border-radius: 10px;
644
+ background: var(--color-parchment-card);
645
+ }
646
+
647
+ .scene-row.blocked {
648
+ background: #fff;
649
+ box-shadow: var(--shadow-subtle);
650
+ }
651
+
652
+ .scene-row strong {
653
+ color: var(--color-midnight);
654
+ }
655
+
656
+ .scene-row span {
657
+ color: var(--color-ash);
658
+ font-size: 13px;
659
+ }
660
+
661
+ .storyboard-scene {
662
+ display: grid;
663
+ gap: 16px;
664
+ }
665
+
666
+ .storyboard-dashboard-wrap {
667
+ margin: 22px 0;
668
+ background: #fff;
669
+ box-shadow: var(--shadow-subtle);
670
+ border-radius: 10px;
671
+ padding: 16px;
672
+ }
673
+
674
+ .storyboard-dashboard-head {
675
+ display: flex;
676
+ justify-content: space-between;
677
+ gap: 14px;
678
+ align-items: flex-start;
679
+ margin-bottom: 14px;
680
+ }
681
+
682
+ .storyboard-dashboard-head h3 {
683
+ margin: 0;
684
+ color: var(--color-midnight);
685
+ font-size: 23px;
686
+ line-height: 1.2;
687
+ letter-spacing: -0.44px;
688
+ }
689
+
690
+ .storyboard-dashboard-head p {
691
+ margin: 6px 0 0;
692
+ color: var(--color-graphite);
693
+ }
694
+
695
+ .storyboard-dashboard {
696
+ display: grid;
697
+ grid-template-columns: repeat(4, minmax(0, 1fr));
698
+ gap: 12px;
699
+ }
700
+
701
+ .storyboard-shot-card {
702
+ display: grid;
703
+ gap: 10px;
704
+ text-align: left;
705
+ border: 0;
706
+ border-radius: 10px;
707
+ background: #fff;
708
+ box-shadow: var(--shadow-subtle);
709
+ padding: 10px;
710
+ cursor: pointer;
711
+ color: var(--color-charcoal-primary);
712
+ }
713
+
714
+ .storyboard-shot-card.active {
715
+ outline: 3px solid var(--color-midnight);
716
+ outline-offset: -3px;
717
+ }
718
+
719
+ .storyboard-shot-card.done {
720
+ background: var(--color-parchment-card);
721
+ }
722
+
723
+ .storyboard-shot-thumb,
724
+ .scene-switch-thumb {
725
+ overflow: hidden;
726
+ border-radius: 8px;
727
+ background: var(--color-parchment-card);
728
+ aspect-ratio: 16 / 9;
729
+ }
730
+
731
+ .storyboard-shot-thumb img,
732
+ .scene-switch-thumb img {
733
+ width: 100%;
734
+ height: 100%;
735
+ object-fit: cover;
736
+ display: block;
737
+ }
738
+
739
+ .storyboard-shot-thumb .media,
740
+ .scene-switch-thumb .media {
741
+ height: 100%;
742
+ border-radius: 0;
743
+ }
744
+
745
+ .storyboard-shot-card strong {
746
+ color: var(--color-midnight);
747
+ font-size: 14px;
748
+ }
749
+
750
+ .storyboard-shot-card span {
751
+ color: var(--color-ash);
752
+ font-size: 12px;
753
+ line-height: 1.35;
754
+ }
755
+
756
+ .shot-card-footer {
757
+ display: flex;
758
+ justify-content: space-between;
759
+ gap: 8px;
760
+ align-items: center;
761
+ }
762
+
763
+ .shot-card-footer b {
764
+ color: var(--color-midnight);
765
+ font-size: 12px;
766
+ }
767
+
768
+ .selected-still-panel {
769
+ display: grid;
770
+ grid-template-columns: 260px minmax(0, 1fr);
771
+ gap: 16px;
772
+ align-items: start;
773
+ margin: 14px 0;
774
+ padding: 14px;
775
+ border-radius: 10px;
776
+ background: var(--color-parchment-card);
777
+ }
778
+
779
+ .selected-still-panel .storyboard-shot-thumb {
780
+ background: #fff;
781
+ }
782
+
783
+ .selected-still-panel h4 {
784
+ margin: 0;
785
+ color: var(--color-midnight);
786
+ font-size: 19px;
787
+ line-height: 1.25;
788
+ letter-spacing: -0.25px;
789
+ }
790
+
791
+ .selected-still-panel p {
792
+ margin: 6px 0 12px;
793
+ color: var(--color-graphite);
794
+ }
795
+
796
+ .scene-review-switcher {
797
+ display: grid;
798
+ grid-template-columns: repeat(4, minmax(0, 1fr));
799
+ gap: 10px;
800
+ margin: 18px 0;
801
+ }
802
+
803
+ .scene-switch {
804
+ text-align: left;
805
+ border: 0;
806
+ border-radius: 10px;
807
+ background: #fff;
808
+ box-shadow: var(--shadow-subtle);
809
+ padding: 14px;
810
+ cursor: pointer;
811
+ color: var(--color-charcoal-primary);
812
+ }
813
+
814
+ .scene-switch-thumb {
815
+ margin-bottom: 10px;
816
+ }
817
+
818
+ .scene-switch.active {
819
+ outline: 3px solid var(--color-midnight);
820
+ outline-offset: -3px;
821
+ }
822
+
823
+ .scene-switch.done {
824
+ background: var(--color-parchment-card);
825
+ }
826
+
827
+ .scene-switch strong,
828
+ .scene-switch span {
829
+ display: block;
830
+ }
831
+
832
+ .scene-switch strong {
833
+ color: var(--color-midnight);
834
+ font-size: 14px;
835
+ margin-bottom: 4px;
836
+ }
837
+
838
+ .scene-switch span {
839
+ color: var(--color-ash);
840
+ font-size: 12px;
841
+ line-height: 1.35;
842
+ }
843
+
844
+ .operator-steps {
845
+ display: grid;
846
+ grid-template-columns: repeat(4, minmax(0, 1fr));
847
+ gap: 10px;
848
+ margin: 18px 0;
849
+ }
850
+
851
+ .operator-step {
852
+ background: #fff;
853
+ box-shadow: var(--shadow-subtle);
854
+ border-radius: 10px;
855
+ padding: 14px;
856
+ }
857
+
858
+ .operator-step strong {
859
+ display: block;
860
+ color: var(--color-midnight);
861
+ font-size: 14px;
862
+ margin-bottom: 4px;
863
+ }
864
+
865
+ .operator-step span {
866
+ color: var(--color-ash);
867
+ font-size: 12px;
868
+ line-height: 1.35;
869
+ }
870
+
871
+ .scene-review-card {
872
+ background: #fff;
873
+ box-shadow: var(--shadow-subtle);
874
+ border-radius: 10px;
875
+ padding: 16px;
876
+ }
877
+
878
+ .scene-review-header {
879
+ display: grid;
880
+ grid-template-columns: minmax(0, 1fr) auto;
881
+ gap: 14px;
882
+ align-items: start;
883
+ margin-bottom: 12px;
884
+ }
885
+
886
+ .scene-review-header h3 {
887
+ margin: 0;
888
+ color: var(--color-midnight);
889
+ font-size: 23px;
890
+ line-height: 1.2;
891
+ letter-spacing: -0.44px;
892
+ }
893
+
894
+ .scene-review-header p {
895
+ margin: 6px 0 0;
896
+ color: var(--color-graphite);
897
+ }
898
+
899
+ .candidate-section {
900
+ margin-top: 14px;
901
+ padding-top: 14px;
902
+ border-top: 1px solid var(--color-stone-surface);
903
+ }
904
+
905
+ .candidate-section-header {
906
+ display: flex;
907
+ align-items: center;
908
+ justify-content: space-between;
909
+ gap: 12px;
910
+ margin-bottom: 10px;
911
+ }
912
+
913
+ .candidate-section-header strong {
914
+ color: var(--color-midnight);
915
+ font-size: 15px;
916
+ }
917
+
918
+ .candidate-section-header span {
919
+ color: var(--color-ash);
920
+ font-size: 12px;
921
+ }
922
+
923
+ .candidate-gallery {
924
+ display: grid;
925
+ grid-template-columns: repeat(2, minmax(0, 1fr));
926
+ gap: 10px;
927
+ }
928
+
929
+ .candidate-card {
930
+ display: grid;
931
+ grid-template-columns: 132px minmax(0, 1fr);
932
+ gap: 12px;
933
+ background: #fff;
934
+ border-radius: 10px;
935
+ box-shadow: var(--shadow-subtle);
936
+ padding: 10px;
937
+ }
938
+
939
+ .candidate-card.selected {
940
+ outline: 3px solid var(--color-meadow-green);
941
+ outline-offset: -3px;
942
+ }
943
+
944
+ .candidate-card.rejected {
945
+ opacity: 0.62;
946
+ background: var(--color-parchment-card);
947
+ }
948
+
949
+ .candidate-card.pending {
950
+ background: var(--color-parchment-card);
951
+ }
952
+
953
+ .candidate-card.needs-review {
954
+ outline: 3px solid var(--color-ember-orange);
955
+ outline-offset: -3px;
956
+ }
957
+
958
+ .candidate-thumb {
959
+ min-height: 86px;
960
+ border-radius: 8px;
961
+ background: var(--color-parchment-card);
962
+ overflow: hidden;
963
+ }
964
+
965
+ .candidate-thumb img,
966
+ .candidate-thumb video {
967
+ width: 100%;
968
+ height: 100%;
969
+ min-height: 86px;
970
+ object-fit: cover;
971
+ display: block;
972
+ }
973
+
974
+ .candidate-video-placeholder {
975
+ min-height: 86px;
976
+ display: grid;
977
+ place-items: center;
978
+ padding: 10px;
979
+ border-radius: 8px;
980
+ background: var(--color-midnight);
981
+ color: #fff;
982
+ font-size: 12px;
983
+ text-align: center;
984
+ }
985
+
986
+ .candidate-video-placeholder a {
987
+ color: #fff;
988
+ font-weight: 600;
989
+ }
990
+
991
+ .candidate-body strong {
992
+ display: block;
993
+ color: var(--color-midnight);
994
+ font-size: 14px;
995
+ }
996
+
997
+ .candidate-body p {
998
+ margin: 4px 0 10px;
999
+ color: var(--color-graphite);
1000
+ font-size: 13px;
1001
+ line-height: 1.35;
1002
+ }
1003
+
1004
+ .candidate-warning {
1005
+ margin: 8px 0 10px;
1006
+ color: var(--color-ember-orange);
1007
+ font-size: 12px;
1008
+ line-height: 1.35;
1009
+ }
1010
+
1011
+ .empty-inline {
1012
+ border-radius: 10px;
1013
+ background: #fff;
1014
+ box-shadow: var(--shadow-subtle);
1015
+ padding: 12px 14px;
1016
+ color: var(--color-ash);
1017
+ font-size: 13px;
1018
+ }
1019
+
1020
+ .still-form {
1021
+ display: grid;
1022
+ grid-template-columns: minmax(0, 1.2fr) 120px;
1023
+ gap: 10px;
1024
+ padding: 12px;
1025
+ border-radius: 10px;
1026
+ background: #fff;
1027
+ box-shadow: var(--shadow-subtle);
1028
+ }
1029
+
1030
+ .still-form textarea {
1031
+ grid-column: 1 / -1;
1032
+ min-height: 76px;
1033
+ }
1034
+
1035
+ .still-form .card-actions {
1036
+ grid-column: 1 / -1;
1037
+ }
1038
+
1039
+ .still-form label {
1040
+ display: block;
1041
+ }
1042
+
1043
+ .still-workbench {
1044
+ margin-top: 14px;
1045
+ background: #fff;
1046
+ box-shadow: var(--shadow-subtle);
1047
+ border-radius: 10px;
1048
+ padding: 12px;
1049
+ }
1050
+
1051
+ .still-workbench summary {
1052
+ cursor: pointer;
1053
+ color: var(--color-midnight);
1054
+ font-weight: 600;
1055
+ }
1056
+
1057
+ .still-workbench .still-form {
1058
+ margin-top: 12px;
1059
+ box-shadow: none;
1060
+ padding: 0;
1061
+ }
1062
+
1063
+ .form-label {
1064
+ display: block;
1065
+ color: var(--color-midnight);
1066
+ font-size: 13px;
1067
+ font-weight: 600;
1068
+ margin-bottom: 6px;
1069
+ }
1070
+
1071
+ .role-card {
1072
+ padding: 16px;
1073
+ border-radius: 10px;
1074
+ background: var(--color-parchment-card);
1075
+ }
1076
+
1077
+ .role-card strong {
1078
+ display: block;
1079
+ color: var(--color-sky-blue);
1080
+ margin-bottom: 6px;
1081
+ }
1082
+
1083
+ .side-card {
1084
+ position: sticky;
1085
+ top: 88px;
1086
+ }
1087
+
1088
+ .side-card h3 {
1089
+ margin: 0;
1090
+ color: var(--color-midnight);
1091
+ font-size: 23px;
1092
+ line-height: 1.2;
1093
+ letter-spacing: -0.44px;
1094
+ }
1095
+
1096
+ .side-card p {
1097
+ color: var(--color-graphite);
1098
+ }
1099
+
1100
+ .ledger {
1101
+ margin-top: 18px;
1102
+ background: var(--color-parchment-card);
1103
+ border-radius: 12px;
1104
+ padding: 14px;
1105
+ max-height: 360px;
1106
+ overflow: auto;
1107
+ }
1108
+
1109
+ .ledger summary {
1110
+ cursor: pointer;
1111
+ color: var(--color-midnight);
1112
+ font-size: 13px;
1113
+ font-weight: 600;
1114
+ }
1115
+
1116
+ .ledger pre {
1117
+ margin-top: 12px;
1118
+ }
1119
+
1120
+ .gate-checks {
1121
+ display: grid;
1122
+ gap: 8px;
1123
+ margin: 16px 0;
1124
+ }
1125
+
1126
+ .gate-check {
1127
+ display: grid;
1128
+ grid-template-columns: 26px minmax(0, 1fr);
1129
+ gap: 10px;
1130
+ align-items: center;
1131
+ background: var(--color-parchment-card);
1132
+ border-radius: 10px;
1133
+ padding: 10px;
1134
+ }
1135
+
1136
+ .gate-dot {
1137
+ width: 26px;
1138
+ height: 26px;
1139
+ display: grid;
1140
+ place-items: center;
1141
+ border-radius: 40px;
1142
+ background: var(--color-stone-surface);
1143
+ color: var(--color-ash);
1144
+ font-weight: 600;
1145
+ font-size: 12px;
1146
+ }
1147
+
1148
+ .gate-check.done .gate-dot {
1149
+ background: var(--color-meadow-green);
1150
+ color: #fff;
1151
+ }
1152
+
1153
+ .gate-check span {
1154
+ color: var(--color-charcoal-primary);
1155
+ font-size: 13px;
1156
+ font-weight: 500;
1157
+ }
1158
+
1159
+ .handoff {
1160
+ margin: 16px 0;
1161
+ background: var(--color-parchment-card);
1162
+ border-radius: 12px;
1163
+ padding: 14px;
1164
+ box-shadow: var(--shadow-subtle);
1165
+ overflow-wrap: anywhere;
1166
+ }
1167
+
1168
+ .handoff strong {
1169
+ display: block;
1170
+ color: var(--color-midnight);
1171
+ font-size: 14px;
1172
+ margin-bottom: 6px;
1173
+ }
1174
+
1175
+ .handoff p {
1176
+ margin: 0 0 10px;
1177
+ color: var(--color-graphite);
1178
+ font-size: 13px;
1179
+ }
1180
+
1181
+ .handoff code {
1182
+ display: block;
1183
+ white-space: pre-wrap;
1184
+ word-break: break-word;
1185
+ font-size: 12px;
1186
+ line-height: 1.45;
1187
+ }
1188
+
1189
+ .handoff details {
1190
+ margin: 10px 0;
1191
+ }
1192
+
1193
+ .handoff summary {
1194
+ cursor: pointer;
1195
+ color: var(--color-midnight);
1196
+ font-size: 13px;
1197
+ font-weight: 600;
1198
+ }
1199
+
1200
+ .visual-handoff-strip,
1201
+ .handoff-mini-strip {
1202
+ display: grid;
1203
+ gap: 10px;
1204
+ }
1205
+
1206
+ .visual-handoff-strip {
1207
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1208
+ margin-top: 14px;
1209
+ }
1210
+
1211
+ .visual-handoff-shot,
1212
+ .handoff-mini-shot {
1213
+ display: grid;
1214
+ gap: 8px;
1215
+ text-align: left;
1216
+ border: 0;
1217
+ border-radius: 10px;
1218
+ background: var(--color-parchment-card);
1219
+ box-shadow: var(--shadow-subtle);
1220
+ color: var(--color-charcoal-primary);
1221
+ cursor: pointer;
1222
+ }
1223
+
1224
+ .visual-handoff-shot {
1225
+ padding: 10px;
1226
+ }
1227
+
1228
+ .handoff-mini-strip {
1229
+ margin: 12px 0;
1230
+ }
1231
+
1232
+ .handoff-mini-shot {
1233
+ grid-template-columns: 74px minmax(0, 1fr);
1234
+ align-items: center;
1235
+ padding: 8px;
1236
+ }
1237
+
1238
+ .handoff-shot-thumb,
1239
+ .handoff-mini-thumb {
1240
+ overflow: hidden;
1241
+ border-radius: 8px;
1242
+ background: #fff;
1243
+ aspect-ratio: 16 / 9;
1244
+ }
1245
+
1246
+ .handoff-mini-thumb {
1247
+ width: 74px;
1248
+ }
1249
+
1250
+ .handoff-shot-thumb img,
1251
+ .handoff-mini-thumb img {
1252
+ display: block;
1253
+ width: 100%;
1254
+ height: 100%;
1255
+ object-fit: cover;
1256
+ }
1257
+
1258
+ .visual-handoff-shot strong,
1259
+ .handoff-mini-shot strong {
1260
+ display: block;
1261
+ color: var(--color-midnight);
1262
+ font-size: 13px;
1263
+ line-height: 1.25;
1264
+ }
1265
+
1266
+ .visual-handoff-shot span,
1267
+ .handoff-mini-shot span {
1268
+ display: block;
1269
+ color: var(--color-ash);
1270
+ font-size: 12px;
1271
+ line-height: 1.35;
1272
+ }
1273
+
1274
+ .handoff-video {
1275
+ margin: 12px 0;
1276
+ overflow: hidden;
1277
+ border-radius: 10px;
1278
+ background: var(--color-midnight);
1279
+ box-shadow: var(--shadow-subtle);
1280
+ }
1281
+
1282
+ .handoff-video video {
1283
+ width: 100%;
1284
+ display: block;
1285
+ aspect-ratio: 16 / 9;
1286
+ background: var(--color-midnight);
1287
+ }
1288
+
1289
+ .handoff-summary {
1290
+ margin: 18px 0 24px;
1291
+ display: grid;
1292
+ gap: 12px;
1293
+ }
1294
+
1295
+ .summary-panel {
1296
+ background: #fff;
1297
+ box-shadow: var(--shadow-subtle);
1298
+ border-radius: 10px;
1299
+ padding: 18px;
1300
+ }
1301
+
1302
+ .summary-panel h3,
1303
+ .scene-brief h3 {
1304
+ margin: 0;
1305
+ color: var(--color-midnight);
1306
+ font-size: 23px;
1307
+ line-height: 1.2;
1308
+ letter-spacing: -0.44px;
1309
+ }
1310
+
1311
+ .summary-panel p,
1312
+ .scene-brief p {
1313
+ margin: 8px 0 0;
1314
+ color: var(--color-graphite);
1315
+ }
1316
+
1317
+ .lesson-list {
1318
+ margin: 12px 0 0;
1319
+ padding-left: 18px;
1320
+ }
1321
+
1322
+ .lesson-list li {
1323
+ margin: 6px 0;
1324
+ }
1325
+
1326
+ .scene-brief-grid {
1327
+ display: grid;
1328
+ gap: 12px;
1329
+ }
1330
+
1331
+ .scene-brief {
1332
+ display: grid;
1333
+ grid-template-columns: 180px minmax(0, 1fr);
1334
+ gap: 14px;
1335
+ align-items: start;
1336
+ background: var(--color-parchment-card);
1337
+ border-radius: 10px;
1338
+ padding: 12px;
1339
+ }
1340
+
1341
+ .scene-brief .media {
1342
+ height: 102px;
1343
+ border-radius: 8px;
1344
+ }
1345
+
1346
+ .scene-brief .chips {
1347
+ margin: 10px 0 0;
1348
+ }
1349
+
1350
+ .details-panel {
1351
+ margin: 16px 0;
1352
+ background: #fff;
1353
+ border-radius: 10px;
1354
+ box-shadow: var(--shadow-subtle);
1355
+ padding: 14px;
1356
+ }
1357
+
1358
+ .details-panel.empty {
1359
+ background: var(--color-parchment-card);
1360
+ }
1361
+
1362
+ .details-panel h4 {
1363
+ margin: 0;
1364
+ color: var(--color-midnight);
1365
+ font-size: 19px;
1366
+ line-height: 1.25;
1367
+ letter-spacing: -0.25px;
1368
+ }
1369
+
1370
+ .details-panel p {
1371
+ margin: 8px 0 12px;
1372
+ color: var(--color-graphite);
1373
+ font-size: 13px;
1374
+ }
1375
+
1376
+ .details-list {
1377
+ display: grid;
1378
+ gap: 8px;
1379
+ margin: 12px 0;
1380
+ }
1381
+
1382
+ .details-row {
1383
+ display: grid;
1384
+ grid-template-columns: 96px minmax(0, 1fr);
1385
+ gap: 10px;
1386
+ padding: 8px;
1387
+ border-radius: 8px;
1388
+ background: var(--color-parchment-card);
1389
+ font-size: 12px;
1390
+ }
1391
+
1392
+ .details-row span:first-child {
1393
+ color: var(--color-ash);
1394
+ font-weight: 600;
1395
+ }
1396
+
1397
+ .details-row span:last-child {
1398
+ color: var(--color-charcoal-primary);
1399
+ word-break: break-word;
1400
+ }
1401
+
1402
+ .quality-panel {
1403
+ margin-top: 18px;
1404
+ background: #fff;
1405
+ border-radius: 10px;
1406
+ box-shadow: var(--shadow-subtle);
1407
+ padding: 18px;
1408
+ }
1409
+
1410
+ .quality-panel h3 {
1411
+ margin: 0;
1412
+ color: var(--color-midnight);
1413
+ font-size: 23px;
1414
+ line-height: 1.2;
1415
+ letter-spacing: -0.44px;
1416
+ }
1417
+
1418
+ .quality-grid {
1419
+ display: grid;
1420
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1421
+ gap: 10px;
1422
+ margin-top: 12px;
1423
+ }
1424
+
1425
+ .quality-item {
1426
+ display: grid;
1427
+ grid-template-columns: 28px minmax(0, 1fr);
1428
+ gap: 10px;
1429
+ align-items: start;
1430
+ background: var(--color-parchment-card);
1431
+ border-radius: 10px;
1432
+ padding: 12px;
1433
+ }
1434
+
1435
+ .quality-item.done .gate-dot {
1436
+ background: var(--color-meadow-green);
1437
+ color: #fff;
1438
+ }
1439
+
1440
+ .quality-item strong {
1441
+ display: block;
1442
+ color: var(--color-charcoal-primary);
1443
+ font-size: 13px;
1444
+ }
1445
+
1446
+ .quality-item span {
1447
+ color: var(--color-ash);
1448
+ font-size: 12px;
1449
+ line-height: 1.35;
1450
+ }
1451
+
1452
+ pre {
1453
+ white-space: pre-wrap;
1454
+ word-break: break-word;
1455
+ overflow-wrap: anywhere;
1456
+ margin: 0;
1457
+ color: var(--color-graphite);
1458
+ font-size: 12px;
1459
+ line-height: 1.58;
1460
+ letter-spacing: -0.14px;
1461
+ }
1462
+
1463
+ .side-stats {
1464
+ display: grid;
1465
+ grid-template-columns: 1fr 1fr;
1466
+ gap: 10px;
1467
+ margin: 16px 0;
1468
+ }
1469
+
1470
+ .side-stat {
1471
+ background: var(--color-parchment-card);
1472
+ border-radius: 10px;
1473
+ padding: 14px;
1474
+ }
1475
+
1476
+ .side-stat strong {
1477
+ display: block;
1478
+ color: var(--color-midnight);
1479
+ font-size: 28px;
1480
+ line-height: 1;
1481
+ }
1482
+
1483
+ .side-stat span {
1484
+ color: var(--color-ash);
1485
+ font-size: 12px;
1486
+ }
1487
+
1488
+ .next-footer {
1489
+ display: flex;
1490
+ justify-content: space-between;
1491
+ gap: 10px;
1492
+ margin-top: 24px;
1493
+ padding-top: 20px;
1494
+ border-top: 1px solid var(--color-stone-surface);
1495
+ }
1496
+
1497
+ .next-footer .btn-primary.needs-work {
1498
+ background: var(--color-ember-orange);
1499
+ }
1500
+
1501
+ .footer-status {
1502
+ flex: 1;
1503
+ display: flex;
1504
+ align-items: center;
1505
+ justify-content: center;
1506
+ color: var(--color-ash);
1507
+ font-size: 13px;
1508
+ text-align: center;
1509
+ }
1510
+
1511
+ @media (max-width: 1100px) {
1512
+ .stage-rail { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1513
+ .layout { grid-template-columns: 1fr; }
1514
+ .side-card { position: static; }
1515
+ .cards-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1516
+ }
1517
+
1518
+ @media (max-width: 760px) {
1519
+ .shell { width: min(100% - 24px, var(--page-max-width)); }
1520
+ .nav-links { display: none; }
1521
+ .hero {
1522
+ display: grid;
1523
+ padding: 22px 0 14px;
1524
+ gap: 14px;
1525
+ }
1526
+ .hero h1 { font-size: 30px; }
1527
+ .hero-actions {
1528
+ justify-content: flex-start;
1529
+ min-width: 0;
1530
+ }
1531
+ .stage-rail,
1532
+ .stats-grid,
1533
+ .cards-grid,
1534
+ .small-grid,
1535
+ .role-grid,
1536
+ .operator-steps,
1537
+ .storyboard-dashboard,
1538
+ .visual-handoff-strip,
1539
+ .scene-review-switcher,
1540
+ .quality-grid,
1541
+ .candidate-gallery,
1542
+ .candidate-card,
1543
+ .selected-still-panel,
1544
+ .controls,
1545
+ .empty-state,
1546
+ .scene-review-header,
1547
+ .scene-row,
1548
+ .scene-brief,
1549
+ .now-box {
1550
+ grid-template-columns: 1fr;
1551
+ }
1552
+ .main-card,
1553
+ .side-card {
1554
+ padding: 20px;
1555
+ }
1556
+ .storyboard-dashboard-head {
1557
+ display: grid;
1558
+ }
1559
+ .storyboard-dashboard-head .chips {
1560
+ justify-content: flex-start;
1561
+ }
1562
+ .next-footer,
1563
+ .nav-actions {
1564
+ flex-wrap: wrap;
1565
+ }
1566
+ .still-form {
1567
+ grid-template-columns: 1fr;
1568
+ margin-left: 0;
1569
+ }
1570
+ .section-title { font-size: 34px; }
1571
+ }
1572
+ </style>
1573
+ </head>
1574
+ <body>
1575
+ <nav class="nav">
1576
+ <div class="shell nav-inner">
1577
+ <div class="brand">
1578
+ <div class="brand-mark">V</div>
1579
+ <span>Video Claw Review</span>
1580
+ </div>
1581
+ <div class="nav-links">
1582
+ <button class="nav-link" data-stage-jump="inventory">Inventory</button>
1583
+ <button class="nav-link" data-stage-jump="characters">Characters</button>
1584
+ <button class="nav-link" data-stage-jump="storyboard">Storyboard</button>
1585
+ <button class="nav-link" data-stage-jump="motion">Motion plan</button>
1586
+ </div>
1587
+ <div class="nav-actions">
1588
+ <button class="btn btn-secondary" data-action="reset">Reset</button>
1589
+ <button class="btn btn-primary" data-action="next">Next</button>
1590
+ </div>
1591
+ </div>
1592
+ </nav>
1593
+
1594
+ <header class="shell hero">
1595
+ <div>
1596
+ <h1>Review the video project and fix the next blocking step.</h1>
1597
+ <p>One operator flow for characters, references, storyboard stills, artifact-backed 4K assets, motion plans, and final assembly. The handoff is ready only when the saved review report passes and publish readiness is true.</p>
1598
+ </div>
1599
+ <div class="hero-actions">
1600
+ <button class="btn btn-primary" data-action="recommend">Show next fix</button>
1601
+ <button class="btn btn-secondary" data-stage-jump="storyboard">Review images</button>
1602
+ <button class="btn btn-secondary" data-action="applyDirectorDefaults">Apply defaults</button>
1603
+ </div>
1604
+ </header>
1605
+
1606
+ <section class="shell">
1607
+ <div class="stage-rail" id="stageRail"></div>
1608
+ <div class="layout">
1609
+ <main class="main-card">
1610
+ <div class="step-meta" id="stepMeta"></div>
1611
+ <div class="section-kicker" id="kicker"></div>
1612
+ <h2 class="section-title" id="title"></h2>
1613
+ <p class="section-copy" id="description"></p>
1614
+ <div class="now-box">
1615
+ <div>
1616
+ <strong id="nowTitle"></strong>
1617
+ <span id="nowCopy"></span>
1618
+ </div>
1619
+ <button class="btn btn-primary" data-action="recommendedAction" id="nowButton"></button>
1620
+ </div>
1621
+ <div class="action-feedback" id="actionFeedback" role="status" aria-live="polite"></div>
1622
+ <div id="qualityPanel"></div>
1623
+ <div id="content"></div>
1624
+ <div class="next-footer">
1625
+ <button class="btn btn-secondary" data-action="prev">Back</button>
1626
+ <div class="footer-status" id="footerStatus"></div>
1627
+ <button class="btn btn-primary" data-action="next" id="continueButton">Continue</button>
1628
+ </div>
1629
+ </main>
1630
+
1631
+ <aside class="side-card">
1632
+ <h3>Project state</h3>
1633
+ <p>Selections update this ledger immediately. In the real app these become project artifacts.</p>
1634
+ <label for="projectSelect">Project</label>
1635
+ <select id="projectSelect"></select>
1636
+ <div class="side-stats">
1637
+ <div class="side-stat"><strong id="choiceCount">0</strong><span>choices made</span></div>
1638
+ <div class="side-stat"><strong id="gateCount">6</strong><span>open gates</span></div>
1639
+ </div>
1640
+ <div class="gate-checks" id="gateChecks"></div>
1641
+ <div id="handoff"></div>
1642
+ <div id="detailsPanel"></div>
1643
+ <button class="btn btn-primary" data-action="saveProgress">Save progress</button>
1644
+ <button class="btn btn-outline" data-action="downloadLedger">Download JSON</button>
1645
+ <details class="ledger">
1646
+ <summary>Advanced: raw JSON ledger</summary>
1647
+ <pre id="ledger"></pre>
1648
+ </details>
1649
+ </aside>
1650
+ </div>
1651
+ </section>
1652
+
1653
+ <script>
1654
+ const repoRoot = new URL("../../", window.location.href);
1655
+ const asset = (path) => {
1656
+ const raw = String(path || "");
1657
+ if (!raw) return "";
1658
+ if (/^https?:\/\//.test(raw)) return `/api/media-proxy?url=${encodeURIComponent(raw)}`;
1659
+ let normalized = raw;
1660
+ if (inventory.root && normalized.startsWith(inventory.root)) {
1661
+ normalized = normalized.slice(inventory.root.length).replace(/^\/+/, "");
1662
+ }
1663
+ const projectPathIndex = normalized.indexOf("/projects/");
1664
+ if (normalized.startsWith("/") && projectPathIndex >= 0) {
1665
+ normalized = normalized.slice(projectPathIndex + 1);
1666
+ }
1667
+ normalized = normalized.replace(/^\/+/, "");
1668
+ return new URL(normalized, repoRoot).href;
1669
+ };
1670
+
1671
+ function escapeHtml(value) {
1672
+ return String(value ?? "")
1673
+ .replace(/&/g, "&amp;")
1674
+ .replace(/</g, "&lt;")
1675
+ .replace(/>/g, "&gt;")
1676
+ .replace(/"/g, "&quot;")
1677
+ .replace(/'/g, "&#39;");
1678
+ }
1679
+
1680
+ const seedanceWorkflow = {
1681
+ source: "docs/REFERENCE_VIDEO_SEEDANCE_MOTION_DESIGN_WORKFLOW.md",
1682
+ qualityBar: "award-winning director cinematic video with minimal operator work",
1683
+ method: [
1684
+ "idea and voiceover first",
1685
+ "role-tagged reference canvas before prompting",
1686
+ "still storyboard locked before motion",
1687
+ "focused edit prompts before animation",
1688
+ "upscale locked frames before Seedance",
1689
+ "Seedance start/end frame chaining",
1690
+ "long control prompt plus short variant prompt",
1691
+ "bridge poses for hard hand/object/logo motion",
1692
+ "continuity end-frame extraction between shots",
1693
+ "planned post retiming and voiceover fit"
1694
+ ],
1695
+ promptPatterns: {
1696
+ stillCreate: "Use reference roles as source truth. Create one visual beat. Preserve identity, materials, colors, lighting, and camera. Avoid readable text, logos, clutter, extra objects, and real people.",
1697
+ stillEdit: "Edit only the named defect or layout issue. Keep identity, pose, camera, lighting, composition, and background unchanged.",
1698
+ motionControl: "Use image 1 as start and image 2 as end. Preserve identity, style, background, lighting, and camera. Animate one readable action with easing, anticipation, overshoot, and soft settle. End exactly matching target.",
1699
+ shortVariant: "Static camera. Same start and target. Faster playful single-action alternate while preserving character, background, composition, and style.",
1700
+ bridgePose: "Create intermediate pose stills for catches, throws, hand-object contact, object escapes, transformations, and logo morphs before video generation.",
1701
+ postPolish: "Retiming, voiceover fit, opacity, and final logo reveal are planned before publish."
1702
+ },
1703
+ bridgeTriggers: ["catch", "throw", "hand-object contact", "escaping card", "character transformation", "logo morph", "multi-object choreography"],
1704
+ negativeGuidance: ["no readable UI text unless required", "no random logos", "no clutter", "no extra objects", "no real humans", "no distorted anatomy", "no unwanted camera change"]
1705
+ };
1706
+
1707
+ const inventory = {
1708
+ root: "",
1709
+ projects: [
1710
+ { id: "fresh-proof", name: "fresh-proof", path: "projects/fresh-proof", status: "empty scaffold" }
1711
+ ],
1712
+ characters: [],
1713
+ presenterPacks: [
1714
+ {
1715
+ id: "davendra-presenter",
1716
+ name: "Davendra Presenter",
1717
+ assets: [
1718
+ "skills/davendra-presenter/assets/davendra_intro_1.jpg",
1719
+ "skills/davendra-presenter/assets/davendra_intro_2.jpg",
1720
+ "skills/davendra-presenter/assets/davendra_outro_1.jpg",
1721
+ "skills/davendra-presenter/assets/davendra_outro_2.jpg"
1722
+ ],
1723
+ notes: "Existing intro and outro presenter image pack."
1724
+ },
1725
+ {
1726
+ id: "nex-presenter",
1727
+ name: "Nex Presenter",
1728
+ assets: [
1729
+ "skills/nex-presenter/assets/nex_intro_1.jpg",
1730
+ "skills/nex-presenter/assets/nex_intro_2.jpg",
1731
+ "skills/nex-presenter/assets/nex_outro_1.jpg",
1732
+ "skills/nex-presenter/assets/nex_outro_2.jpg",
1733
+ "skills/nex-presenter/assets/logo_source.jpg",
1734
+ "skills/nex-presenter/assets/text_overlay.png",
1735
+ "skills/nex-presenter/assets/nex_brief_intro.mp4"
1736
+ ],
1737
+ notes: "Brand presenter pack with stills, logo source, overlay, and intro video."
1738
+ }
1739
+ ],
1740
+ mediaAssets: [
1741
+ "docs/assets/logo.jpg",
1742
+ "docs/assets/diagram-lifecycle.jpg",
1743
+ "docs/assets/diagram-architecture.jpg",
1744
+ "docs/assets/diagram-routing.jpg",
1745
+ "docs/assets/diagram-skills-ecosystem.jpg",
1746
+ "docs/assets/diagram-obsidian-vault.jpg",
1747
+ "docs/assets/diagram-obsidian-loop.jpg",
1748
+ "docs/assets/demo-quickstart.gif"
1749
+ ],
1750
+ playbooks: [
1751
+ { id: "seedance-ugc", name: "Seedance UGC", provider: "seedance", desc: "Best for stylized, character-led short clips with strong image anchors." },
1752
+ { id: "veo-generic", name: "Veo Generic", provider: "veo", desc: "Best for general motion-led scenes and realistic continuity." }
1753
+ ],
1754
+ references: [
1755
+ ["seedance-ugc-formulas", "Seedance prompt formulas, image-import guidance, camera vocabulary."],
1756
+ ["character-reference-sheet", "Character continuity and reference-sheet rules."],
1757
+ ["clone-ad-template-workflow", "Reference video analysis into reusable structure."],
1758
+ ["dialogue-duration-preflight", "Dialogue timing checks before generation."],
1759
+ ["generation-telemetry", "Route, timing, cost, and output telemetry."],
1760
+ ["stage-directors", "Stage-specific operator guidance."],
1761
+ ["style-template-schema", "Style layer structure."],
1762
+ ["veo-prompting-guide", "Veo route prompting notes."],
1763
+ ["checkpoint-protocol", "Lifecycle checkpoint discipline."]
1764
+ ],
1765
+ templates: [
1766
+ ["product-commercial-4", "Problem, reveal, proof, payoff.", 4],
1767
+ ["food-tutorial-6", "Ingredients, prep, transformation, plate, taste.", 6],
1768
+ ["dance-social-6", "Hook pose, choreography beats, loopable finish.", 6],
1769
+ ["dramatic-short-6", "Reveal, choice, confrontation, aftermath.", 6],
1770
+ ["action-short-6", "Threat, obstacle, impact, near miss, release.", 6],
1771
+ ["beat-structure-3", "Establish, develop, payoff.", 3],
1772
+ ["beat-structure-6", "Two shots per establish, develop, payoff.", 6],
1773
+ ["dialogue-confrontation", "Conversation with reaction and counter-reaction close-ups.", 6],
1774
+ ["chase-pursuit", "Pursuit geography and near-catch beats.", 8],
1775
+ ["discovery-reveal", "Discovery and reveal sequence.", 6],
1776
+ ["product-story", "Product problem through satisfaction.", 7]
1777
+ ],
1778
+ schemas: ["storyboard", "asset-manifest", "scene-candidates", "scene-selection", "reference-sheets", "execution-plan", "execution-report", "review-report", "publish-report", "brief", "clone-plan", "analyze-output"],
1779
+ brief: null,
1780
+ storyboard: null,
1781
+ assetManifest: null,
1782
+ executionReport: null,
1783
+ publishReport: null,
1784
+ reviewReport: null,
1785
+ generationQueue: { schemaVersion: 1, projectSlug: "fresh-proof", updatedAt: "", requests: [] },
1786
+ characterQueue: { schemaVersion: 1, projectSlug: "fresh-proof", updatedAt: "", requests: [] },
1787
+ roles: {
1788
+ identity: ["identity", "wardrobe", "silhouette", "age-reference"],
1789
+ "outfit-material": ["outfit", "material", "accessory", "texture", "product-hero", "product-variant", "product-in-use", "packaging"],
1790
+ environment: ["location", "set-dressing", "weather", "time-of-day"],
1791
+ "motion-camera": ["motion-rhythm", "camera-behavior", "blocking", "shot-framing"],
1792
+ "palette-mood": ["palette", "composition", "mood", "lighting-reference"]
1793
+ }
1794
+ };
1795
+
1796
+ function prettyName(value) {
1797
+ return String(value || "")
1798
+ .replace(/[-_]+/g, " ")
1799
+ .replace(/\b\w/g, (letter) => letter.toUpperCase());
1800
+ }
1801
+
1802
+ function validProjectSlug(value) {
1803
+ return /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(String(value || ""));
1804
+ }
1805
+
1806
+ function visibleProjects() {
1807
+ const seen = new Set();
1808
+ return (inventory.projects || []).filter((project) => {
1809
+ const id = project?.id || "";
1810
+ if (!validProjectSlug(id) || seen.has(id)) return false;
1811
+ seen.add(id);
1812
+ return true;
1813
+ });
1814
+ }
1815
+
1816
+ function normalizeApiInventory(api) {
1817
+ const next = {};
1818
+ next.root = api.root || "";
1819
+ const projects = Array.isArray(api.projects) && api.projects.length
1820
+ ? api.projects
1821
+ : [api.projectSlug].filter(Boolean);
1822
+ const orderedProjects = [
1823
+ api.projectSlug,
1824
+ ...projects.filter((id) => id !== api.projectSlug)
1825
+ ].filter((id, index, list) => Boolean(id) && validProjectSlug(id) && list.indexOf(id) === index);
1826
+ next.projects = orderedProjects.map((id) => ({
1827
+ id,
1828
+ name: id,
1829
+ path: `projects/${id}`,
1830
+ status: id === api.projectSlug ? (api.projectExists ? "current project" : "selected missing") : "existing project"
1831
+ }));
1832
+ next.characters = Array.isArray(api.characters) ? api.characters : [];
1833
+ next.playbooks = Array.isArray(api.playbooks)
1834
+ ? api.playbooks.map((name) => ({
1835
+ id: name,
1836
+ name: prettyName(name),
1837
+ provider: String(name).includes("seedance") ? "seedance" : "veo",
1838
+ desc: "Provider playbook available in this repo."
1839
+ }))
1840
+ : inventory.playbooks;
1841
+ next.references = Array.isArray(api.promptReferences)
1842
+ ? api.promptReferences.map((reference) => [reference.name, reference.summary])
1843
+ : inventory.references;
1844
+ next.templates = Array.isArray(api.storyboardTemplates)
1845
+ ? api.storyboardTemplates.map((template) => [template.id, template.description, template.panels?.length || 0])
1846
+ : inventory.templates;
1847
+ next.schemas = Array.isArray(api.schemas) ? api.schemas : inventory.schemas;
1848
+ next.mediaAssets = Array.isArray(api.mediaAssets)
1849
+ ? api.mediaAssets.map((item) => item.path)
1850
+ : inventory.mediaAssets;
1851
+ next.referenceSheets = api.referenceSheets || { sheets: [] };
1852
+ next.sceneCandidates = api.sceneCandidates || { scenes: [] };
1853
+ next.sceneSelection = api.sceneSelection || { scenes: [] };
1854
+ next.reviewLedger = api.reviewLedger || null;
1855
+ next.reviewReport = api.reviewReport || null;
1856
+ next.brief = api.brief || null;
1857
+ next.storyboard = api.storyboard || null;
1858
+ next.assetManifest = api.assetManifest || null;
1859
+ next.executionReport = api.executionReport || null;
1860
+ next.publishReport = api.publishReport || null;
1861
+ next.generationQueue = api.generationQueue || { schemaVersion: 1, projectSlug: api.projectSlug || "", updatedAt: "", requests: [] };
1862
+ next.characterQueue = api.characterQueue || { schemaVersion: 1, projectSlug: api.projectSlug || "", updatedAt: "", requests: [] };
1863
+ return next;
1864
+ }
1865
+
1866
+ async function loadInventoryFromApi() {
1867
+ if (window.location.protocol === "file:") return;
1868
+ const params = new URLSearchParams(window.location.search);
1869
+ const project = params.get("project") || state.project || inventory.projects[0]?.id || "";
1870
+ try {
1871
+ const response = await fetch(`/api/review-inventory?project=${encodeURIComponent(project)}`);
1872
+ if (!response.ok) throw new Error(await response.text());
1873
+ const apiInventory = await response.json();
1874
+ Object.assign(inventory, normalizeApiInventory(apiInventory));
1875
+ state.project = apiInventory.projectSlug || project;
1876
+ const candidateScenes = apiInventory.sceneCandidates?.scenes || [];
1877
+ const savedDecision = apiInventory.reviewLedger?.decision;
1878
+ if (savedDecision?.selections && typeof savedDecision.selections === "object") {
1879
+ state.selected = { ...savedDecision.selections };
1880
+ state.statusMessage = "Loaded saved project ledger from disk.";
1881
+ state.inspected = null;
1882
+ }
1883
+ if (savedDecision?.notes && typeof savedDecision.notes === "object") {
1884
+ state.notes = { ...savedDecision.notes };
1885
+ }
1886
+ if (savedDecision?.savedArtifact && typeof savedDecision.savedArtifact === "object") {
1887
+ state.savedArtifact = savedDecision.savedArtifact;
1888
+ }
1889
+ if (savedDecision?.reviewCompleteAt) {
1890
+ state.selected.reviewCompleteAt = savedDecision.reviewCompleteAt;
1891
+ }
1892
+ if (savedReviewPassed() && state.savedArtifact && !state.savedArtifact.lifecycle) {
1893
+ state.savedArtifact.lifecycle = {
1894
+ status: "completed",
1895
+ currentStage: "publish",
1896
+ lastCompletedStage: "review",
1897
+ manifestUpdated: true
1898
+ };
1899
+ }
1900
+ if (apiInventory.publishReport?.status === "ready" && savedReviewPassed()) {
1901
+ state.stage = "assembly";
1902
+ state.savedArtifact = {
1903
+ ...(state.savedArtifact || {}),
1904
+ lifecycle: {
1905
+ status: "completed",
1906
+ currentStage: "publish",
1907
+ lastCompletedStage: "review",
1908
+ manifestUpdated: true
1909
+ }
1910
+ };
1911
+ state.statusMessage = "Loaded published project handoff and final video.";
1912
+ }
1913
+ if (reviewComplete()) {
1914
+ state.stage = "assembly";
1915
+ } else if (savedDecision?.activeGate && stages.some((stage) => stage.id === savedDecision.activeGate)) {
1916
+ state.stage = savedDecision.activeGate;
1917
+ }
1918
+ reconcileSelectionsWithInventory();
1919
+ state.scenes = buildSceneRows(candidateScenes, apiInventory.sceneSelection?.scenes || []);
1920
+ snapStageToFirstIncomplete();
1921
+ } catch (error) {
1922
+ state.blockedMessage = `Live inventory failed, showing fallback data: ${error.message}`;
1923
+ }
1924
+ }
1925
+
1926
+ function selectedTemplateSceneCount() {
1927
+ const templateId = state.selected.template || "product-commercial-4";
1928
+ const template = inventory.templates.find(([id]) => id === templateId);
1929
+ return Number(template?.[2]) || 4;
1930
+ }
1931
+
1932
+ function defaultSceneTitle(index) {
1933
+ return ["Problem hook", "Solution reveal", "Proof beat", "Payoff and CTA"][index] || `Scene ${index}`;
1934
+ }
1935
+
1936
+ function buildSceneRows(candidateScenes = [], selectionScenes = []) {
1937
+ const count = Math.max(selectedTemplateSceneCount(), 1);
1938
+ return Array.from({ length: count }, (_, index) => {
1939
+ const scene = candidateScenes.find((entry) => entry.sceneIndex === index) || { candidates: [] };
1940
+ const selected = selectionScenes.find((entry) => entry.sceneIndex === index);
1941
+ const candidateCount = scene.candidates?.length || 0;
1942
+ const status = selected?.selectedCandidateId
1943
+ ? `selected ${selected.selectedCandidateId}`
1944
+ : candidateCount
1945
+ ? `${candidateCount} candidate${candidateCount === 1 ? "" : "s"} awaiting review`
1946
+ : "needs GoBananas still";
1947
+ return [
1948
+ `scene-${String(index).padStart(2, "0")}`,
1949
+ defaultSceneTitle(index),
1950
+ status,
1951
+ index > 0
1952
+ ];
1953
+ });
1954
+ }
1955
+
1956
+ const stages = [
1957
+ {
1958
+ id: "inventory",
1959
+ label: "Inventory",
1960
+ sub: "See what exists",
1961
+ kicker: "Repo inventory",
1962
+ title: "Everything available to choose from",
1963
+ desc: "This is the pantry. It lists current project folders, saved characters, presenter packs, media assets, playbooks, prompt references, templates, and schemas.",
1964
+ now: "Start by checking character availability.",
1965
+ help: "There are no saved project characters yet, so the safest next step is the character gate."
1966
+ },
1967
+ {
1968
+ id: "characters",
1969
+ label: "Characters",
1970
+ sub: "Lock identity",
1971
+ kicker: "GoBananas lane",
1972
+ title: "Choose or create the character source",
1973
+ desc: "If saved characters exist, pick one. If not, select an existing presenter pack or request GoBananas character iterations before storyboard work starts.",
1974
+ now: "No saved project characters were found.",
1975
+ help: "Pick a presenter pack for now, or mark the ledger to generate GoBananas character iterations."
1976
+ },
1977
+ {
1978
+ id: "references",
1979
+ label: "References",
1980
+ sub: "Assign roles",
1981
+ kicker: "Reference board",
1982
+ title: "Give every reference one job",
1983
+ desc: "Reference images should not all influence everything. Assign identity, wardrobe, texture, camera, motion, location, palette, and mood deliberately.",
1984
+ now: "Attach the references that should guide generation.",
1985
+ help: "Use role tags to prevent pose references from rewriting character identity."
1986
+ },
1987
+ {
1988
+ id: "storyboard",
1989
+ label: "Storyboard",
1990
+ sub: "Lock stills",
1991
+ kicker: "Still-frame review",
1992
+ title: "Approve the still storyboard in order",
1993
+ desc: "This mirrors the reference video: generate still frames first, edit them, and only move to Seedance after the sequence is visually locked.",
1994
+ now: "Pick a template, then lock each scene still.",
1995
+ help: "Scene 00 starts the chain; later scenes should use previous end frames."
1996
+ },
1997
+ {
1998
+ id: "motion",
1999
+ label: "Motion plan",
2000
+ sub: "Prepare clips",
2001
+ kicker: "Seedance review",
2002
+ title: "Prepare the Seedance motion plan",
2003
+ desc: "Videos are not generated in this review pass. This step records the control-pass, variant, bridge-pose, and continuity instructions that the next agent will use after the still storyboard is locked.",
2004
+ now: "Confirm the image-to-video recipe.",
2005
+ help: "The next agent should use each locked 4k still as the start frame, then generate motion and extract end frames."
2006
+ },
2007
+ {
2008
+ id: "assembly",
2009
+ label: "Assembly",
2010
+ sub: "Finish",
2011
+ kicker: "Post plan",
2012
+ title: "Approve pacing and final assembly",
2013
+ desc: "Confirm time remapping, voiceover fit, final logo reveal, and publish readiness.",
2014
+ now: "Select the final assembly plan.",
2015
+ help: "This writes the post plan and final review report."
2016
+ }
2017
+ ];
2018
+
2019
+ const state = {
2020
+ stage: "inventory",
2021
+ project: inventory.projects[0]?.id || "",
2022
+ filter: "",
2023
+ tab: "all",
2024
+ selected: {},
2025
+ notes: {},
2026
+ blockedMessage: "",
2027
+ statusMessage: "",
2028
+ savedArtifact: null,
2029
+ inspected: null,
2030
+ activeSceneIndex: 0,
2031
+ scenes: [
2032
+ ["scene-00", "Problem hook", "needs locked still", false],
2033
+ ["scene-01", "Solution reveal", "needs locked still", true],
2034
+ ["scene-02", "Proof beat", "needs bridge-pose check", true],
2035
+ ["scene-03", "Payoff and CTA", "needs locked still", true]
2036
+ ]
2037
+ };
2038
+
2039
+ let hydrationComplete = false;
2040
+
2041
+ function initialProjectFromLocation() {
2042
+ if (window.location.protocol === "file:") return "";
2043
+ return new URLSearchParams(window.location.search).get("project") || "";
2044
+ }
2045
+
2046
+ function storageKey() {
2047
+ return `vclaw-review-station:${state.project || "default"}`;
2048
+ }
2049
+
2050
+ function loadLocalProgress() {
2051
+ try {
2052
+ const raw = localStorage.getItem(storageKey());
2053
+ if (!raw) return;
2054
+ const saved = JSON.parse(raw);
2055
+ if (saved.stage && stages.some((stage) => stage.id === saved.stage)) state.stage = saved.stage;
2056
+ if (saved.selected && typeof saved.selected === "object") state.selected = saved.selected;
2057
+ if (saved.notes && typeof saved.notes === "object") state.notes = saved.notes;
2058
+ if (saved.savedArtifact) state.savedArtifact = saved.savedArtifact;
2059
+ if (saved.inspected) state.inspected = saved.inspected;
2060
+ if (Number.isInteger(saved.activeSceneIndex)) state.activeSceneIndex = saved.activeSceneIndex;
2061
+ state.statusMessage = "Restored saved progress from this browser.";
2062
+ } catch (error) {
2063
+ state.blockedMessage = `Could not restore saved browser progress: ${error.message}`;
2064
+ }
2065
+ }
2066
+
2067
+ function persistLocalProgress() {
2068
+ if (!hydrationComplete || !state.project) return;
2069
+ localStorage.setItem(storageKey(), JSON.stringify({
2070
+ stage: state.stage,
2071
+ selected: state.selected,
2072
+ notes: state.notes,
2073
+ savedArtifact: state.savedArtifact,
2074
+ inspected: state.inspected,
2075
+ activeSceneIndex: state.activeSceneIndex
2076
+ }));
2077
+ }
2078
+
2079
+ function hasLiveInventory() {
2080
+ return Boolean(inventory.root || inventory.reviewLedger || inventory.reviewReport || inventory.sceneCandidates?.scenes?.length);
2081
+ }
2082
+
2083
+ function inventoryReady() {
2084
+ return Boolean(
2085
+ state.project
2086
+ && visibleProjects().some((project) => project.id === state.project)
2087
+ && inventory.templates.length
2088
+ && inventory.references.length
2089
+ && inventory.schemas.length
2090
+ && (window.location.protocol === "file:" || hasLiveInventory())
2091
+ );
2092
+ }
2093
+
2094
+ function findSceneCandidate(sceneIndex, candidateId) {
2095
+ return inventory.sceneCandidates?.scenes
2096
+ ?.find((scene) => scene.sceneIndex === sceneIndex)
2097
+ ?.candidates?.find((candidate) => candidate.id === candidateId) || null;
2098
+ }
2099
+
2100
+ function reconcileSelectionsWithInventory() {
2101
+ const removed = [];
2102
+ for (const [key, value] of Object.entries({ ...state.selected })) {
2103
+ const match = /^(draftStill|lockedStill|upscaledStill)-(\d+)$/.exec(key);
2104
+ if (!match || !value) continue;
2105
+ const sceneIndex = Number(match[2]);
2106
+ if (!findSceneCandidate(sceneIndex, String(value))) {
2107
+ delete state.selected[key];
2108
+ removed.push(key);
2109
+ }
2110
+ }
2111
+ if (removed.length) {
2112
+ state.statusMessage = `Removed stale saved choices that no longer exist in scene candidates: ${removed.join(", ")}.`;
2113
+ }
2114
+ }
2115
+
2116
+ function stageIndex() {
2117
+ return stages.findIndex((stage) => stage.id === state.stage);
2118
+ }
2119
+
2120
+ function clampSceneIndex(value) {
2121
+ const index = Number(value);
2122
+ if (!Number.isFinite(index)) return 0;
2123
+ return Math.max(0, Math.min(state.scenes.length - 1, Math.trunc(index)));
2124
+ }
2125
+
2126
+ function currentStage() {
2127
+ return stages[stageIndex()] || stages[0];
2128
+ }
2129
+
2130
+ function setStage(id) {
2131
+ state.stage = id;
2132
+ if (id === "storyboard") state.activeSceneIndex = clampSceneIndex(state.activeSceneIndex);
2133
+ state.blockedMessage = "";
2134
+ state.statusMessage = `Moved to ${currentStage().label}.`;
2135
+ render();
2136
+ }
2137
+
2138
+ function shouldAutosaveSelection(key) {
2139
+ return /^(?:lockedStill|upscaledStill|continuity)-\d+$/.test(key)
2140
+ || /^assemblyCheck-/.test(key)
2141
+ || ["bridgePosePlan", "motionCandidate", "variantStrategy", "assemblyPlan"].includes(key);
2142
+ }
2143
+
2144
+ async function choose(key, value) {
2145
+ const sceneSelectionMatch = /^(?:draftStill|editStill|lockedStill|upscaledStill)-(\d+)$/.exec(key);
2146
+ if (sceneSelectionMatch) {
2147
+ const sceneIndex = Number(sceneSelectionMatch[1]);
2148
+ if (!isSceneWorkflowEnabled(sceneIndex)) {
2149
+ state.blockedMessage = sceneWorkflowMessage(sceneIndex);
2150
+ state.statusMessage = "";
2151
+ render();
2152
+ return;
2153
+ }
2154
+ if (/^upscaledStill-/.test(key) && !isCandidateBackedLock(sceneIndex)) {
2155
+ state.blockedMessage = "Lock one real generated candidate before marking the upscale.";
2156
+ state.statusMessage = "";
2157
+ render();
2158
+ return;
2159
+ }
2160
+ }
2161
+ state.selected[key] = value;
2162
+ if (key === "template") {
2163
+ state.scenes = buildSceneRows(inventory.sceneCandidates?.scenes || [], inventory.sceneSelection?.scenes || []);
2164
+ }
2165
+ const lockMatch = /^lockedStill-(\d+)$/.exec(key);
2166
+ if (lockMatch) {
2167
+ const sceneIndex = lockMatch[1];
2168
+ const candidate = candidateScene(Number(sceneIndex)).candidates?.find((entry) => entry.id === value);
2169
+ if (!candidateMatchesSelectedCharacter(candidate)) {
2170
+ state.blockedMessage = `${value} does not match the selected character. Generate or choose a ${selectedCharacterLabel()} candidate before locking.`;
2171
+ state.statusMessage = "";
2172
+ render();
2173
+ return;
2174
+ }
2175
+ if (!state.selected[`draftStill-${sceneIndex}`]) {
2176
+ state.selected[`draftStill-${sceneIndex}`] = value;
2177
+ }
2178
+ const upscaledKey = `upscaledStill-${sceneIndex}`;
2179
+ if (state.selected[upscaledKey] && !String(state.selected[upscaledKey]).startsWith(`${value}-`)) {
2180
+ delete state.selected[upscaledKey];
2181
+ }
2182
+ }
2183
+ state.blockedMessage = "";
2184
+ state.statusMessage = shouldAutosaveSelection(key) ? "Saving storyboard selection..." : selectionFeedback(key, value);
2185
+ render();
2186
+ if (!shouldAutosaveSelection(key)) return;
2187
+ try {
2188
+ state.savedArtifact = await saveLedgerToProject();
2189
+ state.statusMessage = /^upscaledStill-\d+$/.test(key)
2190
+ ? `${value} upscale marker saved.`
2191
+ : /^continuity-\d+$/.test(key)
2192
+ ? `${value} continuity marker saved.`
2193
+ : key === "bridgePosePlan"
2194
+ ? "Bridge-pose plan saved."
2195
+ : key === "motionCandidate"
2196
+ ? `${value} motion strategy saved.`
2197
+ : key === "assemblyPlan"
2198
+ ? `${value} assembly plan saved.`
2199
+ : /^assemblyCheck-/.test(key)
2200
+ ? `${value} assembly approval saved.`
2201
+ : `${value} locked and saved.`;
2202
+ } catch (error) {
2203
+ state.blockedMessage = `Selection made locally, but could not save: ${error.message}`;
2204
+ state.statusMessage = "";
2205
+ }
2206
+ render();
2207
+ }
2208
+
2209
+ function selectionFeedback(key, value) {
2210
+ if (/^characterCheck-\d+$/.test(key)) return "Character checklist item marked done.";
2211
+ if (/^referenceRole-/.test(key)) return `${key.replace("referenceRole-", "")} reference role marked assigned.`;
2212
+ if (/^draftStill-\d+$/.test(key)) return `${value} saved as the draft still for this scene.`;
2213
+ if (/^editStill-\d+$/.test(key)) return "Prompt edit note saved for this scene.";
2214
+ const labels = {
2215
+ character: "Character selected.",
2216
+ characterPlan: "Character workflow choice selected.",
2217
+ characterSource: "Character source selected.",
2218
+ reference: "Reference attached.",
2219
+ asset: "Asset attached.",
2220
+ playbook: "Playbook selected.",
2221
+ template: "Storyboard template selected.",
2222
+ variantStrategy: "Variant strategy selected.",
2223
+ };
2224
+ return labels[key] || `${value} selected.`;
2225
+ }
2226
+
2227
+ function selected(key, value) {
2228
+ return state.selected[key] === value;
2229
+ }
2230
+
2231
+ function ext(path) {
2232
+ return path.split(".").pop().toLowerCase();
2233
+ }
2234
+
2235
+ function candidateScene(sceneIndex) {
2236
+ return inventory.sceneCandidates?.scenes?.find((scene) => scene.sceneIndex === sceneIndex) || { sceneIndex, candidates: [] };
2237
+ }
2238
+
2239
+ function candidateSelection(sceneIndex) {
2240
+ return inventory.sceneSelection?.scenes?.find((scene) => scene.sceneIndex === sceneIndex) || null;
2241
+ }
2242
+
2243
+ function isCandidateBackedLock(sceneIndex) {
2244
+ const selectedId = state.selected[`lockedStill-${sceneIndex}`];
2245
+ return Boolean(selectedId && !candidateRejected(selectedId) && (candidateScene(sceneIndex).candidates || []).some((candidate) => candidate.id === selectedId));
2246
+ }
2247
+
2248
+ function isSceneUpscaled(sceneIndex) {
2249
+ return hasArtifactBackedUpscale(sceneIndex);
2250
+ }
2251
+
2252
+ function hasArtifactBackedUpscale(sceneIndex) {
2253
+ const upscaledStill = state.selected[`upscaledStill-${sceneIndex}`];
2254
+ return Boolean(upscaledStill && (candidateScene(sceneIndex).candidates || []).some((candidate) => candidate.id === upscaledStill));
2255
+ }
2256
+
2257
+ function isContinuityConfirmed(sceneIndex) {
2258
+ return Boolean(state.selected[`continuity-${sceneIndex}`]);
2259
+ }
2260
+
2261
+ function disabledAttr(disabled) {
2262
+ return disabled ? "disabled aria-disabled=\"true\"" : "";
2263
+ }
2264
+
2265
+ function isSceneWorkflowEnabled(sceneIndex) {
2266
+ if (!state.selected.template) return false;
2267
+ if (sceneIndex === 0) return true;
2268
+ return isCandidateBackedLock(sceneIndex - 1) && isSceneUpscaled(sceneIndex - 1);
2269
+ }
2270
+
2271
+ function sceneWorkflowMessage(sceneIndex) {
2272
+ if (!state.selected.template) return "Choose the storyboard template first.";
2273
+ if (!isSceneWorkflowEnabled(sceneIndex)) {
2274
+ return `Finish scene-${String(sceneIndex - 1).padStart(2, "0")} lock and upscale before this scene opens.`;
2275
+ }
2276
+ if (!candidateScene(sceneIndex).candidates?.length) return "Paste a real GoBananas image URL below.";
2277
+ if (!state.selected[`draftStill-${sceneIndex}`]) return "Choose the first real candidate as draft.";
2278
+ if (!isCandidateBackedLock(sceneIndex)) return "Review the candidate cards and lock one real image.";
2279
+ if (!isSceneUpscaled(sceneIndex)) return "Attach the artifact-backed 4K/upscaled version of the locked image.";
2280
+ return "Scene is locked and ready for the next one.";
2281
+ }
2282
+
2283
+ function assemblyChecks() {
2284
+ return [
2285
+ ["voiceover-fit", "Voiceover timing fits", "Scene pacing has been checked against the spoken script."],
2286
+ ["continuity-cuts", "Continuity cuts reviewed", "Frame handoffs and transitions are planned before final render."],
2287
+ ["retiming-polish", "Retiming polish approved", "Slow clips will be tightened without losing readable action."],
2288
+ ["logo-payoff", "Logo/payoff reveal approved", "Final reveal has a clean hold and readable composition."],
2289
+ ["review-report", "Review report ready", "The next agent has enough saved evidence to render or continue."]
2290
+ ];
2291
+ }
2292
+
2293
+ function isAssemblyCheckDone(id) {
2294
+ return Boolean(state.selected[`assemblyCheck-${id}`]);
2295
+ }
2296
+
2297
+ function missingAssemblyChecks() {
2298
+ return assemblyChecks().filter(([id]) => !isAssemblyCheckDone(id));
2299
+ }
2300
+
2301
+ function imageOutput(candidate) {
2302
+ if (!candidate) return null;
2303
+ return candidate.outputs?.find((output) => output.kind === "image") || candidate.outputs?.[0] || null;
2304
+ }
2305
+
2306
+ function rejectedStillCandidateIds() {
2307
+ return Array.isArray(state.selected.rejectedStillCandidates) ? state.selected.rejectedStillCandidates : [];
2308
+ }
2309
+
2310
+ function candidateRejected(id) {
2311
+ return rejectedStillCandidateIds().includes(id);
2312
+ }
2313
+
2314
+ function candidateRejectedForScene(sceneIndex, id) {
2315
+ const sceneRejected = candidateSelection(sceneIndex)?.rejectedCandidateIds || [];
2316
+ return candidateRejected(id) || sceneRejected.includes(id);
2317
+ }
2318
+
2319
+ function candidatePendingForScene(sceneIndex, id) {
2320
+ return (candidateSelection(sceneIndex)?.pendingCandidateIds || []).includes(id);
2321
+ }
2322
+
2323
+ function candidateReviewOrder(sceneIndex, selectedId) {
2324
+ return (candidate) => {
2325
+ if (candidate.id === selectedId) return 0;
2326
+ if (candidateRejectedForScene(sceneIndex, candidate.id) || candidatePendingForScene(sceneIndex, candidate.id)) return 2;
2327
+ return 1;
2328
+ };
2329
+ }
2330
+
2331
+ function candidateQuality(output) {
2332
+ if (!output?.path) return { ok: false, label: "missing output", detail: "This candidate has no image output to review." };
2333
+ try {
2334
+ const parsed = new URL(output.path, window.location.href);
2335
+ const host = parsed.hostname.toLowerCase();
2336
+ if (["example.com", "example.net", "example.org"].includes(host)) {
2337
+ return { ok: false, label: "placeholder URL", detail: "Reject this candidate or replace it with a real GoBananas render URL." };
2338
+ }
2339
+ if (!/\.(jpg|jpeg|png|webp|gif)(?:$|\?)/.test(`${parsed.pathname.toLowerCase()}${parsed.search}`)) {
2340
+ return { ok: false, label: "not an image URL", detail: "Storyboard still candidates must point to a jpg, png, webp, or gif image." };
2341
+ }
2342
+ } catch (error) {
2343
+ return { ok: false, label: "invalid URL", detail: "The output path cannot be opened as a reviewable image." };
2344
+ }
2345
+ return { ok: true, label: "renderable image", detail: "Image URL is suitable for review and lock selection." };
2346
+ }
2347
+
2348
+ function selectedCharacterLabel() {
2349
+ return state.selected.character
2350
+ || "";
2351
+ }
2352
+
2353
+ function selectedCharacterToken() {
2354
+ const label = String(selectedCharacterLabel() || "").trim().toLowerCase();
2355
+ if (!label || label === "generate-gobananas-iterations" || label.startsWith("gobananas://character/")) return "";
2356
+ return label;
2357
+ }
2358
+
2359
+ function candidateMatchesSelectedCharacter(candidate) {
2360
+ const token = selectedCharacterToken();
2361
+ if (!token) return true;
2362
+ if (!candidate) return false;
2363
+ const outputText = (candidate.outputs || []).map((output) => output.path || "").join(" ");
2364
+ const text = `${candidate.prompt || ""} ${candidate.route || ""} ${candidate.source?.externalJobId || ""} ${outputText}`.toLowerCase();
2365
+ return text.includes(token);
2366
+ }
2367
+
2368
+ function compactPrompt(prompt) {
2369
+ const text = String(prompt || "").replace(/\s+/g, " ").trim();
2370
+ return text.length > 150 ? `${text.slice(0, 147)}...` : text;
2371
+ }
2372
+
2373
+ function candidatePreview(output) {
2374
+ if (!output?.path) return illustration("var(--color-ice-blue)");
2375
+ const type = ext(output.path);
2376
+ const url = asset(output.path);
2377
+ if (["jpg", "jpeg", "png", "gif", "webp"].includes(type)) {
2378
+ if (!candidateQuality(output).ok) return illustration("var(--color-stone-surface)");
2379
+ return `<div class="candidate-thumb"><img src="${url}" alt=""></div>`;
2380
+ }
2381
+ if (["mp4", "webm", "mov"].includes(type)) {
2382
+ if (/^https?:\/\//.test(String(output.path || ""))) {
2383
+ return `<div class="candidate-thumb"><div class="candidate-video-placeholder">Remote video candidate<br><a href="${asset(output.path)}" target="_blank" rel="noreferrer">Open link</a></div></div>`;
2384
+ }
2385
+ return `<div class="candidate-thumb"><video src="${url}" muted playsinline controls></video></div>`;
2386
+ }
2387
+ return `<div class="candidate-thumb">${illustration("var(--color-ice-blue)")}</div>`;
2388
+ }
2389
+
2390
+ function stillThumb(output, className = "storyboard-shot-thumb") {
2391
+ if (!output?.path) return `<div class="${className}">${illustration("var(--color-ice-blue)")}</div>`;
2392
+ const type = ext(output.path);
2393
+ if (!["jpg", "jpeg", "png", "gif", "webp"].includes(type) || !candidateQuality(output).ok) {
2394
+ return `<div class="${className}">${illustration("var(--color-ice-blue)")}</div>`;
2395
+ }
2396
+ return `<div class="${className}"><img src="${asset(output.path)}" alt=""></div>`;
2397
+ }
2398
+
2399
+ function outputKind(candidate) {
2400
+ return imageOutput(candidate)?.kind || "missing";
2401
+ }
2402
+
2403
+ function isGoBananasStill(candidate) {
2404
+ return candidate.route === "gobananas-storyboard-still" && outputKind(candidate) === "image";
2405
+ }
2406
+
2407
+ function isUpscaledStill(candidate) {
2408
+ return candidate.route === "upscaled-storyboard-still" && outputKind(candidate) === "image";
2409
+ }
2410
+
2411
+ function isGeneratedVideo(candidate) {
2412
+ return outputKind(candidate) === "video";
2413
+ }
2414
+
2415
+ function approvedStoryboardStillId(sceneIndex) {
2416
+ const locked = state.selected[`lockedStill-${sceneIndex}`];
2417
+ const lockedCandidate = locked ? findCandidateById(locked)?.candidate : null;
2418
+ if (lockedCandidate && isGoBananasStill(lockedCandidate)) return locked;
2419
+ const upscaled = (candidateScene(sceneIndex).candidates || [])
2420
+ .find((candidate) => isUpscaledStill(candidate) && candidate.source?.chainedFromCandidateId);
2421
+ return upscaled?.source?.chainedFromCandidateId || null;
2422
+ }
2423
+
2424
+ function primarySceneCandidate(sceneIndex) {
2425
+ const candidates = candidateScene(sceneIndex).candidates || [];
2426
+ const selectedId = approvedStoryboardStillId(sceneIndex) || candidateSelection(sceneIndex)?.selectedCandidateId;
2427
+ if (selectedId) {
2428
+ const selectedCandidate = candidates.find((candidate) => candidate.id === selectedId);
2429
+ if (selectedCandidate) return selectedCandidate;
2430
+ }
2431
+ return candidates.find((candidate) => isGoBananasStill(candidate) && !candidateRejectedForScene(sceneIndex, candidate.id)) || candidates[0] || null;
2432
+ }
2433
+
2434
+ function renderCandidateCards(sceneIndex, candidates, mode) {
2435
+ const sceneEnabled = isSceneWorkflowEnabled(sceneIndex);
2436
+ const selectedId = mode === "stills"
2437
+ ? approvedStoryboardStillId(sceneIndex)
2438
+ : candidateSelection(sceneIndex)?.selectedCandidateId;
2439
+ const orderedCandidates = [...candidates].sort((left, right) => {
2440
+ const leftOrder = candidateReviewOrder(sceneIndex, selectedId)(left);
2441
+ const rightOrder = candidateReviewOrder(sceneIndex, selectedId)(right);
2442
+ if (leftOrder !== rightOrder) return leftOrder - rightOrder;
2443
+ return String(left.id).localeCompare(String(right.id));
2444
+ });
2445
+ const rejectedCount = candidates.filter((candidate) => candidateRejectedForScene(sceneIndex, candidate.id)).length;
2446
+ if (!candidates.length) return "";
2447
+ return `<div class="candidate-gallery">${orderedCandidates.map((candidate) => {
2448
+ const output = imageOutput(candidate);
2449
+ const quality = mode === "video"
2450
+ ? { ok: Boolean(output?.path), label: output?.path ? "renderable video" : "missing output", detail: output?.path ? "Video output is available for playback." : "This video candidate has no output path." }
2451
+ : candidateQuality(output);
2452
+ const characterMatch = candidateMatchesSelectedCharacter(candidate);
2453
+ const isRejected = candidateRejectedForScene(sceneIndex, candidate.id);
2454
+ const isPending = candidatePendingForScene(sceneIndex, candidate.id);
2455
+ const isSelected = selectedId === candidate.id && !isRejected;
2456
+ const canLockStill = mode === "stills";
2457
+ const lockDisabled = !canLockStill || isRejected || !sceneEnabled || !quality.ok || !characterMatch;
2458
+ const secondaryDisabled = isRejected || !sceneEnabled;
2459
+ return `<article class="candidate-card ${isSelected ? "selected" : ""} ${isRejected ? "rejected" : ""} ${isPending ? "pending" : ""} ${quality.ok ? "" : "needs-review"}">
2460
+ ${candidatePreview(output)}
2461
+ <div class="candidate-body">
2462
+ <strong>${candidate.id}</strong>
2463
+ <p>${compactPrompt(candidate.prompt)}</p>
2464
+ <div class="chips">
2465
+ <span class="chip">${candidate.route}</span>
2466
+ <span class="chip">${candidate.status}</span>
2467
+ <span class="chip">${output?.kind || "no output"}</span>
2468
+ <span class="chip">${quality.label}</span>
2469
+ <span class="chip">${characterMatch ? `${selectedCharacterLabel() || "character"} match` : "identity mismatch"}</span>
2470
+ ${isRejected ? `<span class="chip">rejected</span>` : ""}
2471
+ ${isPending ? `<span class="chip">alternate</span>` : ""}
2472
+ </div>
2473
+ ${quality.ok ? "" : `<div class="candidate-warning">${quality.detail}</div>`}
2474
+ ${characterMatch ? "" : `<div class="candidate-warning">This candidate does not look tied to ${selectedCharacterLabel()}. Keep it rejected or regenerate the scene with the saved character.</div>`}
2475
+ <div class="card-actions">
2476
+ ${canLockStill && !isRejected ? `<button class="btn btn-primary" data-select-key="lockedStill-${sceneIndex}" data-select-value="${candidate.id}" ${disabledAttr(lockDisabled)}>${isSelected ? "Approved image" : !characterMatch ? "Wrong character" : quality.ok ? "Approve image" : "Fix URL first"}</button>` : ""}
2477
+ ${canLockStill ? `<button class="btn btn-secondary" data-select-key="draftStill-${sceneIndex}" data-select-value="${candidate.id}" ${disabledAttr(secondaryDisabled)}>Use as draft</button>` : ""}
2478
+ ${canLockStill ? `<button class="btn btn-secondary" data-select-key="editStill-${sceneIndex}" data-select-value="${candidate.id}-edit-needed" ${disabledAttr(secondaryDisabled)}>Needs prompt edit</button>` : ""}
2479
+ <button class="btn btn-secondary" data-inspect-key="candidate" data-inspect="${candidate.id}">Details</button>
2480
+ <button class="btn btn-secondary" data-action="${isRejected ? "restoreCandidate" : "rejectCandidate"}" data-candidate-id="${candidate.id}" data-scene-index="${sceneIndex}">${isRejected ? "Restore" : "Reject"}</button>
2481
+ </div>
2482
+ </div>
2483
+ </article>`;
2484
+ }).join("")}</div>${rejectedCount ? `<div class="empty-inline">${rejectedCount} rejected candidate${rejectedCount === 1 ? "" : "s"} will be excluded from lock checks and saved handoff selections.</div>` : ""}`;
2485
+ }
2486
+
2487
+ function candidateSection(title, hint, sceneIndex, candidates, mode) {
2488
+ if (!candidates.length) return "";
2489
+ return `<div class="candidate-section">
2490
+ <div class="candidate-section-header">
2491
+ <div><strong>${title}</strong><br><span>${hint}</span></div>
2492
+ <span class="chip">${candidates.length} item${candidates.length === 1 ? "" : "s"}</span>
2493
+ </div>
2494
+ ${renderCandidateCards(sceneIndex, candidates, mode)}
2495
+ </div>`;
2496
+ }
2497
+
2498
+ function renderCandidateGallery(sceneIndex) {
2499
+ const scene = candidateScene(sceneIndex);
2500
+ const candidates = scene.candidates || [];
2501
+ const stills = candidates.filter(isGoBananasStill);
2502
+ const upscaled = candidates.filter(isUpscaledStill);
2503
+ const videos = candidates.filter(isGeneratedVideo);
2504
+ const other = candidates.filter((candidate) => !isGoBananasStill(candidate) && !isUpscaledStill(candidate) && !isGeneratedVideo(candidate));
2505
+ const empty = !stills.length
2506
+ ? `<div class="empty-inline">No Go Bananas images are recorded for this scene yet. Edit the prompt, queue regeneration, then add the returned image URL.</div>`
2507
+ : "";
2508
+ return `${empty}
2509
+ ${candidateSection("Generated Go Bananas images", "Review these first. Approve one image or reject/regenerate before motion.", sceneIndex, stills, "stills")}
2510
+ ${candidateSection("4K / upscaled stills", "These are the approved stills prepared for Seedance. Attach one after approving a Go Bananas image.", sceneIndex, upscaled, "upscale")}
2511
+ ${candidateSection("Seedance video outputs", "These are motion results generated after image approval. They are for final video review, not still approval.", sceneIndex, videos, "video")}
2512
+ ${candidateSection("Other candidates", "Unclassified candidates are shown for provenance only.", sceneIndex, other, "other")}
2513
+ ${renderStillCandidateForm(sceneIndex, Boolean(stills.length))}`;
2514
+ }
2515
+
2516
+ function renderStillCandidateForm(sceneIndex, collapsed = false) {
2517
+ const queued = latestGenerationRequest(sceneIndex);
2518
+ const prompt = state.notes[`stillPrompt-${sceneIndex}`] || defaultStillPrompt(sceneIndex);
2519
+ const negativePrompt = state.notes[`stillNegative-${sceneIndex}`] || seedanceWorkflow.negativeGuidance.join(", ");
2520
+ return `<details class="still-workbench" ${collapsed ? "" : "open"}>
2521
+ <summary>Regenerate or add a Go Bananas image</summary>
2522
+ <div class="still-form">
2523
+ <div style="grid-column:1 / -1" class="empty-inline">
2524
+ <strong>Go Bananas prompt workbench</strong><br>
2525
+ ${queued ? `Queued Go Bananas request ${queued.id}. The agent can run it, then paste the returned image URL below.` : "Edit the prompt, queue a Go Bananas image, then paste the returned image URL to add it to the review board."}
2526
+ </div>
2527
+ <label><span class="form-label">Returned image URL</span>
2528
+ <input id="stillUrl-${sceneIndex}" placeholder="Paste GoBananas image URL for scene ${sceneIndex}" value="">
2529
+ </label>
2530
+ <label><span class="form-label">Go Bananas image id</span>
2531
+ <input id="stillImageId-${sceneIndex}" placeholder="GB id">
2532
+ </label>
2533
+ <label style="grid-column:1 / -1"><span class="form-label">Prompt to regenerate this scene</span>
2534
+ <textarea id="stillPrompt-${sceneIndex}" placeholder="Prompt or notes for this still">${prompt}</textarea>
2535
+ </label>
2536
+ <label style="grid-column:1 / -1"><span class="form-label">Negative prompt</span>
2537
+ <textarea id="stillNegative-${sceneIndex}" placeholder="Negative prompt">${negativePrompt}</textarea>
2538
+ </label>
2539
+ <label><span class="form-label">Upscaled / 4K image URL</span>
2540
+ <input id="upscaleUrl-${sceneIndex}" placeholder="Paste 4k/upscaled image URL after locking scene ${sceneIndex}" value="">
2541
+ </label>
2542
+ <label><span class="form-label">Upscale id</span>
2543
+ <input id="upscaleImageId-${sceneIndex}" placeholder="4k/upscale id">
2544
+ </label>
2545
+ <div class="card-actions">
2546
+ <button class="btn btn-secondary" data-action="queueStillRequest" data-scene-index="${sceneIndex}" ${disabledAttr(!isSceneWorkflowEnabled(sceneIndex))}>Regenerate with this prompt</button>
2547
+ <button class="btn btn-primary" data-action="recordStillCandidate" data-scene-index="${sceneIndex}" ${disabledAttr(!isSceneWorkflowEnabled(sceneIndex))}>Add candidate</button>
2548
+ <button class="btn btn-secondary" data-action="recordUpscaledStill" data-scene-index="${sceneIndex}" ${disabledAttr(!isCandidateBackedLock(sceneIndex))}>Attach 4k asset</button>
2549
+ </div>
2550
+ </div>
2551
+ </details>`;
2552
+ }
2553
+
2554
+ function defaultStillPrompt(sceneIndex) {
2555
+ const row = state.scenes[sceneIndex] || [];
2556
+ const title = row[1] || `Scene ${sceneIndex}`;
2557
+ const anchor = sceneIndex === 0
2558
+ ? "opening anchor"
2559
+ : `continue from scene-${String(sceneIndex - 1).padStart(2, "0")} locked end frame`;
2560
+ return `Scene ${String(sceneIndex).padStart(2, "0")} storyboard still for ${state.project}: ${title}. ${anchor}. ${seedanceWorkflow.promptPatterns.stillCreate} Warm cream paper, playful illustrated characters, vivid primary accents, clean cinematic composition, no readable text, no logos.`;
2561
+ }
2562
+
2563
+ function latestGenerationRequest(sceneIndex) {
2564
+ const requests = inventory.generationQueue?.requests || [];
2565
+ return [...requests].reverse().find((request) => request.sceneIndex === sceneIndex && request.status === "queued") || null;
2566
+ }
2567
+
2568
+ function media(path, fallbackColor = "var(--color-sky-blue)") {
2569
+ if (!path) return illustration(fallbackColor);
2570
+ const url = asset(path);
2571
+ const type = ext(path);
2572
+ if (["jpg", "jpeg", "png", "gif", "webp"].includes(type)) {
2573
+ return `<div class="media"><img src="${url}" alt=""></div>`;
2574
+ }
2575
+ if (["mp4", "webm", "mov"].includes(type)) {
2576
+ if (/^https?:\/\//.test(String(path || ""))) return illustration(fallbackColor);
2577
+ return `<div class="media"><video src="${url}" controls muted playsinline></video></div>`;
2578
+ }
2579
+ return illustration(fallbackColor);
2580
+ }
2581
+
2582
+ function finalVideoPath() {
2583
+ const fromPublish = inventory.publishReport?.finalOutputPath;
2584
+ if (fromPublish) return fromPublish;
2585
+ const finalAsset = (inventory.assetManifest?.assets || [])
2586
+ .find((item) => item.kind === "video" && /final/i.test(String(item.id || item.path || "")));
2587
+ return finalAsset?.path || "";
2588
+ }
2589
+
2590
+ function sceneVideoAsset(sceneIndex) {
2591
+ return (inventory.assetManifest?.assets || [])
2592
+ .find((item) => item.sceneIndex === sceneIndex && item.kind === "video") || null;
2593
+ }
2594
+
2595
+ function storyboardScenes() {
2596
+ const fromArtifact = Array.isArray(inventory.storyboard?.scenes) ? inventory.storyboard.scenes : [];
2597
+ if (fromArtifact.length) return fromArtifact;
2598
+ return state.scenes.map(([, title], sceneIndex) => ({
2599
+ sceneIndex,
2600
+ description: title,
2601
+ characters: [],
2602
+ }));
2603
+ }
2604
+
2605
+ function storySummaryText() {
2606
+ const title = inventory.brief?.title || state.project || "this project";
2607
+ const scenes = storyboardScenes();
2608
+ const characters = [...new Set(scenes.flatMap((scene) => Array.isArray(scene.characters) ? scene.characters : []))];
2609
+ const characterText = characters.length ? ` starring ${characters.map(prettyName).join(", ")}` : "";
2610
+ const firstBeat = restoreCharacterCase(cleanBeatDescription(scenes[0]?.description || "a clear problem hook"), characters);
2611
+ const finalBeat = restoreCharacterCase(cleanBeatDescription(scenes[scenes.length - 1]?.description || "a confident payoff"), characters);
2612
+ return `${prettyName(title)} is a ${scenes.length}-scene director storyboard${characterText}. It opens as ${firstBeat} and resolves with ${finalBeat}.`;
2613
+ }
2614
+
2615
+ function cleanBeatDescription(value) {
2616
+ return String(value || "")
2617
+ .replace(/^\s*(problem hook|solution reveal|proof beat|payoff(?: and cta)?|payoff hero)\s*:\s*/i, "")
2618
+ .replace(/\s+/g, " ")
2619
+ .replace(/[.。]+$/g, "")
2620
+ .trim();
2621
+ }
2622
+
2623
+ function restoreCharacterCase(text, characters) {
2624
+ return characters.reduce((next, character) => {
2625
+ const source = String(character || "").trim();
2626
+ if (!source) return next;
2627
+ const escaped = source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2628
+ return next.replace(new RegExp(`\\b${escaped}\\b`, "gi"), prettyName(source));
2629
+ }, String(text || ""));
2630
+ }
2631
+
2632
+ function renderFinalVideoBlock(compact = false) {
2633
+ const path = finalVideoPath();
2634
+ if (!path) return "";
2635
+ const url = asset(path);
2636
+ return `<div class="handoff-video">
2637
+ <video src="${url}" controls playsinline preload="metadata"></video>
2638
+ </div>
2639
+ ${compact ? "" : `<div class="card-actions">
2640
+ <a class="btn btn-primary" href="${url}" target="_blank" rel="noreferrer">Open final video</a>
2641
+ <a class="btn btn-secondary" href="${url}" download>Download MP4</a>
2642
+ </div>`}`;
2643
+ }
2644
+
2645
+ function sceneBriefsMarkup() {
2646
+ const scenes = storyboardScenes();
2647
+ return `<div class="scene-brief-grid">${scenes.map((scene) => {
2648
+ const sceneIndex = Number(scene.sceneIndex) || 0;
2649
+ const selected = candidateSelection(sceneIndex);
2650
+ const videoAsset = sceneVideoAsset(sceneIndex);
2651
+ const candidate = selected?.selectedCandidateId ? findCandidateById(selected.selectedCandidateId)?.candidate : null;
2652
+ const mediaPath = imageOutput(candidate)?.path || videoAsset?.path;
2653
+ const characters = Array.isArray(scene.characters) ? scene.characters.join(", ") : "";
2654
+ return `<article class="scene-brief">
2655
+ ${mediaPath ? media(mediaPath, "var(--color-ice-blue)") : illustration("var(--color-ice-blue)")}
2656
+ <div>
2657
+ <h3>Scene ${String(sceneIndex).padStart(2, "0")}</h3>
2658
+ <p>${escapeHtml(scene.description || defaultSceneTitle(sceneIndex))}</p>
2659
+ <div class="chips">
2660
+ <span class="chip">${selected?.selectedCandidateId || "no selection"}</span>
2661
+ <span class="chip">${videoAsset ? "video ready" : candidate ? "candidate ready" : "needs media"}</span>
2662
+ <span class="chip">${sceneIndex > 0 ? "chained from previous scene" : "opening anchor"}</span>
2663
+ ${characters ? `<span class="chip">${escapeHtml(characters)}</span>` : ""}
2664
+ </div>
2665
+ </div>
2666
+ </article>`;
2667
+ }).join("")}</div>`;
2668
+ }
2669
+
2670
+ function visualStoryboardStrip(compact = false) {
2671
+ const scenes = storyboardScenes();
2672
+ const stripClass = compact ? "handoff-mini-strip" : "visual-handoff-strip";
2673
+ const shotClass = compact ? "handoff-mini-shot" : "visual-handoff-shot";
2674
+ const thumbClass = compact ? "handoff-mini-thumb" : "handoff-shot-thumb";
2675
+ return `<div class="${stripClass}">${scenes.map((scene) => {
2676
+ const sceneIndex = Number(scene.sceneIndex) || 0;
2677
+ const candidate = primarySceneCandidate(sceneIndex);
2678
+ const candidateId = candidate?.id || "needs image";
2679
+ const label = `Scene ${String(sceneIndex).padStart(2, "0")}`;
2680
+ const description = cleanBeatDescription(scene.description || defaultSceneTitle(sceneIndex));
2681
+ return `<button class="${shotClass}" data-action="selectStoryboardScene" data-scene-index="${sceneIndex}">
2682
+ ${stillThumb(imageOutput(candidate), thumbClass)}
2683
+ <div>
2684
+ <strong>${label}</strong>
2685
+ <span>${escapeHtml(compactPrompt(description))}</span>
2686
+ <span>${escapeHtml(candidateId)}</span>
2687
+ </div>
2688
+ </button>`;
2689
+ }).join("")}</div>`;
2690
+ }
2691
+
2692
+ function lessonsMarkup() {
2693
+ return `<ul class="lesson-list">
2694
+ <li><strong>Show the story before the JSON.</strong> Operators need the project purpose, scene beats, and final media in plain English.</li>
2695
+ <li><strong>Make final output first-class.</strong> If a publish report has an MP4, the review handoff should embed it immediately.</li>
2696
+ <li><strong>Separate still review from motion review.</strong> Still candidates, upscaled locks, chained videos, and final assembly need different labels and gates.</li>
2697
+ <li><strong>Preserve provider lessons.</strong> Seedance chaining works when images and previous MP4s are classified into the right reference fields.</li>
2698
+ </ul>`;
2699
+ }
2700
+
2701
+ function renderHandoffSummary() {
2702
+ const publish = inventory.publishReport;
2703
+ const report = inventory.reviewReport;
2704
+ const metrics = report?.metrics || {};
2705
+ const statusText = publish?.status
2706
+ ? `Publish artifact status: ${publish.status}.`
2707
+ : report?.verdict
2708
+ ? `Review verdict: ${report.verdict}.`
2709
+ : "Review handoff is still in progress.";
2710
+ return `<div class="handoff-summary">
2711
+ <div class="summary-panel">
2712
+ <h3>What this video is about</h3>
2713
+ <p>${escapeHtml(storySummaryText())}</p>
2714
+ <div class="chips" style="margin-top:12px">
2715
+ <span class="chip">${statusText}</span>
2716
+ <span class="chip">${storyboardScenes().length} scenes</span>
2717
+ <span class="chip">${savedReviewPassed() ? "publish ready" : "reviewing"}</span>
2718
+ <span class="chip">Seedance director workflow</span>
2719
+ </div>
2720
+ <div class="card-actions" style="margin-top:14px">
2721
+ <button class="btn btn-primary" data-stage-jump="storyboard">Review Go Bananas images</button>
2722
+ <button class="btn btn-secondary" data-stage-jump="motion">Review motion handoff</button>
2723
+ </div>
2724
+ </div>
2725
+ <div class="summary-panel">
2726
+ <h3>Visual storyboard</h3>
2727
+ <p>These are the selected Go Bananas stills. Click any frame to reopen that scene's human review loop.</p>
2728
+ ${visualStoryboardStrip()}
2729
+ </div>
2730
+ ${finalVideoPath() ? `<div class="summary-panel">
2731
+ <h3>Final video</h3>
2732
+ <p>${escapeHtml((publish?.notes || [])[0] || "Final MP4 is available for review.")}</p>
2733
+ ${renderFinalVideoBlock()}
2734
+ </div>` : ""}
2735
+ <div class="summary-panel">
2736
+ <h3>Scene-by-scene handover</h3>
2737
+ <p>Each beat below shows what the scene is doing and whether it has a selected video or still.</p>
2738
+ ${sceneBriefsMarkup()}
2739
+ </div>
2740
+ <div class="summary-panel">
2741
+ <h3>Lessons learned from this run</h3>
2742
+ ${lessonsMarkup()}
2743
+ </div>
2744
+ </div>`;
2745
+ }
2746
+
2747
+ function illustration(color) {
2748
+ return `<div class="media"><div class="paper-illustration"><div class="mini-blob" style="--fill:${color}"></div></div></div>`;
2749
+ }
2750
+
2751
+ function card({ key, id, title, desc, chips = [], image, color, action = "Select" }) {
2752
+ const active = selected(key, id);
2753
+ return `<article class="item-card ${active ? "selected" : ""}">
2754
+ ${media(image, color)}
2755
+ <div class="item-body">
2756
+ <h3>${title}</h3>
2757
+ <p>${desc}</p>
2758
+ <div class="chips">${chips.map((chip) => `<span class="chip">${chip}</span>`).join("")}</div>
2759
+ <div class="card-actions">
2760
+ <button class="btn btn-primary" data-select-key="${key}" data-select-value="${id}">${active ? "Selected" : action}</button>
2761
+ <button class="btn btn-secondary" data-inspect-key="${key}" data-inspect="${id}">Details</button>
2762
+ </div>
2763
+ </div>
2764
+ </article>`;
2765
+ }
2766
+
2767
+ function emptyCharacterState() {
2768
+ return `<div class="empty-state">
2769
+ <div class="mini-blob" style="--fill:var(--color-ember-orange)"></div>
2770
+ <div>
2771
+ <strong>No saved project characters yet.</strong>
2772
+ <p>The repo has project folders, but no <code>projects/&lt;slug&gt;/characters/characters.json</code> entries. The real flow should call GoBananas MCP here, generate iterations, then save the chosen character with GB id and reference assets.</p>
2773
+ <div class="card-actions">
2774
+ <button class="btn btn-primary" data-select-key="characterPlan" data-select-value="generate-gobananas-iterations">Generate GoBananas iterations</button>
2775
+ <button class="btn btn-secondary" data-select-key="characterPlan" data-select-value="import-gobananas-library">Import from GB library</button>
2776
+ </div>
2777
+ </div>
2778
+ </div>`;
2779
+ }
2780
+
2781
+ function renderInventory() {
2782
+ const stillCandidateCount = (inventory.sceneCandidates?.scenes || [])
2783
+ .reduce((count, scene) => count + (scene.candidates?.filter((candidate) => candidate.outputs?.some((output) => output.kind === "image")).length || 0), 0);
2784
+ const stats = [
2785
+ ["Projects", inventory.projects.length],
2786
+ ["Saved characters", inventory.characters.length],
2787
+ ["Still candidates", stillCandidateCount],
2788
+ ["Presenter packs", inventory.presenterPacks.length],
2789
+ ["Media assets", inventory.mediaAssets.length],
2790
+ ["Playbooks", inventory.playbooks.length],
2791
+ ["Prompt refs", inventory.references.length],
2792
+ ["Templates", inventory.templates.length],
2793
+ ["Schemas", inventory.schemas.length]
2794
+ ];
2795
+ return `<div class="stats-grid">${stats.map(([label, count]) => `<div class="stat-card"><strong>${count}</strong><span>${label}</span></div>`).join("")}</div>
2796
+ <div class="controls">
2797
+ <input id="filter" placeholder="Search assets, templates, playbooks" value="${state.filter}">
2798
+ <select id="tab">
2799
+ ${["all", "characters", "assets", "playbooks", "templates", "schemas"].map((tab) => `<option value="${tab}" ${state.tab === tab ? "selected" : ""}>${tab}</option>`).join("")}
2800
+ </select>
2801
+ <button class="btn btn-secondary" data-action="clearFilter">Clear</button>
2802
+ </div>
2803
+ ${inventorySection()}`;
2804
+ }
2805
+
2806
+ function inventorySection() {
2807
+ const show = (tab) => state.tab === "all" || state.tab === tab;
2808
+ let html = "";
2809
+ if (show("characters")) {
2810
+ html += `<h3>Saved project characters</h3>${inventory.characters.length ? `<div class="cards-grid">${characterCards()}</div>` : emptyCharacterState()}`;
2811
+ }
2812
+ if (show("assets")) {
2813
+ html += `<h3 style="margin-top:28px">Existing selectable assets</h3><div class="cards-grid">${assetCards().join("")}</div>`;
2814
+ }
2815
+ if (show("playbooks")) {
2816
+ html += `<h3 style="margin-top:28px">Provider playbooks</h3><div class="cards-grid">${inventory.playbooks.map((item) => card({ key: "playbook", id: item.id, title: item.name, desc: item.desc, chips: [item.provider, "playbook"], color: "var(--color-meadow-green)", action: "Use" })).join("")}</div>`;
2817
+ }
2818
+ if (show("templates")) {
2819
+ html += `<h3 style="margin-top:28px">Storyboard templates</h3><div class="cards-grid">${templateCards()}</div>`;
2820
+ }
2821
+ if (show("schemas")) {
2822
+ html += `<h3 style="margin-top:28px">Artifact schemas</h3><div class="small-grid">${inventory.schemas.map((schema) => card({ key: "schema", id: schema, title: schema, desc: `schemas/video/artifacts/${schema}.schema.json`, chips: ["JSON", "artifact"], color: "var(--color-sunburst-yellow)", action: "Inspect" })).join("")}</div>`;
2823
+ }
2824
+ return html;
2825
+ }
2826
+
2827
+ function assetCards() {
2828
+ const packAssets = inventory.presenterPacks.flatMap((pack) => pack.assets.map((path) => ({ path, label: pack.name })));
2829
+ const packPaths = new Set(packAssets.map((item) => item.path));
2830
+ const mediaAssets = inventory.mediaAssets
2831
+ .filter((path) => !packPaths.has(path))
2832
+ .map((path) => ({ path, label: path.split("/").slice(0, -1).join("/") || "asset" }));
2833
+ const query = state.filter.trim().toLowerCase();
2834
+ return [...packAssets, ...mediaAssets]
2835
+ .filter((item) => !query || `${item.path} ${item.label}`.toLowerCase().includes(query))
2836
+ .map((item) => card({
2837
+ key: "asset",
2838
+ id: item.path,
2839
+ title: item.path.split("/").pop(),
2840
+ desc: item.path,
2841
+ chips: [item.label, ext(item.path)],
2842
+ image: item.path,
2843
+ action: "Attach"
2844
+ }));
2845
+ }
2846
+
2847
+ function templateCards() {
2848
+ return inventory.templates.map(([id, desc, count]) => card({
2849
+ key: "template",
2850
+ id,
2851
+ title: id,
2852
+ desc,
2853
+ chips: [`${count} scenes`, "template"],
2854
+ color: "var(--color-ice-blue)",
2855
+ action: "Use"
2856
+ })).join("");
2857
+ }
2858
+
2859
+ function characterCards() {
2860
+ return inventory.characters.map((character, index) => card({
2861
+ key: "character",
2862
+ id: character.id,
2863
+ title: character.name,
2864
+ desc: character.description || "Saved project character profile.",
2865
+ chips: [
2866
+ character.goBananasId ? `GB ${character.goBananasId}` : "local",
2867
+ `${character.referenceAssets?.length || 0} refs`
2868
+ ],
2869
+ image: character.referenceAssets?.find((path) => !path.startsWith("gobananas://")),
2870
+ color: index % 3 === 0 ? "var(--color-ember-orange)" : index % 3 === 1 ? "var(--color-meadow-green)" : "var(--color-sky-blue)",
2871
+ action: "Use character"
2872
+ })).join("");
2873
+ }
2874
+
2875
+ function renderCharacters() {
2876
+ const queued = inventory.characterQueue?.requests || [];
2877
+ const characterPrompt = state.notes.characterPrompt || defaultCharacterPrompt();
2878
+ const negativePrompt = state.notes.characterNegative || "inconsistent identity, realistic human photo, readable text, logos, clutter, distorted anatomy, extra limbs";
2879
+ return `${inventory.characters.length ? `<h3>Saved project characters</h3><div class="cards-grid">${characterCards()}</div>` : emptyCharacterState()}
2880
+ <div class="quality-panel" style="margin-top:28px">
2881
+ <h3>GoBananas character iterations</h3>
2882
+ <p>${queued.length ? `${queued.length} character iteration request${queued.length === 1 ? "" : "s"} queued. Generate the options, then save the chosen character profile before storyboard work continues.` : "Queue a concrete character iteration prompt so the agent can generate options without guessing."}</p>
2883
+ <div class="still-form">
2884
+ <input id="characterName" placeholder="Character name" value="${state.notes.characterName || "Komo"}">
2885
+ <input id="characterCount" placeholder="Iterations" value="${state.notes.characterCount || "4"}">
2886
+ <textarea id="characterPrompt" placeholder="Character generation prompt">${characterPrompt}</textarea>
2887
+ <textarea id="characterNegative" placeholder="Negative prompt">${negativePrompt}</textarea>
2888
+ <div class="card-actions">
2889
+ <button class="btn btn-primary" data-action="queueCharacterRequest">Queue character iterations</button>
2890
+ </div>
2891
+ </div>
2892
+ ${queued.length ? `<div class="details-list">${queued.slice(-3).reverse().map((request) => `<div class="details-row"><span>${request.id}</span><span>${request.characterName} · ${request.count} option${request.count === 1 ? "" : "s"} · ${request.aspectRatio} · ${request.status}${request.goBananasId ? ` · GB ${request.goBananasId}` : ""}${request.error ? ` · ${request.error}` : ""}</span></div>`).join("")}</div>` : ""}
2893
+ </div>
2894
+ <h3 style="margin-top:28px">Existing presenter packs</h3>
2895
+ <div class="cards-grid">${inventory.presenterPacks.map((pack, index) => card({
2896
+ key: "characterSource",
2897
+ id: pack.id,
2898
+ title: pack.name,
2899
+ desc: pack.notes,
2900
+ chips: [`${pack.assets.length} assets`, "existing pack"],
2901
+ image: pack.assets[0],
2902
+ color: index ? "var(--color-sky-blue)" : "var(--color-ember-orange)",
2903
+ action: "Use pack"
2904
+ })).join("")}</div>
2905
+ <h3 style="margin-top:28px">Character lock checklist</h3>
2906
+ <div class="scene-list">${["Canonical identity", "Full-body reference", "Close-up texture reference", "Expression variants", "Action bridge poses", "Negative identity constraints"].map((label, index) => `
2907
+ <div class="scene-row"><strong>${String(index + 1).padStart(2, "0")}</strong><div>${label}<br><span>Required before stable multi-scene generation</span></div><button class="btn btn-secondary" data-select-key="characterCheck-${index}" data-select-value="done">Mark done</button></div>
2908
+ `).join("")}</div>`;
2909
+ }
2910
+
2911
+ function defaultCharacterPrompt() {
2912
+ return "Create 4 consistent character design iterations for Komo, a playful flat illustrated fintech adventure mascot on warm cream paper. Organic blob silhouette, expressive dot eyes, simple smile, stick limbs, vivid primary accent color, clean full-body view plus identity consistency. Pixar storyboard charm without photorealism. No readable text, no logos, no clutter.";
2913
+ }
2914
+
2915
+ function renderReferences() {
2916
+ const mustHaveRoles = ["identity", "pose", "lookdev", "background", "ui-structure", "prop", "start-frame", "end-frame", "texture"];
2917
+ return `<div class="now-box ready" style="margin-bottom:18px">
2918
+ <div>
2919
+ <strong>One reference, one job.</strong>
2920
+ <span>Assign roles before any image influences prompts. This prevents pose references from changing identity, UI references from creating readable text, and lookdev references from rewriting the scene.</span>
2921
+ </div>
2922
+ <div class="card-actions">
2923
+ <button class="btn btn-primary" data-action="autoRoleReferences">Auto-role essentials</button>
2924
+ <button class="btn btn-secondary" data-action="assignSelectedReferenceRoles">Use selected reference</button>
2925
+ </div>
2926
+ </div>
2927
+ <h3>Required reference roles</h3>
2928
+ <div class="quality-grid">${mustHaveRoles.map((role) => {
2929
+ const done = state.selected[`referenceRole-${role}`];
2930
+ return `<div class="quality-item ${done ? "done" : ""}">
2931
+ <span class="gate-dot">${done ? "✓" : "!"}</span>
2932
+ <div>
2933
+ <strong>${role}</strong>
2934
+ <span>${done ? `Assigned to ${done}` : "Assign or confirm before generation."}</span>
2935
+ <div style="margin-top:8px"><button class="btn btn-secondary" data-select-key="referenceRole-${role}" data-select-value="${role}-assigned">Mark assigned</button></div>
2936
+ </div>
2937
+ </div>`;
2938
+ }).join("")}</div>
2939
+ <h3 style="margin-top:28px">Reference vocabulary</h3>
2940
+ <div class="role-grid">${Object.entries(inventory.roles).map(([type, roles]) => `
2941
+ <div class="role-card"><strong>${type}</strong>${roles.join(", ")}</div>
2942
+ `).join("")}</div>
2943
+ <h3 style="margin-top:28px">Prompt and guidance references</h3>
2944
+ <div class="cards-grid">${inventory.references.map(([id, desc]) => card({ key: "reference", id, title: id, desc, chips: ["guidance", "markdown"], color: "var(--color-meadow-green)", action: "Attach" })).join("")}</div>
2945
+ <h3 style="margin-top:28px">Reference note</h3>
2946
+ <textarea id="referenceNote" placeholder="Example: use this image only for identity, not wardrobe or background.">${state.notes.reference || ""}</textarea>
2947
+ <div style="margin-top:12px"><button class="btn btn-primary" data-save-note="reference">Save note</button></div>`;
2948
+ }
2949
+
2950
+ function sceneReviewStatus(sceneIndex) {
2951
+ const scene = candidateScene(sceneIndex);
2952
+ const candidates = scene.candidates || [];
2953
+ const generated = candidates.filter(isGoBananasStill).length;
2954
+ const upscaled = candidates.filter(isUpscaledStill).length;
2955
+ const videos = candidates.filter(isGeneratedVideo).length;
2956
+ const approved = approvedStoryboardStillId(sceneIndex);
2957
+ const has4k = hasArtifactBackedUpscale(sceneIndex);
2958
+ const ready = Boolean(approved && has4k);
2959
+ return {
2960
+ generated,
2961
+ upscaled,
2962
+ videos,
2963
+ approved,
2964
+ has4k,
2965
+ ready,
2966
+ label: ready ? "ready for motion" : approved ? "attach 4K still" : generated ? "choose Go Bananas image" : "generate image"
2967
+ };
2968
+ }
2969
+
2970
+ function renderStoryboardScenePicker() {
2971
+ return `<div class="scene-review-switcher">${state.scenes.map(([id, title], index) => {
2972
+ const status = sceneReviewStatus(index);
2973
+ const active = clampSceneIndex(state.activeSceneIndex) === index;
2974
+ const candidate = primarySceneCandidate(index);
2975
+ return `<button class="scene-switch ${active ? "active" : ""} ${status.ready ? "done" : ""}" data-action="selectStoryboardScene" data-scene-index="${index}">
2976
+ ${stillThumb(imageOutput(candidate), "scene-switch-thumb")}
2977
+ <strong>${id}: ${title}</strong>
2978
+ <span>${status.label}</span>
2979
+ <span>${status.generated} GB image${status.generated === 1 ? "" : "s"} · ${status.upscaled} 4K · ${status.videos} video${status.videos === 1 ? "" : "s"}</span>
2980
+ </button>`;
2981
+ }).join("")}</div>`;
2982
+ }
2983
+
2984
+ function renderStoryboardDashboard() {
2985
+ const completed = state.scenes.filter((_, index) => sceneReviewStatus(index).ready).length;
2986
+ return `<section class="storyboard-dashboard-wrap">
2987
+ <div class="storyboard-dashboard-head">
2988
+ <div>
2989
+ <h3>Storyboard dashboard</h3>
2990
+ <p>Human-in-the-loop review: click a frame, inspect the selected image, then approve, reject, regenerate, or attach the 4K still.</p>
2991
+ </div>
2992
+ <div class="chips">
2993
+ <span class="chip">${completed}/${state.scenes.length} motion-ready</span>
2994
+ <span class="chip">Go Bananas image-first</span>
2995
+ </div>
2996
+ </div>
2997
+ <div class="storyboard-dashboard">${state.scenes.map(([id, title], index) => {
2998
+ const status = sceneReviewStatus(index);
2999
+ const active = clampSceneIndex(state.activeSceneIndex) === index;
3000
+ const candidate = primarySceneCandidate(index);
3001
+ const candidateId = candidate?.id || "no image yet";
3002
+ return `<button class="storyboard-shot-card ${active ? "active" : ""} ${status.ready ? "done" : ""}" data-action="selectStoryboardScene" data-scene-index="${index}">
3003
+ ${stillThumb(imageOutput(candidate))}
3004
+ <strong>${id}: ${title}</strong>
3005
+ <span>${candidateId}</span>
3006
+ <div class="shot-card-footer"><span>${status.label}</span><b>${active ? "Open" : "Review"}</b></div>
3007
+ </button>`;
3008
+ }).join("")}</div>
3009
+ </section>`;
3010
+ }
3011
+
3012
+ function renderTemplateChooser() {
3013
+ if (!state.selected.template) {
3014
+ return `<h3>Storyboard template</h3><div class="cards-grid">${templateCards()}</div>`;
3015
+ }
3016
+ const selectedTemplate = inventory.templates.find(([id]) => id === state.selected.template);
3017
+ return `<details class="details-panel">
3018
+ <summary><strong>Storyboard template:</strong> ${state.selected.template}</summary>
3019
+ <p>${selectedTemplate?.[1] || "Template selected for this project."}</p>
3020
+ <div class="cards-grid">${templateCards()}</div>
3021
+ </details>`;
3022
+ }
3023
+
3024
+ function renderSceneReviewCard(index) {
3025
+ const [id, title, status, chain] = state.scenes[index] || state.scenes[0];
3026
+ const lockedReady = isCandidateBackedLock(index);
3027
+ const sceneEnabled = isSceneWorkflowEnabled(index);
3028
+ const hasPlaceholderLock = state.selected[`lockedStill-${index}`] && !lockedReady;
3029
+ const lockedStill = state.selected[`lockedStill-${index}`];
3030
+ const upscaleValue = lockedStill ? `${lockedStill}-4k` : "";
3031
+ const upscaleMarked = isSceneUpscaled(index);
3032
+ const upscaleBacked = hasArtifactBackedUpscale(index);
3033
+ const approvedStill = approvedStoryboardStillId(index);
3034
+ const reviewStatus = sceneReviewStatus(index);
3035
+ const selectedCandidate = primarySceneCandidate(index);
3036
+ return `<section class="scene-review-card" id="scene-review-${index}">
3037
+ <div class="scene-review-header">
3038
+ <div>
3039
+ <div class="section-kicker">Review scene ${index + 1} of ${state.scenes.length}</div>
3040
+ <h3>${id}: ${title}</h3>
3041
+ <p>${status} · ${chain ? "chain from previous approved image" : "opening anchor"}</p>
3042
+ <div class="chips" style="margin-top:8px">
3043
+ <span class="chip">${reviewStatus.generated} Go Bananas image${reviewStatus.generated === 1 ? "" : "s"}</span>
3044
+ <span class="chip">${approvedStill ? `approved ${approvedStill}` : lockedReady ? "locked" : hasPlaceholderLock ? "needs real candidate" : "needs approval"}</span>
3045
+ <span class="chip">${upscaleBacked ? "4K attached" : upscaleMarked ? "4K marker only" : "needs 4K"}</span>
3046
+ <span class="chip">${reviewStatus.videos ? `${reviewStatus.videos} motion output${reviewStatus.videos === 1 ? "" : "s"}` : "no motion yet"}</span>
3047
+ </div>
3048
+ <div class="empty-inline" style="margin-top:8px">${sceneWorkflowMessage(index)}${upscaleMarked && !upscaleBacked ? " Upscale is currently an operator marker; attach the actual 4K still before video generation." : ""}</div>
3049
+ </div>
3050
+ <div class="card-actions">
3051
+ <button class="btn btn-secondary" data-action="queueStillRequest" data-scene-index="${index}" ${disabledAttr(!sceneEnabled)}>Regenerate</button>
3052
+ <button class="btn btn-primary" data-action="explainCandidateLock">Approve image below</button>
3053
+ <button class="btn btn-secondary" data-select-key="upscaledStill-${index}" data-select-value="${upscaleValue}" ${disabledAttr(!upscaleBacked)}>${upscaleBacked ? "4K attached" : "Use attach form"}</button>
3054
+ </div>
3055
+ </div>
3056
+ <div class="selected-still-panel">
3057
+ ${stillThumb(imageOutput(selectedCandidate))}
3058
+ <div>
3059
+ <h4>${selectedCandidate ? `Selected still: ${selectedCandidate.id}` : "No selected still yet"}</h4>
3060
+ <p>${selectedCandidate ? compactPrompt(selectedCandidate.prompt) : "Generate or add a Go Bananas image, then approve it here before motion."}</p>
3061
+ <div class="chips">
3062
+ <span class="chip">${selectedCandidate?.source?.externalJobId ? `GB ${selectedCandidate.source.externalJobId}` : "no GB id"}</span>
3063
+ <span class="chip">${approvedStill ? "approved image" : "needs approval"}</span>
3064
+ <span class="chip">${upscaleBacked ? "4K attached" : "4K still needed"}</span>
3065
+ </div>
3066
+ </div>
3067
+ </div>
3068
+ ${renderCandidateGallery(index)}
3069
+ </section>`;
3070
+ }
3071
+
3072
+ function renderStoryboard() {
3073
+ const activeSceneIndex = clampSceneIndex(state.activeSceneIndex);
3074
+ return `<div class="now-box ready" style="margin-bottom:18px">
3075
+ <div>
3076
+ <strong>Storyboard image review is where Go Bananas outputs live.</strong>
3077
+ <span>For each scene: edit the prompt, regenerate if needed, compare the generated images, approve one image, then attach the 4K/upscaled version before Seedance motion.</span>
3078
+ </div>
3079
+ <button class="btn btn-primary" data-action="recommendStoryboardScene">Show next scene</button>
3080
+ </div>
3081
+ ${renderStoryboardDashboard()}
3082
+ <div class="operator-steps">
3083
+ <div class="operator-step"><strong>1. Pick scene</strong><span>Work top-to-bottom. Later scenes stay chained to the previous approved image.</span></div>
3084
+ <div class="operator-step"><strong>2. Edit prompt</strong><span>Change only what is wrong: pose, framing, identity, clutter, or composition.</span></div>
3085
+ <div class="operator-step"><strong>3. Review images</strong><span>Generated Go Bananas images appear as cards. Approve, reject, or inspect details.</span></div>
3086
+ <div class="operator-step"><strong>4. Regenerate or lock</strong><span>If it is wrong, regenerate. If it is right, approve it and attach the 4K still.</span></div>
3087
+ </div>
3088
+ ${renderTemplateChooser()}
3089
+ <div class="quality-panel">
3090
+ <h3>Still image recipe</h3>
3091
+ <p>Each scene must pass draft, narrow edit, lock, and upscale before motion. The create and edit prompt recipes below come from the Seedance reference workflow.</p>
3092
+ <div class="small-grid">
3093
+ <div class="role-card"><strong>Create prompt</strong>${seedanceWorkflow.promptPatterns.stillCreate}</div>
3094
+ <div class="role-card"><strong>Edit prompt</strong>${seedanceWorkflow.promptPatterns.stillEdit}</div>
3095
+ </div>
3096
+ </div>
3097
+ <h3 style="margin-top:28px">Go Bananas image review board</h3>
3098
+ <div class="storyboard-scene">${renderSceneReviewCard(activeSceneIndex)}</div>
3099
+ <h3 style="margin-top:28px">Edit instruction for next still pass</h3>
3100
+ <textarea id="storyboardNote" placeholder="Example: same character, wider shot, cleaner board layout, no readable UI text.">${state.notes.storyboard || ""}</textarea>
3101
+ <div style="margin-top:12px"><button class="btn btn-primary" data-save-note="storyboard">Save instruction</button></div>`;
3102
+ }
3103
+
3104
+ function renderMotion() {
3105
+ const candidates = [
3106
+ ["control-pass", "Detailed control pass", "Precise action, more control, slower pacing.", "long prompt"],
3107
+ ["short-variant", "Short variant pass", "More variety and cleaner timing from the same inputs.", "short prompt"],
3108
+ ["bridge-pose-pass", "Bridge pose pass", "Best for hands, throws, catches, transformations, and logo morphs.", "bridge poses"]
3109
+ ];
3110
+ return `<div class="quality-panel">
3111
+ <h3>Continuity chain</h3>
3112
+ <p>Motion is only allowed after locked stills exist. Each shot uses a start frame and target frame, then the generated end frame becomes the next shot's start frame.</p>
3113
+ <div class="scene-list">${state.scenes.map(([id], index) => `
3114
+ <div class="scene-row">
3115
+ <strong>${id}</strong>
3116
+ <div>${index === 0 ? "locked still -> first motion start frame" : `scene-${String(index - 1).padStart(2, "0")} end frame -> ${id} start frame`}<br><span>${seedanceWorkflow.promptPatterns.motionControl}</span></div>
3117
+ <button class="btn btn-secondary" data-select-key="continuity-${index}" data-select-value="${index === 0 ? "start-frame-confirmed" : "extract-end-frame"}">${isContinuityConfirmed(index) ? "Confirmed" : "Confirm"}</button>
3118
+ </div>
3119
+ `).join("")}</div>
3120
+ </div>
3121
+ <div class="now-box blocked" style="margin-bottom:18px">
3122
+ <div>
3123
+ <strong>Bridge-pose warning</strong>
3124
+ <span>Use bridge poses for ${seedanceWorkflow.bridgeTriggers.join(", ")}. This is what keeps hard action from looking random.</span>
3125
+ </div>
3126
+ <button class="btn btn-primary" data-select-key="bridgePosePlan" data-select-value="bridge-hard-actions">${state.selected.bridgePosePlan ? "Bridge plan saved" : "Require bridge poses"}</button>
3127
+ </div>
3128
+ <div class="cards-grid">${candidates.map(([id, title, desc, chip], index) => card({
3129
+ key: "motionCandidate",
3130
+ id,
3131
+ title,
3132
+ desc,
3133
+ chips: [chip, "Seedance"],
3134
+ color: index === 0 ? "var(--color-sky-blue)" : index === 1 ? "var(--color-meadow-green)" : "var(--color-sunburst-yellow)",
3135
+ action: "Choose"
3136
+ })).join("")}</div>
3137
+ `;
3138
+ }
3139
+
3140
+ function renderAssembly() {
3141
+ const plans = [
3142
+ ["social-fast", "Social fast cut", "Fast retiming and strongest short-form retention."],
3143
+ ["balanced", "Balanced story cut", "Best voiceover fit and readable transitions."],
3144
+ ["cinematic", "Cinematic polish", "Slower timing and stronger logo reveal."]
3145
+ ];
3146
+ return `${renderHandoffSummary()}
3147
+ <h3 style="margin-top:28px">Assembly plan</h3>
3148
+ <div class="cards-grid">${plans.map(([id, title, desc], index) => card({
3149
+ key: "assemblyPlan",
3150
+ id,
3151
+ title,
3152
+ desc,
3153
+ chips: ["postPlan", "time remap"],
3154
+ color: index === 0 ? "var(--color-ember-orange)" : index === 1 ? "var(--color-meadow-green)" : "var(--color-sky-blue)",
3155
+ action: "Approve"
3156
+ })).join("")}</div>
3157
+ <h3 style="margin-top:28px">Publish approvals</h3>
3158
+ <div class="scene-list">${assemblyChecks().map(([id, title, desc], index) => {
3159
+ const done = isAssemblyCheckDone(id);
3160
+ return `<div class="scene-row">
3161
+ <strong>${String(index + 1).padStart(2, "0")}</strong>
3162
+ <div>${title}<br><span>${desc}</span></div>
3163
+ <button class="btn btn-secondary" data-select-key="assemblyCheck-${id}" data-select-value="${id}-approved">${done ? "Approved" : "Approve"}</button>
3164
+ </div>`;
3165
+ }).join("")}</div>`;
3166
+ }
3167
+
3168
+ function content() {
3169
+ if (state.stage === "inventory") return renderInventory();
3170
+ if (state.stage === "characters") return renderCharacters();
3171
+ if (state.stage === "references") return renderReferences();
3172
+ if (state.stage === "storyboard") return renderStoryboard();
3173
+ if (state.stage === "motion") return renderMotion();
3174
+ return renderAssembly();
3175
+ }
3176
+
3177
+ function qualityChecks() {
3178
+ const selected = state.selected;
3179
+ return [
3180
+ ["Script/voiceover anchor", Boolean(state.notes.voiceover || state.notes.storyboard), "Every visual beat should map to story timing."],
3181
+ ["Role-tagged reference board", Boolean(selected.reference || state.notes.reference), "References must have jobs: identity, pose, lookdev, UI, prop, start/end frame."],
3182
+ ["Still-frame lock before motion", Boolean(selected.template && isCandidateBackedLock(0)), "Storyboard stills are refined and locked before Seedance."],
3183
+ ["Upscale readiness", state.scenes.every((_, index) => isSceneUpscaled(index)), "Every locked still needs an artifact-backed 4K/upscaled asset before image-to-video."],
3184
+ ["Start/end continuity chain", state.scenes.every((_, index) => isContinuityConfirmed(index)), "Use current start frame and next locked still as target frame."],
3185
+ ["Control plus short variant plan", Boolean(selected.motionCandidate), "Record the detailed control pass and short rerun variant before video generation."],
3186
+ ["Bridge hard actions", Boolean(selected.bridgePosePlan), "Add in-between poses for catches, throws, transformations, and logo morphs."],
3187
+ ["Post polish plan", Boolean(selected.assemblyPlan && missingAssemblyChecks().length === 0), "Time remap, voiceover fit, opacity, and logo reveal are planned before publish."]
3188
+ ];
3189
+ }
3190
+
3191
+ function qualityScore() {
3192
+ const checks = qualityChecks();
3193
+ return checks.filter(([, done]) => done).length;
3194
+ }
3195
+
3196
+ function renderQualityPanel() {
3197
+ const checks = qualityChecks();
3198
+ return `<div class="quality-panel">
3199
+ <h3>Seedance director system</h3>
3200
+ <p>This is the production recipe from <code>${seedanceWorkflow.source}</code>. The ledger records these checks so the next agent can produce a cinematic, continuity-safe video without rethinking the workflow.</p>
3201
+ <div class="quality-grid">${checks.map(([label, done, detail], index) => `
3202
+ <div class="quality-item ${done ? "done" : ""}">
3203
+ <span class="gate-dot">${done ? "✓" : index + 1}</span>
3204
+ <div><strong>${label}</strong><span>${detail}</span></div>
3205
+ </div>
3206
+ `).join("")}</div>
3207
+ </div>`;
3208
+ }
3209
+
3210
+ function findCandidateById(id) {
3211
+ for (const scene of inventory.sceneCandidates?.scenes || []) {
3212
+ const candidate = scene.candidates?.find((item) => item.id === id);
3213
+ if (candidate) return { sceneIndex: scene.sceneIndex, candidate };
3214
+ }
3215
+ return null;
3216
+ }
3217
+
3218
+ function roleAssignmentsForSelection(id) {
3219
+ return Object.entries(state.selected)
3220
+ .filter(([key, value]) => key.startsWith("referenceRole-") && value === id)
3221
+ .map(([key]) => key.replace(/^referenceRole-/, ""));
3222
+ }
3223
+
3224
+ function detailRows(rows) {
3225
+ return `<div class="details-list">${rows
3226
+ .filter(([, value]) => value !== undefined && value !== null && value !== "")
3227
+ .map(([label, value]) => `<div class="details-row"><span>${label}</span><span>${value}</span></div>`)
3228
+ .join("")}</div>`;
3229
+ }
3230
+
3231
+ function selectedDetail() {
3232
+ if (!state.inspected?.id) return null;
3233
+ const { key, id } = state.inspected;
3234
+ if (key === "candidate") {
3235
+ const found = findCandidateById(id);
3236
+ if (!found) return null;
3237
+ const output = imageOutput(found.candidate);
3238
+ const quality = candidateQuality(output);
3239
+ const rejected = candidateRejectedForScene(found.sceneIndex, id);
3240
+ const locked = state.selected[`lockedStill-${found.sceneIndex}`] === id && !rejected;
3241
+ return {
3242
+ title: found.candidate.id,
3243
+ desc: compactPrompt(found.candidate.prompt),
3244
+ chips: [found.candidate.route, found.candidate.status, output?.kind || "no output", quality.label, rejected ? "rejected" : "active"],
3245
+ rows: [
3246
+ ["Type", "storyboard still candidate"],
3247
+ ["Scene", found.sceneIndex],
3248
+ ["Locked", locked ? "yes" : "no"],
3249
+ ["Rejected", rejected ? "yes" : "no"],
3250
+ ["Review health", quality.detail],
3251
+ ["Output", output?.path],
3252
+ ["Prompt", found.candidate.prompt],
3253
+ ["Route", found.candidate.route],
3254
+ ["Round", found.candidate.generationRound],
3255
+ ["Adapter", found.candidate.source?.adapter],
3256
+ ["External job", found.candidate.source?.externalJobId],
3257
+ ["Submitted", found.candidate.submittedAt],
3258
+ ["Completed", found.candidate.completedAt],
3259
+ ],
3260
+ };
3261
+ }
3262
+ if (key === "character") {
3263
+ const item = inventory.characters.find((character) => character.id === id);
3264
+ if (!item) return null;
3265
+ return {
3266
+ title: item.name,
3267
+ desc: item.description || "Saved character profile.",
3268
+ chips: [item.goBananasId ? `GB ${item.goBananasId}` : "local", "identity"],
3269
+ rows: [
3270
+ ["Type", "character"],
3271
+ ["ID", item.id],
3272
+ ["GoBananas ID", item.goBananasId],
3273
+ ["Refs", item.referenceAssets?.join("\n")],
3274
+ ["Roles", roleAssignmentsForSelection(id).join(", ")],
3275
+ ],
3276
+ };
3277
+ }
3278
+ if (key === "characterSource") {
3279
+ const item = inventory.presenterPacks.find((pack) => pack.id === id);
3280
+ if (!item) return null;
3281
+ return {
3282
+ title: item.name,
3283
+ desc: item.notes,
3284
+ chips: ["presenter pack", `${item.assets.length} assets`],
3285
+ rows: [
3286
+ ["Type", "presenter pack"],
3287
+ ["ID", item.id],
3288
+ ["Assets", item.assets.join("\n")],
3289
+ ["Roles", roleAssignmentsForSelection(id).join(", ")],
3290
+ ],
3291
+ };
3292
+ }
3293
+ if (key === "asset") {
3294
+ return {
3295
+ title: id.split("/").pop(),
3296
+ desc: id,
3297
+ chips: [ext(id), "asset"],
3298
+ rows: [
3299
+ ["Type", "media asset"],
3300
+ ["Path", id],
3301
+ ["Role use", roleAssignmentsForSelection(id).join(", ") || "not role-tagged yet"],
3302
+ ],
3303
+ };
3304
+ }
3305
+ if (key === "playbook") {
3306
+ const item = inventory.playbooks.find((playbook) => playbook.id === id);
3307
+ if (!item) return null;
3308
+ return {
3309
+ title: item.name,
3310
+ desc: item.desc,
3311
+ chips: [item.provider, "playbook"],
3312
+ rows: [["Type", "provider playbook"], ["ID", item.id], ["Provider", item.provider]],
3313
+ };
3314
+ }
3315
+ if (key === "reference") {
3316
+ const item = inventory.references.find(([referenceId]) => referenceId === id);
3317
+ if (!item) return null;
3318
+ return {
3319
+ title: item[0],
3320
+ desc: item[1],
3321
+ chips: ["prompt reference", "guidance"],
3322
+ rows: [
3323
+ ["Type", "prompt/reference guide"],
3324
+ ["ID", item[0]],
3325
+ ["Role use", roleAssignmentsForSelection(id).join(", ") || "workflow guidance"],
3326
+ ["Source", `references/video/${item[0]}.md or docs/reference`],
3327
+ ],
3328
+ };
3329
+ }
3330
+ if (key === "template") {
3331
+ const item = inventory.templates.find(([templateId]) => templateId === id);
3332
+ if (!item) return null;
3333
+ return {
3334
+ title: item[0],
3335
+ desc: item[1],
3336
+ chips: [`${item[2]} scenes`, "storyboard template"],
3337
+ rows: [["Type", "storyboard template"], ["ID", item[0]], ["Scenes", item[2]]],
3338
+ };
3339
+ }
3340
+ if (key === "schema") {
3341
+ return {
3342
+ title: id,
3343
+ desc: `schemas/video/artifacts/${id}.schema.json`,
3344
+ chips: ["JSON schema", "contract"],
3345
+ rows: [["Type", "artifact schema"], ["Name", id], ["Path", `schemas/video/artifacts/${id}.schema.json`]],
3346
+ };
3347
+ }
3348
+ return {
3349
+ title: id,
3350
+ desc: "Selected item.",
3351
+ chips: [key || "item"],
3352
+ rows: [["Type", key || "item"], ["ID", id]],
3353
+ };
3354
+ }
3355
+
3356
+ function detailsMarkup() {
3357
+ const detail = selectedDetail();
3358
+ if (!detail) {
3359
+ return `<div class="details-panel empty">
3360
+ <h4>Details</h4>
3361
+ <p>Select Details on a character, reference, asset, template, or generated still to inspect source, prompt, role use, and output provenance here.</p>
3362
+ </div>`;
3363
+ }
3364
+ return `<div class="details-panel">
3365
+ <h4>${detail.title}</h4>
3366
+ <p>${detail.desc}</p>
3367
+ <div class="chips">${detail.chips.map((chip) => `<span class="chip">${chip}</span>`).join("")}</div>
3368
+ ${detailRows(detail.rows)}
3369
+ <div class="card-actions">
3370
+ <button class="btn btn-secondary" data-action="clearDetails">Close details</button>
3371
+ </div>
3372
+ </div>`;
3373
+ }
3374
+
3375
+ function ledger() {
3376
+ return {
3377
+ project: state.project,
3378
+ activeGate: reviewComplete() ? "assembly" : state.stage,
3379
+ seedanceWorkflow,
3380
+ qualityScore: `${qualityScore()}/${qualityChecks().length}`,
3381
+ qualityChecks: qualityChecks().map(([label, done, detail]) => ({ label, done, detail })),
3382
+ recommendedNextAction: nextAction(),
3383
+ existingInventory: {
3384
+ savedProjectCharacters: inventory.characters.length,
3385
+ presenterPacks: inventory.presenterPacks.map((pack) => pack.id),
3386
+ mediaAssets: inventory.mediaAssets.length,
3387
+ playbooks: inventory.playbooks.map((playbook) => playbook.id),
3388
+ templates: inventory.templates.map(([id]) => id),
3389
+ queuedGoBananasCharacterRequests: (inventory.characterQueue?.requests || []).filter((request) => request.status === "queued").length,
3390
+ fulfilledGoBananasCharacterRequests: (inventory.characterQueue?.requests || []).filter((request) => request.status === "fulfilled").length,
3391
+ failedGoBananasCharacterRequests: (inventory.characterQueue?.requests || []).filter((request) => request.status === "failed").length,
3392
+ queuedGoBananasStillRequests: (inventory.generationQueue?.requests || []).filter((request) => request.status === "queued").length,
3393
+ fulfilledGoBananasStillRequests: (inventory.generationQueue?.requests || []).filter((request) => request.status === "fulfilled").length
3394
+ },
3395
+ selections: state.selected,
3396
+ notes: state.notes,
3397
+ inspected: state.inspected,
3398
+ savedArtifact: state.savedArtifact,
3399
+ artifactsToWrite: [
3400
+ "characters/characters.json",
3401
+ "gobananas-character-iteration-requests.json",
3402
+ "reference-board.json",
3403
+ "reference-sheets.json",
3404
+ "scene-candidates.json",
3405
+ "scene-selection.json",
3406
+ "storyboard-still-generation-requests.json",
3407
+ "director-seedance-plan.json",
3408
+ "storyboard-stills-plan.json",
3409
+ "gobananas-character-brief.json",
3410
+ "post-plan.json",
3411
+ "review-report.json"
3412
+ ]
3413
+ };
3414
+ }
3415
+
3416
+ function handoffMarkup() {
3417
+ const savedPath = state.savedArtifact?.path || `projects/${state.project}/artifacts/review-ui-ledger.json`;
3418
+ const rootArg = inventory.root || "<repo-root>";
3419
+ const launchCommand = `vclaw video review-ui --project ${state.project} --root ${rootArg}`;
3420
+ const lifecycle = state.savedArtifact?.lifecycle || null;
3421
+ const report = currentReviewReport();
3422
+ const metrics = report?.metrics || {};
3423
+ const reportSummary = report
3424
+ ? `<p style="margin-top:10px">Review report</p><code>${report.verdict || "unknown"} · locked ${metrics.lockedSceneCount ?? "?"}/${metrics.expectedSceneCount ?? "?"} · character mismatches ${metrics.characterMismatchCount ?? "?"} · 4k assets ${metrics.artifactBackedUpscaleCount ?? 0}/${metrics.upscaleMarkerCount ?? 0} · publish ${metrics.publishReady ? "ready" : "not ready"}</code>`
3425
+ : "";
3426
+ const derived = state.savedArtifact?.derivedArtifacts || [
3427
+ { name: "reference-board.json", path: `projects/${state.project}/artifacts/reference-board.json` },
3428
+ { name: "director-seedance-plan.json", path: `projects/${state.project}/artifacts/director-seedance-plan.json` },
3429
+ { name: "storyboard-stills-plan.json", path: `projects/${state.project}/artifacts/storyboard-stills-plan.json` },
3430
+ { name: "scene-selection.json", path: `projects/${state.project}/artifacts/scene-selection.json` },
3431
+ { name: "gobananas-character-brief.json", path: `projects/${state.project}/artifacts/gobananas-character-brief.json` },
3432
+ { name: "post-plan.json", path: `projects/${state.project}/artifacts/post-plan.json` },
3433
+ { name: "review-report.json", path: `projects/${state.project}/artifacts/review-report.json` }
3434
+ ];
3435
+ return `<div class="handoff">
3436
+ <strong>Agent handoff</strong>
3437
+ <p>${handoffSummaryText()}</p>
3438
+ <div class="card-actions">
3439
+ <button class="btn btn-primary" data-stage-jump="storyboard">Review Go Bananas images</button>
3440
+ <button class="btn btn-secondary" data-stage-jump="motion">Check motion plan</button>
3441
+ </div>
3442
+ ${visualStoryboardStrip(true)}
3443
+ <p style="margin-top:10px">Plain-English summary</p>
3444
+ <code>${storySummaryText()}</code>
3445
+ ${finalVideoPath() ? `<p style="margin-top:10px">Final video</p>${renderFinalVideoBlock(true)}<code>${finalVideoPath()}</code>` : ""}
3446
+ ${lifecycle ? `<p style="margin-top:10px">Lifecycle</p><code>${lifecycle.status} · current stage ${lifecycle.currentStage} · last completed ${lifecycle.lastCompletedStage}</code>` : ""}
3447
+ ${reportSummary}
3448
+ <details>
3449
+ <summary>Operator command and artifact paths</summary>
3450
+ <code>${launchCommand}</code>
3451
+ <p style="margin-top:10px">Ledger artifact</p>
3452
+ <code>${savedPath}</code>
3453
+ <p style="margin-top:10px">Derived artifacts</p>
3454
+ <code>${derived.map((artifact) => artifact.path || artifact.name).join("\n")}</code>
3455
+ </details>
3456
+ <p style="margin-top:10px">Next decision</p>
3457
+ <code>${nextAction()}</code>
3458
+ </div>`;
3459
+ }
3460
+
3461
+ function nextAction() {
3462
+ const selected = state.selected;
3463
+ if (savedReviewPassed()) return "Ready for publish handoff.";
3464
+ if (!selected.character && !selected.characterSource && !selected.characterPlan) return "Choose a saved character, presenter pack, or generate GoBananas character iterations.";
3465
+ if (!selected.reference) return "Attach role-tagged references for identity, motion, and lookdev.";
3466
+ if (!selected["referenceRole-identity"]) return "Assign the identity reference role before image prompting.";
3467
+ if (!selected.template) return "Choose a storyboard template.";
3468
+ if (!selected["draftStill-0"]) return "Draft the first storyboard still image.";
3469
+ const nextUnlockedScene = state.scenes.findIndex((_, index) => !isCandidateBackedLock(index));
3470
+ if (nextUnlockedScene >= 0) return `Lock a generated scene-${String(nextUnlockedScene).padStart(2, "0")} candidate before generating motion.`;
3471
+ const nextNotUpscaledScene = state.scenes.findIndex((_, index) => !isSceneUpscaled(index));
3472
+ if (nextNotUpscaledScene >= 0) return `Attach the artifact-backed 4K scene-${String(nextNotUpscaledScene).padStart(2, "0")} still before Seedance.`;
3473
+ const nextUnconfirmedContinuityScene = state.scenes.findIndex((_, index) => !isContinuityConfirmed(index));
3474
+ if (nextUnconfirmedContinuityScene >= 0) return `Confirm continuity frame chaining for scene-${String(nextUnconfirmedContinuityScene).padStart(2, "0")}.`;
3475
+ if (!selected.bridgePosePlan) return "Require bridge poses for hard actions before Seedance motion.";
3476
+ if (!selected.motionCandidate) return "Choose the Seedance motion plan.";
3477
+ if (!selected.assemblyPlan) return "Approve a final assembly plan.";
3478
+ const nextMissingAssembly = missingAssemblyChecks()[0];
3479
+ if (nextMissingAssembly) return `Approve final assembly check: ${nextMissingAssembly[1]}.`;
3480
+ return "Ready to finish review and write artifacts.";
3481
+ }
3482
+
3483
+ function savedReviewPassed() {
3484
+ const report = currentReviewReport();
3485
+ return Boolean(report?.verdict === "pass" && report?.metrics?.publishReady === true);
3486
+ }
3487
+
3488
+ function currentReviewReport() {
3489
+ return state.savedArtifact?.reviewReport || inventory.reviewReport || null;
3490
+ }
3491
+
3492
+ function handoffSummaryText() {
3493
+ if (savedReviewPassed()) {
3494
+ return "Review is complete. Canonical checkpoints are hydrated and the project is ready for publish.";
3495
+ }
3496
+ const report = currentReviewReport();
3497
+ if (report?.verdict === "retry") {
3498
+ return "Review evidence is saved, but the handoff still needs fixes before publish.";
3499
+ }
3500
+ if (state.savedArtifact?.path) {
3501
+ return "Saved review ledger and derived director artifacts are ready for the next agent step.";
3502
+ }
3503
+ return "Save progress or finish review to write the ledger and director artifacts.";
3504
+ }
3505
+
3506
+ function recommendedButtonLabel() {
3507
+ if (state.stage === "inventory") return "Go to characters";
3508
+ if (state.stage === "characters") return "Request iterations";
3509
+ if (state.stage === "references") return "Attach Seedance guide";
3510
+ if (state.stage === "storyboard") return "Use product template";
3511
+ if (state.stage === "motion") return "Choose control pass";
3512
+ return "Approve balanced cut";
3513
+ }
3514
+
3515
+ function recommendedAction() {
3516
+ if (state.stage === "inventory") return setStage("characters");
3517
+ if (state.stage === "characters") return choose("characterPlan", "generate-gobananas-iterations");
3518
+ if (state.stage === "references") return choose("reference", "seedance-ugc-formulas");
3519
+ if (state.stage === "storyboard") return choose("template", "product-commercial-4");
3520
+ if (state.stage === "motion") return choose("motionCandidate", "control-pass");
3521
+ return choose("assemblyPlan", "balanced");
3522
+ }
3523
+
3524
+ function nextStoryboardSceneIndex() {
3525
+ const nextWithoutPrompt = state.scenes.findIndex((_, index) => !state.selected[`draftStill-${index}`]);
3526
+ if (nextWithoutPrompt >= 0) return nextWithoutPrompt;
3527
+ const nextWithoutApprovedImage = state.scenes.findIndex((_, index) => !approvedStoryboardStillId(index) && !isCandidateBackedLock(index));
3528
+ if (nextWithoutApprovedImage >= 0) return nextWithoutApprovedImage;
3529
+ const nextWithoutUpscale = state.scenes.findIndex((_, index) => !isSceneUpscaled(index));
3530
+ return nextWithoutUpscale >= 0 ? nextWithoutUpscale : 0;
3531
+ }
3532
+
3533
+ function jumpToNextStoryboardScene() {
3534
+ state.stage = "storyboard";
3535
+ const sceneIndex = nextStoryboardSceneIndex();
3536
+ state.activeSceneIndex = sceneIndex;
3537
+ state.statusMessage = `Scene ${String(sceneIndex).padStart(2, "0")} is the next storyboard image decision.`;
3538
+ render();
3539
+ const node = document.getElementById(`scene-review-${sceneIndex}`);
3540
+ if (node) node.scrollIntoView({ behavior: "smooth", block: "start" });
3541
+ }
3542
+
3543
+ function applyDirectorDefaults() {
3544
+ state.selected = {
3545
+ ...state.selected,
3546
+ characterPlan: state.selected.character || state.selected.characterSource ? state.selected.characterPlan : "generate-gobananas-iterations",
3547
+ reference: state.selected.reference || "seedance-ugc-formulas",
3548
+ "referenceRole-identity": state.selected["referenceRole-identity"] || "identity-assigned",
3549
+ "referenceRole-pose": state.selected["referenceRole-pose"] || "pose-assigned",
3550
+ "referenceRole-lookdev": state.selected["referenceRole-lookdev"] || "lookdev-assigned",
3551
+ "referenceRole-background": state.selected["referenceRole-background"] || "background-assigned",
3552
+ template: state.selected.template || "product-commercial-4",
3553
+ "draftStill-0": state.selected["draftStill-0"],
3554
+ "editStill-0": state.selected["editStill-0"],
3555
+ "lockedStill-0": state.selected["lockedStill-0"],
3556
+ "upscaledStill-0": state.selected["upscaledStill-0"],
3557
+ continuityPlan: state.selected.continuityPlan || "start-end-frame-chain",
3558
+ bridgePosePlan: state.selected.bridgePosePlan || "bridge-hard-actions",
3559
+ motionCandidate: state.selected.motionCandidate || "control-pass",
3560
+ variantStrategy: state.selected.variantStrategy || "control-plus-short-variant",
3561
+ assemblyPlan: state.selected.assemblyPlan || "balanced"
3562
+ };
3563
+ state.notes.reference = state.notes.reference || "Use references by role only: identity for character, pose for body language, lookdev for material/lighting, UI screenshots for structure, and previous frames for continuity.";
3564
+ state.notes.storyboard = state.notes.storyboard || "Create and refine still frames first. Preserve identity, camera, lighting, composition, and background during edits. Lock stills before motion.";
3565
+ state.notes.motion = state.notes.motion || "Seedance: use image 1 as start and image 2 as target. Static camera unless the scene demands movement. Generate a detailed control pass, then a short variant. Add bridge poses for difficult motion.";
3566
+ state.notes.voiceover = state.notes.voiceover || "Map each scene to voiceover timing before final assembly; use post retiming for slow generated clips.";
3567
+ state.stage = "assembly";
3568
+ state.blockedMessage = "";
3569
+ state.statusMessage = "Director defaults applied from the Seedance workflow. Save progress to hand this to the agent.";
3570
+ render();
3571
+ }
3572
+
3573
+ function missingForStage(id = state.stage) {
3574
+ if (reviewComplete()) return [];
3575
+ const currentIndex = stages.findIndex((stage) => stage.id === id);
3576
+ const prerequisiteMissing = stages
3577
+ .slice(0, Math.max(0, currentIndex))
3578
+ .filter((stage) => stage.id !== "inventory" && !gateDone(stage.id))
3579
+ .map((stage) => `Finish ${stage.label} first: ${ownMissingForStage(stage.id)[0] || gateRequirement(stage.id)}`);
3580
+ return [...prerequisiteMissing, ...ownMissingForStage(id)];
3581
+ }
3582
+
3583
+ function ownMissingForStage(id = state.stage) {
3584
+ const selected = state.selected;
3585
+ if (id === "inventory") {
3586
+ const missing = [];
3587
+ if (!state.project || !visibleProjects().some((project) => project.id === state.project)) missing.push("Select a valid project.");
3588
+ if (!inventory.templates.length) missing.push("Load storyboard templates from the project inventory.");
3589
+ if (!inventory.references.length) missing.push("Load prompt/reference guides from the project inventory.");
3590
+ if (!inventory.schemas.length) missing.push("Load artifact schemas from the project inventory.");
3591
+ if (window.location.protocol !== "file:" && !hasLiveInventory()) missing.push("Wait for the localhost API inventory to load.");
3592
+ return missing;
3593
+ }
3594
+ if (id === "characters") {
3595
+ return selected.character || selected.characterSource || selected.characterPlan
3596
+ ? []
3597
+ : ["Choose a saved character, existing presenter pack, or request GoBananas character iterations."];
3598
+ }
3599
+ if (id === "references") {
3600
+ const hasReference = selected.reference || state.notes.reference;
3601
+ const hasIdentityRole = selected["referenceRole-identity"];
3602
+ return hasReference && hasIdentityRole
3603
+ ? []
3604
+ : [
3605
+ ...(!hasReference ? ["Attach at least one prompt/reference guide or save a reference note."] : []),
3606
+ ...(!hasIdentityRole ? ["Assign at least the identity reference role."] : []),
3607
+ ];
3608
+ }
3609
+ if (id === "storyboard") {
3610
+ const missing = [];
3611
+ if (!selected.template) missing.push("Choose a storyboard template.");
3612
+ const undraftedScenes = state.scenes
3613
+ .map((_, index) => index)
3614
+ .filter((sceneIndex) => !selected[`draftStill-${sceneIndex}`]);
3615
+ if (undraftedScenes.length) {
3616
+ missing.push(`Choose a draft candidate for scene ${undraftedScenes.map((sceneIndex) => String(sceneIndex).padStart(2, "0")).join(", ")}.`);
3617
+ }
3618
+ const unlockedScenes = state.scenes
3619
+ .map((_, index) => index)
3620
+ .filter((sceneIndex) => !isCandidateBackedLock(sceneIndex));
3621
+ if (unlockedScenes.length) {
3622
+ missing.push(`Lock real generated candidates for scene ${unlockedScenes.map((sceneIndex) => String(sceneIndex).padStart(2, "0")).join(", ")}.`);
3623
+ }
3624
+ const notUpscaledScenes = state.scenes
3625
+ .map((_, index) => index)
3626
+ .filter((sceneIndex) => !isSceneUpscaled(sceneIndex));
3627
+ if (notUpscaledScenes.length) {
3628
+ missing.push(`Attach artifact-backed 4K/upscaled stills for scene ${notUpscaledScenes.map((sceneIndex) => String(sceneIndex).padStart(2, "0")).join(", ")}.`);
3629
+ }
3630
+ return missing;
3631
+ }
3632
+ if (id === "motion") {
3633
+ const missing = [];
3634
+ const unconfirmedContinuityScenes = state.scenes
3635
+ .map((_, index) => index)
3636
+ .filter((sceneIndex) => !isContinuityConfirmed(sceneIndex));
3637
+ if (unconfirmedContinuityScenes.length) {
3638
+ missing.push(`Confirm continuity chaining for scene ${unconfirmedContinuityScenes.map((sceneIndex) => String(sceneIndex).padStart(2, "0")).join(", ")}.`);
3639
+ }
3640
+ if (!selected.bridgePosePlan) missing.push("Require bridge poses for hard actions.");
3641
+ if (!selected.motionCandidate) missing.push("Choose the Seedance motion plan to run after storyboard stills are approved.");
3642
+ return missing;
3643
+ }
3644
+ const missing = [];
3645
+ if (!selected.assemblyPlan) missing.push("Approve a final assembly plan.");
3646
+ const openChecks = missingAssemblyChecks();
3647
+ if (openChecks.length) {
3648
+ missing.push(`Approve publish checks: ${openChecks.map(([, title]) => title).join(", ")}.`);
3649
+ }
3650
+ return missing;
3651
+ }
3652
+
3653
+ function missingMarkup() {
3654
+ const missing = missingForStage();
3655
+ if (!missing.length && !state.blockedMessage) return "";
3656
+ const title = state.blockedMessage || "Before continuing, finish this gate:";
3657
+ return `<ul class="missing-list"><li><strong>${title}</strong></li>${missing.map((item) => `<li>${item}</li>`).join("")}</ul>`;
3658
+ }
3659
+
3660
+ function gateRequirement(id = state.stage) {
3661
+ if (id === "inventory") return "Review the pantry, then start with characters.";
3662
+ if (id === "characters") return "Choose a saved character, presenter pack, or request GoBananas iterations.";
3663
+ if (id === "references") return "Attach references and assign identity role.";
3664
+ if (id === "storyboard") return "Draft, edit, lock, and upscale scene-00 still.";
3665
+ if (id === "motion") return "Prepare the Seedance motion plan.";
3666
+ return "Approve one assembly plan.";
3667
+ }
3668
+
3669
+ function canContinue() {
3670
+ return missingForStage().length === 0;
3671
+ }
3672
+
3673
+ function reviewComplete() {
3674
+ return savedReviewPassed();
3675
+ }
3676
+
3677
+ function firstIncompleteStage() {
3678
+ if (reviewComplete()) return stages[stages.length - 1];
3679
+ return stages.find((stage) => !gateSelfDone(stage.id)) || stages[stages.length - 1];
3680
+ }
3681
+
3682
+ function snapStageToFirstIncomplete() {
3683
+ const firstIncomplete = firstIncompleteStage();
3684
+ if (!firstIncomplete) return;
3685
+ const currentIndex = stageIndex();
3686
+ const firstIncompleteIndex = stages.findIndex((stage) => stage.id === firstIncomplete.id);
3687
+ if (currentIndex < 0 || currentIndex > firstIncompleteIndex) {
3688
+ state.stage = firstIncomplete.id;
3689
+ if (state.stage === "storyboard") state.activeSceneIndex = clampSceneIndex(state.activeSceneIndex);
3690
+ if (!reviewComplete()) {
3691
+ state.statusMessage = `Showing first blocking gate: ${firstIncomplete.label}.`;
3692
+ }
3693
+ }
3694
+ }
3695
+
3696
+ function stageNowTitle(stage) {
3697
+ if (stage.id === "characters" && inventory.characters.length) {
3698
+ return "Saved project character is available.";
3699
+ }
3700
+ return stage.now;
3701
+ }
3702
+
3703
+ function stageHelpText(stage) {
3704
+ if (stage.id === "characters" && inventory.characters.length) {
3705
+ return "Select Proofy or another saved character, then continue with role-tagged references and storyboard stills.";
3706
+ }
3707
+ return stage.help;
3708
+ }
3709
+
3710
+ function render() {
3711
+ const projects = visibleProjects();
3712
+ if ((!state.project || !projects.some((project) => project.id === state.project)) && projects[0]) {
3713
+ state.project = projects[0].id;
3714
+ }
3715
+ snapStageToFirstIncomplete();
3716
+ const stage = currentStage();
3717
+ const missing = missingForStage();
3718
+ const ready = canContinue();
3719
+ document.getElementById("stepMeta").innerHTML = `<span class="step-pill">Step ${stageIndex() + 1} of ${stages.length}</span><span>${gateRequirement()}</span>`;
3720
+ document.getElementById("kicker").textContent = stage.kicker;
3721
+ document.getElementById("title").textContent = stage.title;
3722
+ document.getElementById("description").textContent = stage.desc;
3723
+ document.getElementById("nowTitle").textContent = stageNowTitle(stage);
3724
+ document.getElementById("nowCopy").innerHTML = `${stageHelpText(stage)}${missingMarkup()}`;
3725
+ document.getElementById("nowButton").textContent = recommendedButtonLabel();
3726
+ const nowBox = document.querySelector(".now-box");
3727
+ nowBox.classList.toggle("blocked", Boolean(state.blockedMessage));
3728
+ nowBox.classList.toggle("ready", ready && state.stage !== "inventory");
3729
+ const feedback = document.getElementById("actionFeedback");
3730
+ const feedbackText = state.blockedMessage || state.statusMessage || (ready ? "This step is ready. Continue when you are finished reviewing it." : missing[0] || "Use the recommended action above.");
3731
+ feedback.textContent = feedbackText;
3732
+ feedback.classList.toggle("blocked", Boolean(state.blockedMessage));
3733
+ feedback.classList.toggle("ready", !state.blockedMessage && (Boolean(state.statusMessage) || ready));
3734
+ document.getElementById("qualityPanel").innerHTML = renderQualityPanel();
3735
+ document.getElementById("content").innerHTML = content();
3736
+ document.getElementById("stageRail").innerHTML = stages.map((item, index) => {
3737
+ const done = gateDone(item.id);
3738
+ return `<button class="stage-button ${state.stage === item.id ? "active" : ""} ${done ? "done" : ""}" data-stage-jump="${item.id}">
3739
+ <span class="stage-number">${done ? "✓" : index + 1}</span>
3740
+ <span class="stage-title">${item.label}</span>
3741
+ <span class="stage-sub">${item.sub}</span>
3742
+ </button>`;
3743
+ }).join("");
3744
+ document.getElementById("projectSelect").innerHTML = projects.map((project) => `<option value="${project.id}" ${state.project === project.id ? "selected" : ""}>${project.name} — ${project.status}</option>`).join("");
3745
+ const choices = Object.keys(state.selected).length;
3746
+ document.getElementById("choiceCount").textContent = choices;
3747
+ const completeGates = stages.filter((item) => gateDone(item.id)).length;
3748
+ document.getElementById("gateCount").textContent = Math.max(0, stages.length - completeGates);
3749
+ document.getElementById("gateChecks").innerHTML = stages.map((item, index) => {
3750
+ const done = gateDone(item.id);
3751
+ return `<div class="gate-check ${done ? "done" : ""}"><span class="gate-dot">${done ? "✓" : index + 1}</span><span>${item.label}</span></div>`;
3752
+ }).join("");
3753
+ document.getElementById("handoff").innerHTML = handoffMarkup();
3754
+ document.getElementById("detailsPanel").innerHTML = detailsMarkup();
3755
+ document.getElementById("ledger").textContent = JSON.stringify(ledger(), null, 2);
3756
+ const continueButton = document.getElementById("continueButton");
3757
+ continueButton.textContent = reviewComplete() ? "Review complete" : ready ? (stageIndex() === stages.length - 1 ? "Finish review" : "Continue") : "Show what is missing";
3758
+ continueButton.classList.toggle("needs-work", !ready);
3759
+ document.getElementById("footerStatus").textContent = state.statusMessage || (reviewComplete() ? "Review artifacts saved. Ready for publish handoff." : ready ? "This gate is ready." : missing[0] || "Use the recommended action above.");
3760
+ bind();
3761
+ persistLocalProgress();
3762
+ }
3763
+
3764
+ function gateDone(id) {
3765
+ if (reviewComplete()) return true;
3766
+ const currentIndex = stages.findIndex((stage) => stage.id === id);
3767
+ if (currentIndex > 0) {
3768
+ const previousIncomplete = stages
3769
+ .slice(0, currentIndex)
3770
+ .some((stage) => !gateSelfDone(stage.id));
3771
+ if (previousIncomplete) return false;
3772
+ }
3773
+ return gateSelfDone(id);
3774
+ }
3775
+
3776
+ function gateSelfDone(id) {
3777
+ if (id === "inventory") return inventoryReady();
3778
+ if (id === "characters") return Boolean(state.selected.character || state.selected.characterSource || state.selected.characterPlan);
3779
+ if (id === "references") return Boolean((state.selected.reference || state.notes.reference) && state.selected["referenceRole-identity"]);
3780
+ if (id === "storyboard") return Boolean(
3781
+ state.selected.template
3782
+ && state.scenes.every((_, index) => state.selected[`draftStill-${index}`])
3783
+ && state.scenes.every((_, index) => isCandidateBackedLock(index))
3784
+ && state.scenes.every((_, index) => isSceneUpscaled(index))
3785
+ );
3786
+ if (id === "motion") return Boolean(
3787
+ state.selected.motionCandidate
3788
+ && state.selected.bridgePosePlan
3789
+ && state.scenes.every((_, index) => isContinuityConfirmed(index))
3790
+ );
3791
+ return Boolean(state.selected.assemblyPlan && missingAssemblyChecks().length === 0);
3792
+ }
3793
+
3794
+ function bind() {
3795
+ document.querySelectorAll("[data-stage-jump]").forEach((el) => {
3796
+ el.addEventListener("click", () => setStage(el.dataset.stageJump));
3797
+ });
3798
+ document.querySelectorAll("[data-select-key]").forEach((el) => {
3799
+ el.addEventListener("click", () => choose(el.dataset.selectKey, el.dataset.selectValue));
3800
+ });
3801
+ document.querySelectorAll("[data-save-note]").forEach((el) => {
3802
+ el.addEventListener("click", () => {
3803
+ const key = el.dataset.saveNote;
3804
+ const input = document.getElementById(`${key}Note`);
3805
+ if (input) state.notes[key] = input.value;
3806
+ state.statusMessage = "Note saved in this browser. Use Save progress to write the project ledger.";
3807
+ render();
3808
+ });
3809
+ });
3810
+ document.querySelectorAll("[data-inspect]").forEach((el) => {
3811
+ el.addEventListener("click", () => {
3812
+ state.inspected = {
3813
+ key: el.dataset.inspectKey || "item",
3814
+ id: el.dataset.inspect,
3815
+ };
3816
+ state.statusMessage = "Details loaded in the project state panel.";
3817
+ render();
3818
+ });
3819
+ });
3820
+ document.querySelectorAll("[data-action]").forEach((el) => {
3821
+ el.addEventListener("click", () => handleAction(el.dataset.action, el.dataset));
3822
+ });
3823
+ const project = document.getElementById("projectSelect");
3824
+ if (project) project.addEventListener("change", () => {
3825
+ state.project = project.value;
3826
+ if (window.location.protocol === "file:") {
3827
+ render();
3828
+ return;
3829
+ }
3830
+ window.location.href = `/review-ui?project=${encodeURIComponent(project.value)}`;
3831
+ });
3832
+ const filter = document.getElementById("filter");
3833
+ if (filter) filter.addEventListener("input", () => { state.filter = filter.value; render(); });
3834
+ const tab = document.getElementById("tab");
3835
+ if (tab) tab.addEventListener("change", () => { state.tab = tab.value; render(); });
3836
+ }
3837
+
3838
+ async function saveLedgerToProject() {
3839
+ if (window.location.protocol === "file:") {
3840
+ return { skipped: true, reason: "file preview" };
3841
+ }
3842
+ const response = await fetch(`/api/review-decision?project=${encodeURIComponent(state.project)}`, {
3843
+ method: "POST",
3844
+ headers: { "Content-Type": "application/json" },
3845
+ body: JSON.stringify(ledger())
3846
+ });
3847
+ if (!response.ok) throw new Error(await response.text());
3848
+ return response.json();
3849
+ }
3850
+
3851
+ async function recordStillCandidateFromForm(sceneIndex) {
3852
+ if (window.location.protocol === "file:") {
3853
+ throw new Error("Open the localhost review UI to record candidates.");
3854
+ }
3855
+ if (!isSceneWorkflowEnabled(sceneIndex)) {
3856
+ throw new Error(sceneWorkflowMessage(sceneIndex));
3857
+ }
3858
+ const imageUrl = document.getElementById(`stillUrl-${sceneIndex}`)?.value.trim();
3859
+ const imageId = document.getElementById(`stillImageId-${sceneIndex}`)?.value.trim();
3860
+ const prompt = document.getElementById(`stillPrompt-${sceneIndex}`)?.value.trim();
3861
+ if (!imageUrl) throw new Error("Paste a generated image URL first.");
3862
+ const response = await fetch(`/api/storyboard-still-candidate?project=${encodeURIComponent(state.project)}`, {
3863
+ method: "POST",
3864
+ headers: { "Content-Type": "application/json" },
3865
+ body: JSON.stringify({
3866
+ sceneIndex,
3867
+ imageUrl,
3868
+ ...(imageId ? { imageId } : {}),
3869
+ ...(prompt ? { prompt } : {}),
3870
+ notes: "Recorded from review UI storyboard paste form."
3871
+ })
3872
+ });
3873
+ if (!response.ok) throw new Error(await response.text());
3874
+ const result = await response.json();
3875
+ const scene = candidateScene(sceneIndex);
3876
+ if (!(scene.candidates || []).some((candidate) => candidate.id === result.candidate.id)) {
3877
+ scene.candidates = [...(scene.candidates || []), result.candidate];
3878
+ }
3879
+ if (!inventory.sceneCandidates.scenes.some((entry) => entry.sceneIndex === sceneIndex)) {
3880
+ inventory.sceneCandidates.scenes = [...(inventory.sceneCandidates.scenes || []), scene];
3881
+ }
3882
+ state.selected[`draftStill-${sceneIndex}`] = result.candidate.id;
3883
+ state.inspected = { key: "candidate", id: result.candidate.id };
3884
+ state.savedArtifact = await saveLedgerToProject();
3885
+ state.statusMessage = `${result.reused ? "Reused existing" : "Recorded"} ${result.candidate.id}. Review the card, request edits if needed, then lock it.`;
3886
+ return result;
3887
+ }
3888
+
3889
+ async function recordUpscaledStillFromForm(sceneIndex) {
3890
+ if (window.location.protocol === "file:") {
3891
+ throw new Error("Open the localhost review UI to attach upscaled stills.");
3892
+ }
3893
+ const sourceCandidateId = state.selected[`lockedStill-${sceneIndex}`];
3894
+ if (!sourceCandidateId || !isCandidateBackedLock(sceneIndex)) {
3895
+ throw new Error("Lock a real storyboard candidate before attaching the 4k asset.");
3896
+ }
3897
+ const imageUrl = document.getElementById(`upscaleUrl-${sceneIndex}`)?.value.trim();
3898
+ const imageId = document.getElementById(`upscaleImageId-${sceneIndex}`)?.value.trim();
3899
+ if (!imageUrl) throw new Error("Paste the 4k/upscaled image URL first.");
3900
+ const response = await fetch(`/api/upscaled-still-candidate?project=${encodeURIComponent(state.project)}`, {
3901
+ method: "POST",
3902
+ headers: { "Content-Type": "application/json" },
3903
+ body: JSON.stringify({
3904
+ sceneIndex,
3905
+ sourceCandidateId,
3906
+ imageUrl,
3907
+ ...(imageId ? { imageId } : {}),
3908
+ prompt: `Artifact-backed 4k/upscaled still for ${sourceCandidateId}.`
3909
+ })
3910
+ });
3911
+ if (!response.ok) throw new Error(await response.text());
3912
+ const result = await response.json();
3913
+ const scene = candidateScene(sceneIndex);
3914
+ if (!(scene.candidates || []).some((candidate) => candidate.id === result.candidate.id)) {
3915
+ scene.candidates = [...(scene.candidates || []), result.candidate];
3916
+ }
3917
+ if (!inventory.sceneCandidates.scenes.some((entry) => entry.sceneIndex === sceneIndex)) {
3918
+ inventory.sceneCandidates.scenes = [...(inventory.sceneCandidates.scenes || []), scene];
3919
+ }
3920
+ state.selected[`upscaledStill-${sceneIndex}`] = result.candidate.id;
3921
+ state.inspected = { key: "candidate", id: result.candidate.id };
3922
+ state.savedArtifact = await saveLedgerToProject();
3923
+ state.statusMessage = `${result.reused ? "Reused existing" : "Attached"} ${result.candidate.id} as the artifact-backed 4k still.`;
3924
+ return result;
3925
+ }
3926
+
3927
+ async function queueStillGenerationRequest(sceneIndex) {
3928
+ if (window.location.protocol === "file:") {
3929
+ throw new Error("Open the localhost review UI to queue GoBananas requests.");
3930
+ }
3931
+ if (!isSceneWorkflowEnabled(sceneIndex)) {
3932
+ throw new Error(sceneWorkflowMessage(sceneIndex));
3933
+ }
3934
+ const prompt = document.getElementById(`stillPrompt-${sceneIndex}`)?.value.trim() || defaultStillPrompt(sceneIndex);
3935
+ const negativePrompt = document.getElementById(`stillNegative-${sceneIndex}`)?.value.trim() || seedanceWorkflow.negativeGuidance.join(", ");
3936
+ const response = await fetch(`/api/storyboard-still-request?project=${encodeURIComponent(state.project)}`, {
3937
+ method: "POST",
3938
+ headers: { "Content-Type": "application/json" },
3939
+ body: JSON.stringify({
3940
+ sceneIndex,
3941
+ prompt,
3942
+ negativePrompt,
3943
+ aspectRatio: "16:9",
3944
+ notes: "Queued from review UI. Agent should run Go Bananas MCP generate_image, then record the returned image URL as a storyboard still candidate."
3945
+ })
3946
+ });
3947
+ if (!response.ok) throw new Error(await response.text());
3948
+ const result = await response.json();
3949
+ inventory.generationQueue = result.queue;
3950
+ state.notes[`stillPrompt-${sceneIndex}`] = prompt;
3951
+ state.notes[`stillNegative-${sceneIndex}`] = negativePrompt;
3952
+ state.statusMessage = `${result.request.id} queued for GoBananas.`;
3953
+ return result;
3954
+ }
3955
+
3956
+ async function queueCharacterIterationRequest() {
3957
+ if (window.location.protocol === "file:") {
3958
+ throw new Error("Open the localhost review UI to queue character iterations.");
3959
+ }
3960
+ const characterName = document.getElementById("characterName")?.value.trim() || "Komo";
3961
+ const prompt = document.getElementById("characterPrompt")?.value.trim() || defaultCharacterPrompt();
3962
+ const negativePrompt = document.getElementById("characterNegative")?.value.trim() || "inconsistent identity, realistic human photo, readable text, logos, clutter, distorted anatomy, extra limbs";
3963
+ const count = Number(document.getElementById("characterCount")?.value || 4);
3964
+ const response = await fetch(`/api/character-iteration-request?project=${encodeURIComponent(state.project)}`, {
3965
+ method: "POST",
3966
+ headers: { "Content-Type": "application/json" },
3967
+ body: JSON.stringify({
3968
+ characterName,
3969
+ prompt,
3970
+ negativePrompt,
3971
+ count: Number.isFinite(count) && count > 0 ? Math.min(4, Math.round(count)) : 4,
3972
+ aspectRatio: "square",
3973
+ notes: "Queued from review UI. Agent should run Go Bananas MCP generate_image for character iterations, then save the winning character profile."
3974
+ })
3975
+ });
3976
+ if (!response.ok) throw new Error(await response.text());
3977
+ const result = await response.json();
3978
+ inventory.characterQueue = result.queue;
3979
+ state.selected.characterPlan = "generate-gobananas-iterations";
3980
+ state.notes.characterName = characterName;
3981
+ state.notes.characterPrompt = prompt;
3982
+ state.notes.characterNegative = negativePrompt;
3983
+ state.notes.characterCount = String(count);
3984
+ state.statusMessage = `${result.request.id} queued for GoBananas character iterations.`;
3985
+ return result;
3986
+ }
3987
+
3988
+ async function handleAction(action, dataset = {}) {
3989
+ if (action === "next") {
3990
+ const missing = missingForStage();
3991
+ if (missing.length) {
3992
+ state.blockedMessage = "Not ready to continue yet.";
3993
+ render();
3994
+ return;
3995
+ }
3996
+ if (stageIndex() === stages.length - 1) {
3997
+ try {
3998
+ state.selected.reviewCompleteAt = new Date().toISOString();
3999
+ state.savedArtifact = await saveLedgerToProject();
4000
+ state.blockedMessage = "";
4001
+ state.statusMessage = `Review saved to ${state.savedArtifact.path}`;
4002
+ } catch (error) {
4003
+ state.blockedMessage = `Could not save the review ledger: ${error.message}`;
4004
+ state.statusMessage = "";
4005
+ }
4006
+ render();
4007
+ return;
4008
+ }
4009
+ const next = Math.min(stages.length - 1, stageIndex() + 1);
4010
+ setStage(stages[next].id);
4011
+ } else if (action === "prev") {
4012
+ const prev = Math.max(0, stageIndex() - 1);
4013
+ setStage(stages[prev].id);
4014
+ } else if (action === "reset") {
4015
+ state.selected = {};
4016
+ state.notes = {};
4017
+ state.blockedMessage = "";
4018
+ state.statusMessage = "Reloading saved project ledger...";
4019
+ state.savedArtifact = null;
4020
+ state.inspected = null;
4021
+ localStorage.removeItem(storageKey());
4022
+ if (window.location.protocol !== "file:") {
4023
+ await loadInventoryFromApi();
4024
+ } else {
4025
+ state.stage = "inventory";
4026
+ }
4027
+ render();
4028
+ } else if (action === "clearFilter") {
4029
+ state.filter = "";
4030
+ render();
4031
+ } else if (action === "explainCandidateLock") {
4032
+ state.blockedMessage = "Lock a generated candidate card below. Row-level markers are process notes, not valid downstream selections.";
4033
+ state.statusMessage = "";
4034
+ render();
4035
+ } else if (action === "recordStillCandidate") {
4036
+ try {
4037
+ const sceneIndex = Number(dataset.sceneIndex || 0);
4038
+ await recordStillCandidateFromForm(sceneIndex);
4039
+ state.blockedMessage = "";
4040
+ } catch (error) {
4041
+ state.blockedMessage = `Could not record still candidate: ${error.message}`;
4042
+ state.statusMessage = "";
4043
+ }
4044
+ render();
4045
+ } else if (action === "recordUpscaledStill") {
4046
+ try {
4047
+ const sceneIndex = Number(dataset.sceneIndex || 0);
4048
+ await recordUpscaledStillFromForm(sceneIndex);
4049
+ state.blockedMessage = "";
4050
+ } catch (error) {
4051
+ state.blockedMessage = `Could not attach 4k asset: ${error.message}`;
4052
+ state.statusMessage = "";
4053
+ }
4054
+ render();
4055
+ } else if (action === "queueStillRequest") {
4056
+ try {
4057
+ const sceneIndex = Number(dataset.sceneIndex || 0);
4058
+ await queueStillGenerationRequest(sceneIndex);
4059
+ state.blockedMessage = "";
4060
+ } catch (error) {
4061
+ state.blockedMessage = `Could not queue GoBananas request: ${error.message}`;
4062
+ state.statusMessage = "";
4063
+ }
4064
+ render();
4065
+ } else if (action === "queueCharacterRequest") {
4066
+ try {
4067
+ await queueCharacterIterationRequest();
4068
+ state.blockedMessage = "";
4069
+ } catch (error) {
4070
+ state.blockedMessage = `Could not queue character iterations: ${error.message}`;
4071
+ state.statusMessage = "";
4072
+ }
4073
+ render();
4074
+ } else if (action === "rejectCandidate") {
4075
+ const candidateId = dataset.candidateId;
4076
+ if (candidateId) {
4077
+ const rejected = new Set(rejectedStillCandidateIds());
4078
+ rejected.add(candidateId);
4079
+ state.selected.rejectedStillCandidates = [...rejected].sort();
4080
+ for (const key of ["lockedStill", "draftStill", "editStill", "upscaledStill"]) {
4081
+ const selectionKey = `${key}-${dataset.sceneIndex || 0}`;
4082
+ const selectionValue = String(state.selected[selectionKey] || "");
4083
+ const shouldClear = key === "editStill" || key === "upscaledStill"
4084
+ ? selectionValue.startsWith(candidateId)
4085
+ : selectionValue === candidateId;
4086
+ if (shouldClear) {
4087
+ delete state.selected[selectionKey];
4088
+ }
4089
+ }
4090
+ state.inspected = { key: "candidate", id: candidateId };
4091
+ state.blockedMessage = "";
4092
+ state.statusMessage = `${candidateId} rejected. It will not count as a locked still or downstream selection.`;
4093
+ try {
4094
+ state.savedArtifact = await saveLedgerToProject();
4095
+ state.statusMessage = `${candidateId} rejected and saved.`;
4096
+ } catch (error) {
4097
+ state.blockedMessage = `Candidate rejected locally, but could not save: ${error.message}`;
4098
+ }
4099
+ }
4100
+ render();
4101
+ } else if (action === "restoreCandidate") {
4102
+ const candidateId = dataset.candidateId;
4103
+ if (candidateId) {
4104
+ state.selected.rejectedStillCandidates = rejectedStillCandidateIds().filter((id) => id !== candidateId);
4105
+ state.inspected = { key: "candidate", id: candidateId };
4106
+ state.blockedMessage = "";
4107
+ state.statusMessage = `${candidateId} restored for review.`;
4108
+ try {
4109
+ state.savedArtifact = await saveLedgerToProject();
4110
+ state.statusMessage = `${candidateId} restored and saved.`;
4111
+ } catch (error) {
4112
+ state.blockedMessage = `Candidate restored locally, but could not save: ${error.message}`;
4113
+ }
4114
+ }
4115
+ render();
4116
+ } else if (action === "recommend" || action === "recommendedAction") {
4117
+ recommendedAction();
4118
+ } else if (action === "recommendStoryboardScene") {
4119
+ jumpToNextStoryboardScene();
4120
+ } else if (action === "selectStoryboardScene") {
4121
+ const sceneIndex = clampSceneIndex(dataset.sceneIndex);
4122
+ state.activeSceneIndex = sceneIndex;
4123
+ state.stage = "storyboard";
4124
+ state.blockedMessage = "";
4125
+ state.statusMessage = `Scene ${String(sceneIndex).padStart(2, "0")} selected for image review.`;
4126
+ render();
4127
+ } else if (action === "applyDirectorDefaults") {
4128
+ applyDirectorDefaults();
4129
+ } else if (action === "autoRoleReferences") {
4130
+ const referenceSource = state.selected.reference || "docs/REFERENCE_VIDEO_SEEDANCE_MOTION_DESIGN_WORKFLOW.md";
4131
+ const characterSource = state.selected.character || state.selected.characterSource || state.selected.characterPlan || "selected-character-source";
4132
+ const assetSource = state.selected.asset || referenceSource;
4133
+ state.selected["referenceRole-identity"] = characterSource;
4134
+ state.selected["referenceRole-pose"] = assetSource;
4135
+ state.selected["referenceRole-lookdev"] = referenceSource;
4136
+ state.selected["referenceRole-background"] = assetSource;
4137
+ state.selected["referenceRole-ui-structure"] = referenceSource;
4138
+ state.selected["referenceRole-prop"] = assetSource;
4139
+ if (state.selected["lockedStill-0"]) state.selected["referenceRole-start-frame"] = state.selected["lockedStill-0"];
4140
+ if (state.selected["lockedStill-1"]) state.selected["referenceRole-end-frame"] = state.selected["lockedStill-1"];
4141
+ state.selected["referenceRole-texture"] = characterSource;
4142
+ state.statusMessage = "Reference roles assigned. Save progress to write the reference board.";
4143
+ render();
4144
+ } else if (action === "assignSelectedReferenceRoles") {
4145
+ const referenceSource = state.selected.reference || state.selected.asset || state.selected.character || state.selected.characterSource;
4146
+ if (!referenceSource) {
4147
+ state.blockedMessage = "Select a prompt reference, asset, or character first.";
4148
+ state.statusMessage = "";
4149
+ } else {
4150
+ ["identity", "pose", "lookdev", "background", "ui-structure", "prop", "start-frame", "end-frame", "texture"].forEach((role) => {
4151
+ state.selected[`referenceRole-${role}`] = state.selected[`referenceRole-${role}`] || referenceSource;
4152
+ });
4153
+ state.blockedMessage = "";
4154
+ state.statusMessage = "Open reference roles filled from the selected source.";
4155
+ }
4156
+ render();
4157
+ } else if (action === "saveProgress") {
4158
+ try {
4159
+ state.savedArtifact = await saveLedgerToProject();
4160
+ state.blockedMessage = "";
4161
+ state.statusMessage = `Progress saved to ${state.savedArtifact.path}`;
4162
+ } catch (error) {
4163
+ state.blockedMessage = `Could not save the review ledger: ${error.message}`;
4164
+ state.statusMessage = "";
4165
+ }
4166
+ render();
4167
+ } else if (action === "downloadLedger") {
4168
+ const blob = new Blob([JSON.stringify(ledger(), null, 2)], { type: "application/json" });
4169
+ const url = URL.createObjectURL(blob);
4170
+ const a = document.createElement("a");
4171
+ a.href = url;
4172
+ a.download = "review-station-ledger.json";
4173
+ a.click();
4174
+ URL.revokeObjectURL(url);
4175
+ } else if (action === "clearDetails") {
4176
+ state.inspected = null;
4177
+ state.statusMessage = "";
4178
+ render();
4179
+ }
4180
+ }
4181
+
4182
+ async function boot() {
4183
+ state.project = initialProjectFromLocation() || state.project;
4184
+ render();
4185
+ loadLocalProgress();
4186
+ await loadInventoryFromApi();
4187
+ hydrationComplete = true;
4188
+ render();
4189
+ }
4190
+
4191
+ boot();
4192
+ </script>
4193
+ </body>
4194
+ </html>