@autoaiai/aicut 0.1.7 → 0.1.13

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 (306) hide show
  1. package/MANUAL.md +94 -84
  2. package/package.json +1 -1
  3. package/product.json +2 -1
  4. package/skills/aicut/SKILL.md +4 -4
  5. package/skills/aicut/references/commands.md +15 -15
  6. package/skills/aicut/references/film-format.md +1 -1
  7. package/skills/aicut/references/recipes.md +4 -4
  8. package/skills/aicut/references/troubleshooting.md +2 -2
  9. package/skills/aicut-motion/SKILL.md +75 -9
  10. package/skills/aicut-motion/THIRD_PARTY_NOTICES.md +14 -0
  11. package/skills/aicut-motion/assets/atlas/lib/harness.js +116 -0
  12. package/skills/aicut-motion/assets/atlas/lib/sound.js +274 -0
  13. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/code.js +937 -0
  14. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/poster.jpg +0 -0
  15. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/style.json +13 -0
  16. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/code.js +909 -0
  17. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/poster.jpg +0 -0
  18. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/style.json +13 -0
  19. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/code.js +881 -0
  20. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/poster.jpg +0 -0
  21. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/style.json +13 -0
  22. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/code.js +897 -0
  23. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/poster.jpg +0 -0
  24. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/style.json +13 -0
  25. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/code.js +814 -0
  26. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/poster.jpg +0 -0
  27. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/style.json +13 -0
  28. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/code.js +990 -0
  29. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/poster.jpg +0 -0
  30. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/style.json +13 -0
  31. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/code.js +994 -0
  32. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/poster.jpg +0 -0
  33. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/style.json +13 -0
  34. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/code.js +792 -0
  35. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/poster.jpg +0 -0
  36. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/style.json +13 -0
  37. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/code.js +840 -0
  38. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/poster.jpg +0 -0
  39. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/style.json +13 -0
  40. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/code.js +1126 -0
  41. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/poster.jpg +0 -0
  42. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/style.json +13 -0
  43. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/code.js +854 -0
  44. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/poster.jpg +0 -0
  45. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/style.json +13 -0
  46. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/code.js +1124 -0
  47. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/poster.jpg +0 -0
  48. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/style.json +13 -0
  49. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/code.js +728 -0
  50. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/poster.jpg +0 -0
  51. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/style.json +13 -0
  52. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/code.js +664 -0
  53. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/poster.jpg +0 -0
  54. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/style.json +13 -0
  55. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/code.js +991 -0
  56. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/poster.jpg +0 -0
  57. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/style.json +13 -0
  58. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/code.js +801 -0
  59. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/poster.jpg +0 -0
  60. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/style.json +13 -0
  61. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/code.js +692 -0
  62. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/poster.jpg +0 -0
  63. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/style.json +13 -0
  64. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/code.js +756 -0
  65. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/poster.jpg +0 -0
  66. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/style.json +13 -0
  67. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/code.js +922 -0
  68. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/poster.jpg +0 -0
  69. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/style.json +13 -0
  70. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/code.js +852 -0
  71. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/poster.jpg +0 -0
  72. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/style.json +13 -0
  73. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/code.js +724 -0
  74. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/poster.jpg +0 -0
  75. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/style.json +13 -0
  76. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/code.js +793 -0
  77. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/poster.jpg +0 -0
  78. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/style.json +13 -0
  79. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/code.js +846 -0
  80. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/poster.jpg +0 -0
  81. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/style.json +13 -0
  82. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/code.js +1008 -0
  83. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/poster.jpg +0 -0
  84. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/style.json +13 -0
  85. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/code.js +680 -0
  86. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/poster.jpg +0 -0
  87. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/style.json +13 -0
  88. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/code.js +633 -0
  89. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/poster.jpg +0 -0
  90. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/style.json +13 -0
  91. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/code.js +757 -0
  92. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/poster.jpg +0 -0
  93. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/style.json +13 -0
  94. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/code.js +828 -0
  95. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/poster.jpg +0 -0
  96. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/style.json +13 -0
  97. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/code.js +697 -0
  98. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/poster.jpg +0 -0
  99. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/style.json +13 -0
  100. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/code.js +687 -0
  101. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/poster.jpg +0 -0
  102. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/style.json +13 -0
  103. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/code.js +727 -0
  104. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/poster.jpg +0 -0
  105. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/style.json +13 -0
  106. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/code.js +903 -0
  107. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/poster.jpg +0 -0
  108. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/style.json +13 -0
  109. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/code.js +662 -0
  110. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/poster.jpg +0 -0
  111. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/style.json +13 -0
  112. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/code.js +951 -0
  113. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/poster.jpg +0 -0
  114. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/style.json +13 -0
  115. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/code.js +759 -0
  116. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/poster.jpg +0 -0
  117. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/style.json +13 -0
  118. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/code.js +738 -0
  119. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/poster.jpg +0 -0
  120. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/style.json +13 -0
  121. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/code.js +793 -0
  122. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/poster.jpg +0 -0
  123. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/style.json +13 -0
  124. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/code.js +673 -0
  125. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/poster.jpg +0 -0
  126. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/style.json +13 -0
  127. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/code.js +907 -0
  128. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/poster.jpg +0 -0
  129. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/style.json +13 -0
  130. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/code.js +833 -0
  131. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/poster.jpg +0 -0
  132. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/style.json +13 -0
  133. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/code.js +845 -0
  134. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/poster.jpg +0 -0
  135. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/style.json +13 -0
  136. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/code.js +496 -0
  137. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/poster.jpg +0 -0
  138. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/style.json +13 -0
  139. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/code.js +635 -0
  140. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/poster.jpg +0 -0
  141. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/style.json +13 -0
  142. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/code.js +799 -0
  143. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/poster.jpg +0 -0
  144. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/style.json +13 -0
  145. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/code.js +547 -0
  146. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/poster.jpg +0 -0
  147. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/style.json +13 -0
  148. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/code.js +592 -0
  149. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/poster.jpg +0 -0
  150. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/style.json +13 -0
  151. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/code.js +768 -0
  152. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/poster.jpg +0 -0
  153. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/style.json +13 -0
  154. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/code.js +666 -0
  155. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/poster.jpg +0 -0
  156. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/style.json +13 -0
  157. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/code.js +559 -0
  158. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/poster.jpg +0 -0
  159. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/style.json +13 -0
  160. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/code.js +943 -0
  161. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/poster.jpg +0 -0
  162. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/style.json +13 -0
  163. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/code.js +606 -0
  164. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/poster.jpg +0 -0
  165. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/style.json +13 -0
  166. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/code.js +730 -0
  167. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/poster.jpg +0 -0
  168. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/style.json +13 -0
  169. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/code.js +782 -0
  170. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/poster.jpg +0 -0
  171. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/style.json +13 -0
  172. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/code.js +504 -0
  173. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/poster.jpg +0 -0
  174. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/style.json +13 -0
  175. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/code.js +517 -0
  176. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/poster.jpg +0 -0
  177. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/style.json +13 -0
  178. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/code.js +713 -0
  179. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/poster.jpg +0 -0
  180. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/style.json +13 -0
  181. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/code.js +717 -0
  182. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/poster.jpg +0 -0
  183. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/style.json +13 -0
  184. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/code.js +540 -0
  185. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/poster.jpg +0 -0
  186. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/style.json +13 -0
  187. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/code.js +524 -0
  188. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/poster.jpg +0 -0
  189. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/style.json +13 -0
  190. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/code.js +934 -0
  191. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/poster.jpg +0 -0
  192. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/style.json +13 -0
  193. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/code.js +690 -0
  194. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/poster.jpg +0 -0
  195. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/style.json +13 -0
  196. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/code.js +612 -0
  197. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/poster.jpg +0 -0
  198. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/style.json +13 -0
  199. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/code.js +666 -0
  200. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/poster.jpg +0 -0
  201. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/style.json +13 -0
  202. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/code.js +651 -0
  203. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/poster.jpg +0 -0
  204. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/style.json +13 -0
  205. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/code.js +656 -0
  206. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/poster.jpg +0 -0
  207. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/style.json +13 -0
  208. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/code.js +672 -0
  209. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/poster.jpg +0 -0
  210. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/style.json +13 -0
  211. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/code.js +548 -0
  212. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/poster.jpg +0 -0
  213. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/style.json +13 -0
  214. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/code.js +989 -0
  215. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/poster.jpg +0 -0
  216. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/style.json +13 -0
  217. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/code.js +440 -0
  218. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/poster.jpg +0 -0
  219. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/style.json +13 -0
  220. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/code.js +895 -0
  221. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/poster.jpg +0 -0
  222. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/style.json +13 -0
  223. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/code.js +563 -0
  224. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/poster.jpg +0 -0
  225. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/style.json +13 -0
  226. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/code.js +627 -0
  227. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/poster.jpg +0 -0
  228. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/style.json +13 -0
  229. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/code.js +1096 -0
  230. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/poster.jpg +0 -0
  231. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/style.json +13 -0
  232. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/code.js +595 -0
  233. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/poster.jpg +0 -0
  234. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/style.json +13 -0
  235. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/code.js +665 -0
  236. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/poster.jpg +0 -0
  237. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/style.json +13 -0
  238. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/code.js +645 -0
  239. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/poster.jpg +0 -0
  240. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/style.json +13 -0
  241. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/code.js +436 -0
  242. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/poster.jpg +0 -0
  243. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/style.json +13 -0
  244. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/code.js +1237 -0
  245. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/poster.jpg +0 -0
  246. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/style.json +13 -0
  247. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/code.js +622 -0
  248. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/poster.jpg +0 -0
  249. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/style.json +13 -0
  250. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/code.js +524 -0
  251. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/poster.jpg +0 -0
  252. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/style.json +13 -0
  253. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/code.js +689 -0
  254. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/poster.jpg +0 -0
  255. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/style.json +13 -0
  256. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/code.js +574 -0
  257. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/poster.jpg +0 -0
  258. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/style.json +13 -0
  259. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/code.js +469 -0
  260. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/poster.jpg +0 -0
  261. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/style.json +13 -0
  262. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/code.js +531 -0
  263. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/poster.jpg +0 -0
  264. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/style.json +13 -0
  265. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/code.js +675 -0
  266. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/poster.jpg +0 -0
  267. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/style.json +13 -0
  268. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/code.js +609 -0
  269. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/poster.jpg +0 -0
  270. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/style.json +13 -0
  271. package/skills/aicut-motion/references/design-systems/README.md +1 -1
  272. package/skills/aicut-motion/references/motion-delivery.md +55 -0
  273. package/skills/aicut-motion/references/pages.md +1 -1
  274. package/skills/aicut-motion/references/style-index.json +690 -0
  275. package/skills/aicut-motion/references/style-research.md +73 -0
  276. package/skills/aicut-motion/references/style-selection.md +71 -0
  277. package/skills/aicut-motion/scripts/atlas-viewer.html +195 -0
  278. package/skills/aicut-motion/scripts/atlas.mjs +109 -0
  279. package/studio/client.mjs +3 -3
  280. package/studio/server/fetch-github.mjs +14 -3
  281. package/studio/server/server.mjs +71 -6
  282. package/studio/server/skill-summaries.mjs +60 -0
  283. package/studio/server/skills.mjs +121 -7
  284. package/studio/server/styles-store.mjs +235 -0
  285. package/studio/server/template-posters.mjs +2 -2
  286. package/studio/server/templates-page.mjs +93 -93
  287. package/studio/server/templates.mjs +32 -7
  288. package/studio/ui/dist/assets/ExportCenter-DasulhAv.js +1 -0
  289. package/studio/ui/dist/assets/SettingsModal-BZw0Hz2_.js +2 -0
  290. package/studio/ui/dist/assets/apple-touch-icon-ClxQ7j-O.png +0 -0
  291. package/studio/ui/dist/assets/es-C8XPZARI.js +1 -0
  292. package/studio/ui/dist/assets/favicon-Blwx8Trs.png +0 -0
  293. package/studio/ui/dist/assets/index-BjJVmBzB.js +1623 -0
  294. package/studio/ui/dist/assets/index-C3BfKZFO.css +1 -0
  295. package/studio/ui/dist/assets/ja-BeNkdaEj.js +1 -0
  296. package/studio/ui/dist/assets/ko-DcTVhdzK.js +1 -0
  297. package/studio/ui/dist/assets/zh-CN-Cg51ctbL.js +1 -0
  298. package/studio/ui/dist/index.html +4 -3
  299. package/studio/ui/dist/assets/ExportCenter-F-2vF-lQ.js +0 -1
  300. package/studio/ui/dist/assets/SettingsModal-BgVYguNu.js +0 -2
  301. package/studio/ui/dist/assets/es-BRUzvuoM.js +0 -1
  302. package/studio/ui/dist/assets/index-Bhg-9dA3.css +0 -1
  303. package/studio/ui/dist/assets/index-CWur1Iw9.js +0 -1625
  304. package/studio/ui/dist/assets/ja-Cth-uDcW.js +0 -1
  305. package/studio/ui/dist/assets/ko-upvw591D.js +0 -1
  306. package/studio/ui/dist/assets/zh-CN-BCfJFazB.js +0 -1
@@ -0,0 +1,664 @@
1
+ {
2
+ // ---------- Lichtenstein pop-art comic panel ----------
3
+ const C = { red: '#e2231a', blue: '#1f5fbf', yellow: '#ffd51f', black: '#141210', white: '#fbf6ea' };
4
+
5
+ // Ben-Day dot field inside the current clip (staggered grid)
6
+ const benday = (x, color, s, r, x0, y0, x1, y1, seed, rgain) => {
7
+ const rr = H.rng(seed);
8
+ x.fillStyle = color; x.beginPath();
9
+ for (let j = 0, y = y0; y < y1 + s; j++, y += s * 0.5) {
10
+ const off = (j & 1) ? s * 0.5 : 0;
11
+ for (let xx = x0 + off; xx < x1 + s; xx += s) {
12
+ const g = rgain ? rgain(xx, y) : 1;
13
+ const rad = r * g * (1 + (rr() - 0.5) * 0.08);
14
+ if (rad < 0.4) continue;
15
+ const px = xx + (rr() - 0.5) * 0.6, py = y + (rr() - 0.5) * 0.6;
16
+ x.moveTo(px + rad, py); x.arc(px, py, rad, 0, Math.PI * 2);
17
+ }
18
+ }
19
+ x.fill();
20
+ };
21
+
22
+ // ---------- robot head geometry (local coords) ----------
23
+ const headPath = (x) => {
24
+ x.beginPath(); x.moveTo(-238, -70);
25
+ x.bezierCurveTo(-240, -255, -125, -325, 0, -325);
26
+ x.bezierCurveTo(125, -325, 240, -255, 238, -70);
27
+ x.lineTo(228, 170); x.quadraticCurveTo(218, 262, 120, 292);
28
+ x.lineTo(-120, 292); x.quadraticCurveTo(-218, 262, -228, 170); x.closePath();
29
+ };
30
+ const facePath = (x) => {
31
+ x.beginPath(); x.moveTo(-180, -150);
32
+ x.quadraticCurveTo(-182, -205, -120, -208); x.lineTo(120, -208); x.quadraticCurveTo(182, -205, 180, -150);
33
+ x.lineTo(172, 150); x.quadraticCurveTo(165, 240, 80, 252); x.lineTo(-80, 252); x.quadraticCurveTo(-165, 240, -172, 150); x.closePath();
34
+ };
35
+ const eye = (x, cx, cy, sgn) => { x.beginPath(); x.ellipse(cx, cy, 64, 50, sgn * 0.08, 0, Math.PI * 2); };
36
+ const tearPath = (x, cx, cy, s) => {
37
+ x.beginPath(); x.moveTo(cx, cy - 46 * s);
38
+ x.bezierCurveTo(cx + 8 * s, cy - 20 * s, cx + 26 * s, cy, cx + 26 * s, cy + 16 * s);
39
+ x.arc(cx, cy + 16 * s, 26 * s, 0, Math.PI);
40
+ x.bezierCurveTo(cx - 26 * s, cy, cx - 8 * s, cy - 20 * s, cx, cy - 46 * s); x.closePath();
41
+ };
42
+ const mouthPath = (x) => {
43
+ x.beginPath(); x.moveTo(-78, 186); x.quadraticCurveTo(0, 150, 78, 186);
44
+ x.lineTo(70, 216); x.quadraticCurveTo(0, 186, -70, 216); x.closePath();
45
+ };
46
+
47
+ const bubblePath = (x) => {
48
+ x.beginPath();
49
+ x.ellipse(330, 318, 262, 150, -0.04, 0.78, Math.PI * 2 + 0.5);
50
+ x.lineTo(588, 600); x.closePath();
51
+ };
52
+
53
+ // ================= EXPLAINER: a comic page read panel by panel =================
54
+ const PE = H.ease, pcl = H.clamp, pseg = (t, a, b) => pcl((t - a) / (b - a));
55
+ const PPW = 1200, PPH = 660, PG = 44;
56
+ const pX = (i) => i * (PPW + PG);
57
+
58
+ // robot head drawn on colour (c) + key (k) plates; mood: think | eat | sad | happy
59
+ const robot = (c, k, cx, cy, s, rot, mood, t, opt = {}) => {
60
+ for (const [x, isKey] of [[c, false], [k, true]]) {
61
+ x.save(); x.translate(cx, cy); x.rotate(rot); x.scale(s, s);
62
+ x.lineJoin = 'round'; x.lineCap = 'round';
63
+ if (!isKey) {
64
+ x.fillStyle = C.yellow; headPath(x); x.fill();
65
+ // orange Ben-Day shade band down the shell's right side (as in the hero still)
66
+ x.save(); headPath(x); x.clip();
67
+ x.beginPath(); x.moveTo(160, -320); x.bezierCurveTo(150, -150, 150, 60, 170, 320); x.lineTo(320, 320); x.lineTo(320, -320); x.closePath(); x.clip();
68
+ benday(x, C.red, 12, 3.3, 100, -340, 320, 340, 12);
69
+ x.restore();
70
+ x.save(); facePath(x); x.clip(); x.fillStyle = C.white; x.fillRect(-300, -300, 600, 600); benday(x, C.red, 16, 3.7, -260, -260, 260, 300, 8); x.restore();
71
+ x.fillStyle = C.white; x.beginPath(); x.moveTo(-226, -60); x.bezierCurveTo(-226, -200, -160, -270, -90, -300); x.bezierCurveTo(-150, -250, -196, -170, -200, -60); x.closePath(); x.fill();
72
+ for (const [ex, sg] of [[-88, -1], [92, 1]]) { x.fillStyle = C.white; eye(x, ex, -30, sg); x.fill(); x.save(); eye(x, ex, -30, sg); x.clip(); x.fillStyle = C.blue; x.beginPath(); x.arc(ex + (opt.look || [8, -8])[0], -30 + (opt.look || [8, -8])[1], 34, 0, 7); x.fill(); x.restore(); }
73
+ // blue-dot eye-shadow lids (Lichtenstein make-up, matches the hero)
74
+ for (const [ex, sg] of [[-88, -1], [92, 1]]) {
75
+ x.save(); x.beginPath(); x.ellipse(ex, -48, 74, 40, sg * 0.1, Math.PI, Math.PI * 2); x.closePath(); x.clip();
76
+ x.fillStyle = C.white; x.fillRect(ex - 90, -120, 180, 70);
77
+ benday(x, C.blue, 10, 3.4, ex - 100, -130, ex + 100, -40, 21 + ex);
78
+ x.restore();
79
+ }
80
+ x.fillStyle = C.red; x.beginPath(); x.arc(0, -392, 30, 0, 7); x.fill();
81
+ for (const sx of [-1, 1]) { x.beginPath(); x.arc(sx * 262, -10, 46, 0, 7); x.fill(); }
82
+ } else {
83
+ x.strokeStyle = C.black; x.fillStyle = C.black;
84
+ x.lineWidth = 12; x.beginPath(); x.moveTo(0, -325); x.lineTo(0, -362); x.stroke();
85
+ x.lineWidth = 9; x.beginPath(); x.arc(0, -392, 30, 0, 7); x.stroke();
86
+ x.fillStyle = C.white; x.beginPath(); x.ellipse(-10, -402, 9, 6, -0.6, 0, 7); x.fill(); x.fillStyle = C.black;
87
+ for (const sx of [-1, 1]) {
88
+ x.lineWidth = 9; x.beginPath(); x.arc(sx * 262, -10, 46, 0, 7); x.stroke();
89
+ x.lineWidth = 5; x.beginPath(); x.arc(sx * 262, -10, 22, 0, 7); x.stroke();
90
+ const sp = opt.spin || 0; x.lineWidth = 8; x.beginPath(); x.moveTo(sx * 262 + Math.cos(sp) * 30, -10 + Math.sin(sp) * 30); x.lineTo(sx * 262 - Math.cos(sp) * 30, -10 - Math.sin(sp) * 30); x.stroke();
91
+ }
92
+ x.save(); headPath(x); x.clip();
93
+ x.beginPath(); x.moveTo(252, -300); x.bezierCurveTo(222, -170, 214, 40, 226, 300); x.lineTo(320, 300); x.lineTo(320, -300); x.closePath(); x.fill();
94
+ x.beginPath(); x.moveTo(-150, -296); x.bezierCurveTo(-60, -318, 70, -318, 150, -290); x.bezierCurveTo(70, -300, -50, -298, -150, -282); x.closePath(); x.fill();
95
+ x.restore();
96
+ // fat brush outlines, doubled with a slight offset like the hero's inking
97
+ x.lineWidth = 13; headPath(x); x.stroke();
98
+ x.save(); x.translate(5, 4); x.lineWidth = 10; headPath(x); x.stroke(); x.restore();
99
+ x.lineWidth = 9; facePath(x); x.stroke();
100
+ x.save(); x.translate(-4, 3); x.lineWidth = 6; facePath(x); x.stroke(); x.restore();
101
+ for (const [rx, ry] of [[-208, -150], [-206, 100], [204, -150], [200, 100], [-60, -270], [60, -270]]) { x.beginPath(); x.arc(rx, ry, 7, 0, 7); x.fill(); }
102
+ for (const [ex, sg] of [[-88, -1], [92, 1]]) {
103
+ const lk = opt.look || [8, -8];
104
+ x.lineWidth = 8; eye(x, ex, -30, sg); x.stroke();
105
+ x.save(); eye(x, ex, -30, sg); x.clip(); x.beginPath(); x.arc(ex + lk[0], -30 + lk[1], 15, 0, 7); x.fill();
106
+ x.fillStyle = C.white; x.beginPath(); x.ellipse(ex + lk[0] - 14, -30 + lk[1] - 16, 10, 7, -0.5, 0, 7); x.fill(); x.fillStyle = C.black; x.restore();
107
+ }
108
+ // brows by mood
109
+ const browY = mood === 'happy' ? -128 : -118;
110
+ for (const sg of [-1, 1]) {
111
+ x.lineWidth = 14; x.beginPath();
112
+ if (mood === 'sad') { x.moveTo(sg * 165, -100); x.quadraticCurveTo(sg * 100, -150, sg * 36, -150); }
113
+ else if (mood === 'think' && sg > 0) { x.moveTo(36, -150); x.quadraticCurveTo(100, -175, 165, -140); }
114
+ else { x.moveTo(sg * 160, browY + 10); x.quadraticCurveTo(sg * 100, browY - 18, sg * 40, browY); }
115
+ x.stroke();
116
+ }
117
+ // mouth
118
+ x.lineWidth = 12;
119
+ if (mood === 'happy') { x.beginPath(); x.moveTo(-80, 160); x.quadraticCurveTo(0, 235, 80, 160); x.quadraticCurveTo(0, 200, -80, 160); x.fill(); x.stroke(); }
120
+ else if (mood === 'eat') { const o = 30 + 16 * Math.abs(Math.sin(t * 9)); x.beginPath(); x.ellipse(0, 180, 60, o, 0, 0, 7); x.fill(); }
121
+ else if (mood === 'sad') { mouthPath(x); x.fill(); }
122
+ else { x.beginPath(); x.moveTo(-60, 185); x.lineTo(50, 175); x.stroke(); }
123
+ }
124
+ x.restore();
125
+ }
126
+ };
127
+
128
+ // comic devices
129
+ const box = (c, k, x0, y0, w, h, fill, txt, size, t0, t) => {
130
+ if (t < t0) return;
131
+ const u = PE.back(pseg(t, t0, t0 + 0.25)), dx = (1 - u) * -w;
132
+ c.fillStyle = fill; c.fillRect(x0 + dx, y0, w, h);
133
+ k.lineWidth = 6; k.strokeStyle = C.black; k.strokeRect(x0 + dx, y0, w, h);
134
+ k.fillStyle = C.black; k.font = `400 ${size}px "Bangers"`; k.letterSpacing = '1.5px'; k.textAlign = 'left'; k.textBaseline = 'middle';
135
+ k.fillText(txt, x0 + dx + 16, y0 + h / 2 + 2); k.letterSpacing = '0px';
136
+ };
137
+ const balloon = (c, k, cx, cy, rx, ry, tx, ty, sc) => {
138
+ if (sc <= 0) return;
139
+ const path = (x) => { x.beginPath(); x.ellipse(cx, cy, rx, ry, 0, 0, 7); };
140
+ const tail = (x) => { const a = Math.atan2(ty - cy, tx - cx); x.beginPath(); x.moveTo(cx + Math.cos(a - 0.22) * rx * 0.9, cy + Math.sin(a - 0.22) * ry * 0.9); x.lineTo(tx, ty); x.lineTo(cx + Math.cos(a + 0.22) * rx * 0.9, cy + Math.sin(a + 0.22) * ry * 0.9); x.closePath(); };
141
+ for (const x of [c, k]) { x.save(); x.translate(cx, cy); x.scale(sc, sc); x.translate(-cx, -cy); }
142
+ k.lineWidth = 8; k.strokeStyle = C.black; tail(k); k.stroke(); path(k); k.stroke();
143
+ c.fillStyle = C.white; path(c); c.fill(); tail(c); c.fill();
144
+ k.fillStyle = '#fff'; k.save(); k.globalCompositeOperation = 'destination-out'; path(k); k.fill(); k.restore();
145
+ k.fillStyle = C.black; k.lineWidth = 8;
146
+ // re-stroke ellipse (tail join hidden by fill)
147
+ path(k); k.stroke();
148
+ k.save(); k.globalCompositeOperation = 'destination-out'; k.beginPath(); k.ellipse(cx, cy, rx - 4, ry - 4, 0, 0, 7); k.fill(); k.restore();
149
+ for (const x of [c, k]) x.restore();
150
+ };
151
+ const burstPath = (x, cx, cy, r, n, seed) => { const rr = H.rng(seed); x.beginPath(); for (let i = 0; i < n * 2; i++) { const a = (i / (n * 2)) * Math.PI * 2, R = i % 2 ? r * rr.range(0.6, 0.72) : r * rr.range(0.92, 1.12); x.lineTo(cx + Math.cos(a) * R, cy + Math.sin(a) * R); } x.closePath(); };
152
+ const burst = (c, k, cx, cy, r, sc, fill, seed) => {
153
+ if (sc <= 0) return;
154
+ c.fillStyle = fill; burstPath(c, cx, cy, r * sc, 14, seed); c.fill();
155
+ k.lineWidth = 8; k.strokeStyle = C.black; k.lineJoin = 'miter'; burstPath(k, cx, cy, r * sc, 14, seed); k.stroke(); k.lineJoin = 'round';
156
+ };
157
+ const say = (k, txt, size, x, y, sc = 1, align = 'center', rot = 0) => {
158
+ if (sc <= 0) return;
159
+ k.save(); k.translate(x, y); k.rotate(rot); k.scale(sc, sc); k.fillStyle = C.black; k.font = `400 ${size}px "Bangers"`; k.letterSpacing = '2px'; k.textAlign = align; k.textBaseline = 'middle'; k.fillText(txt, 0, 0); k.letterSpacing = '0px'; k.restore();
160
+ };
161
+ // lettering knocked out of whatever is behind it: a white halo on both plates (breaks dots, arrow fills and key lines)
162
+ const sayK = (c, k, txt, size, x, y, sc = 1, align = 'center', rot = 0, halo = 12) => {
163
+ if (sc <= 0) return;
164
+ for (const x_ of [c, k]) {
165
+ x_.save(); x_.translate(x, y); x_.rotate(rot); x_.scale(sc, sc); x_.font = `400 ${size}px "Bangers"`; x_.letterSpacing = '2px'; x_.textAlign = align; x_.textBaseline = 'middle';
166
+ x_.lineJoin = 'round'; x_.lineWidth = halo; x_.strokeStyle = C.white; x_.strokeText(txt, 0, 0); x_.letterSpacing = '0px'; x_.restore();
167
+ }
168
+ say(k, txt, size, x, y, sc, align, rot);
169
+ };
170
+ const popIn = (t, t0) => (t < t0 ? 0 : 1 + 0.3 * (1 - PE.out(pseg(t, t0, t0 + 0.16))) * (t - t0 < 0.16 ? 1 : 0));
171
+ // fat comic arrow along a quadratic, drawn on with progress u (yellow fill, black outline)
172
+ const fatArrow = (c, k, p0, cp, p1, u, w, fill) => {
173
+ if (u <= 0) return;
174
+ const q = (s) => [(1 - s) * (1 - s) * p0[0] + 2 * (1 - s) * s * cp[0] + s * s * p1[0], (1 - s) * (1 - s) * p0[1] + 2 * (1 - s) * s * cp[1] + s * s * p1[1]];
175
+ const N = 24, L = [], R = [], end = u * 0.86;
176
+ for (let i = 0; i <= N; i++) { const s = (i / N) * end, a = q(s), b = q(Math.min(1, s + 0.01)); let dx = b[0] - a[0], dy = b[1] - a[1]; const l = Math.hypot(dx, dy) || 1; dx /= l; dy /= l; const ww = w * (0.6 + 0.4 * (i / N)) / 2; L.push([a[0] - dy * ww, a[1] + dx * ww]); R.push([a[0] + dy * ww, a[1] - dx * ww]); }
177
+ const e = q(end), tip = q(Math.min(1, end + 0.14)); let dx = tip[0] - e[0], dy = tip[1] - e[1]; const l = Math.hypot(dx, dy) || 1; dx /= l; dy /= l;
178
+ const hw = w * 1.1;
179
+ const path = (x) => { x.beginPath(); x.moveTo(L[0][0], L[0][1]); L.forEach((p) => x.lineTo(p[0], p[1])); x.lineTo(e[0] - dy * hw, e[1] + dx * hw); x.lineTo(tip[0], tip[1]); x.lineTo(e[0] + dy * hw, e[1] - dx * hw); for (let i = R.length - 1; i >= 0; i--) x.lineTo(R[i][0], R[i][1]); x.closePath(); };
180
+ c.fillStyle = fill; path(c); c.fill(); k.lineWidth = 6; k.strokeStyle = C.black; k.lineJoin = 'round'; path(k); k.stroke();
181
+ return q(0.5);
182
+ };
183
+ // labelled training photo (comic style)
184
+ const photo = (c, k, cx, cy, s, kind, label, rot) => {
185
+ for (const x of [c, k]) { x.save(); x.translate(cx, cy); x.rotate(rot); x.scale(s / 100, s / 100); }
186
+ c.fillStyle = C.white; c.fillRect(-50, -60, 100, 120); c.fillStyle = C.yellow; c.fillRect(-50, 30, 100, 30);
187
+ benday(c, C.blue, 9, 2.4, -46, -56, 46, 24, 4);
188
+ c.fillStyle = C.white; c.beginPath(); c.arc(0, -16, 30, 0, 7); c.fill();
189
+ k.lineWidth = 4; k.strokeStyle = C.black; k.fillStyle = C.black; k.strokeRect(-50, -60, 100, 120); k.beginPath(); k.moveTo(-50, 30); k.lineTo(50, 30); k.stroke();
190
+ k.lineWidth = 3.5; k.beginPath(); k.arc(0, -16, 28, 0, 7); k.stroke();
191
+ if (kind === 'cat') { k.beginPath(); k.moveTo(-24, -28); k.lineTo(-22, -54); k.lineTo(-6, -42); k.moveTo(24, -28); k.lineTo(22, -54); k.lineTo(6, -42); k.stroke(); k.beginPath(); for (const sg of [-1, 1]) { k.moveTo(sg * 8, -8); k.lineTo(sg * 38, -10); k.moveTo(sg * 8, -4); k.lineTo(sg * 36, 0); } k.stroke(); }
192
+ else { for (const sg of [-1, 1]) { k.beginPath(); k.ellipse(sg * 28, -14, 8, 18, sg * 0.4, 0, 7); k.fill(); } k.beginPath(); k.ellipse(0, -6, 6, 4, 0, 0, 7); k.fill(); }
193
+ k.beginPath(); k.arc(-9, -20, 3.2, 0, 7); k.arc(9, -20, 3.2, 0, 7); k.fill();
194
+ k.font = '400 26px "Bangers"'; k.textAlign = 'center'; k.textBaseline = 'middle'; k.fillText(label, 0, 46);
195
+ for (const x of [c, k]) x.restore();
196
+ };
197
+ const speedLines = (k, x0, y0, len, n, seed) => { const r = H.rng(seed); k.lineWidth = 3; k.strokeStyle = C.black; k.lineCap = 'round'; for (let i = 0; i < n; i++) { const yy = y0 + r.range(-50, 50), l = len * r.range(0.5, 1); k.beginPath(); k.moveTo(x0 - 70, yy); k.lineTo(x0 - 70 - l, yy); k.stroke(); } };
198
+
199
+ const panel = (c, k, i, t, bg) => {
200
+ const x0 = pX(i);
201
+ c.save(); c.translate(x0, 0); c.beginPath(); c.rect(0, 0, PPW, PPH); c.clip();
202
+ k.save(); k.translate(x0, 0); k.beginPath(); k.rect(0, 0, PPW, PPH); k.clip();
203
+ bg(c, k);
204
+ return () => { c.restore(); k.restore(); k.save(); k.translate(x0, 0); k.lineWidth = 9; k.strokeStyle = C.black; k.strokeRect(0, 0, PPW, PPH); k.restore(); };
205
+ };
206
+ const fmtN = (n) => Math.round(n).toLocaleString('en-US');
207
+
208
+ const PANELS = [
209
+ // 1 HOOK
210
+ (c, k, t) => {
211
+ const done = panel(c, k, 0, t, (c) => { c.fillStyle = C.white; c.fillRect(0, 0, PPW, PPH); benday(c, C.blue, 17, 5.2, -10, -10, PPW, PPH, 3, (x, y) => 0.75 + 0.3 * H.smooth(0, 1400, x + y * 0.4)); });
212
+ robot(c, k, 860, 470, 0.82, -0.08, 'think', t, { look: [-14, -16] });
213
+ box(c, k, 22, 20, 420, 60, C.yellow, 'MEANWHILE, IN THE LAB...', 30, 0.1, t);
214
+ balloon(c, k, 330, 300, 290, 165, 600, 430, PE.back(pseg(t, 0.35, 0.6)));
215
+ say(k, 'HOW DOES', 84, 330, 255, popIn(t, 0.55)); say(k, 'AI LEARN?', 92, 330, 345, popIn(t, 0.85));
216
+ const bob = Math.floor(t * 6) % 2 ? -6 : 0;
217
+ if (t > 1.1) { say(k, '?', 90, 1040, 120 + bob, 1, 'center', 0.2); say(k, '?', 64, 1110, 190 - bob, 1, 'center', 0.35); }
218
+ done();
219
+ },
220
+ // 2 DATA
221
+ (c, k, t) => {
222
+ const done = panel(c, k, 1, t, (c) => { c.fillStyle = C.yellow; c.fillRect(0, 0, PPW, PPH); benday(c, C.red, 18, 5.4, -10, -10, PPW, PPH, 5, (x, y) => H.smooth(700, 50, Math.hypot(x - 980, y - 330))); });
223
+ robot(c, k, 1000, 370, 0.6, -0.05, 'eat', t, { look: [-20, 0] });
224
+ // stream of labelled examples, faster and faster
225
+ for (let j = 0, t0 = 2.75; j < 26; j++, t0 += Math.max(0.1, 0.34 - j * 0.02)) {
226
+ const u = pseg(t, t0, t0 + 1.1); if (u <= 0 || u >= 1) continue;
227
+ const e = u * u * 0.4 + u * 0.6, x = -80 + e * 1020, y = 300 + Math.sin(j * 1.7) * 90 * (1 - e), kind = j % 3 === 1 ? 'dog' : 'cat';
228
+ speedLines(k, x, y, 120, 3, j);
229
+ photo(c, k, x, y, 130 * (1 - e * 0.55), kind, kind.toUpperCase(), Math.sin(j * 2.3) * 0.25);
230
+ }
231
+ box(c, k, 22, 20, 470, 60, C.white, 'STEP 1: STUDY EXAMPLES', 32, 2.55, t);
232
+ const bs = PE.back(pseg(t, 2.9, 3.15));
233
+ burst(c, k, 250, 540, 175, bs, C.white, 7);
234
+ if (bs > 0) { sayK(c, k, fmtN(1e6 * Math.pow(pseg(t, 3.0, 5.4), 2.3)), 68, 250, 524, bs, 'center', 0, 10); say(k, 'EXAMPLES!', 32, 250, 580, bs); }
235
+ if (t > 5.4) { k.save(); k.strokeStyle = C.black; k.lineWidth = 6; for (let a = 0; a < 8; a++) { const an = -Math.PI / 2 + (a - 3.5) * 0.28, r0 = 195, r1 = 235 + 20 * (Math.floor(t * 8) % 2); k.beginPath(); k.moveTo(250 + Math.cos(an) * r0, 540 + Math.sin(an) * r0); k.lineTo(250 + Math.cos(an) * r1, 540 + Math.sin(an) * r1); k.stroke(); } k.restore(); }
236
+ done();
237
+ },
238
+ // 3 GUESS -> CHECK -> ADJUST
239
+ (c, k, t) => {
240
+ const done = panel(c, k, 2, t, (c) => { c.fillStyle = C.white; c.fillRect(0, 0, PPW, PPH); benday(c, C.blue, 16, 3.6, -10, -10, PPW, PPH, 9); });
241
+ const right = t > 9.0;
242
+ const spin = PE.inOut(pseg(t, 8.2, 8.9)) * Math.PI * 4;
243
+ robot(c, k, 560, 430, 0.55, 0.03, right ? 'happy' : (t > 7.5 ? 'sad' : 'think'), t, { spin, look: [-22, -4] });
244
+ photo(c, k, 165, 400, 230 * PE.back(pseg(t, 6.15, 6.4)), 'cat', '?', -0.06);
245
+ sayK(c, k, 'NEW PICTURE', 30, 165, 590, popIn(t, 6.3), 'center', 0, 9);
246
+ // GUESS arrow (photo -> robot -> thought)
247
+ const g = fatArrow(c, k, [210, 240], [330, 120], [430, 200], PE.out(pseg(t, 6.45, 6.85)), 34, C.yellow);
248
+ if (t > 6.7) sayK(c, k, 'GUESS', 40, 325, 120, popIn(t, 6.7), 'center', -0.1);
249
+ // thought balloon
250
+ const ts = PE.back(pseg(t, 6.85, 7.1));
251
+ if (ts > 0) {
252
+ for (const [bx, by, br] of [[730, 250, 12], [760, 215, 18]]) { c.fillStyle = C.white; c.beginPath(); c.arc(bx, by, br, 0, 7); c.fill(); k.lineWidth = 5; k.beginPath(); k.arc(bx, by, br, 0, 7); k.stroke(); }
253
+ balloon(c, k, 900, 140, 150, 90, 900, 140, ts);
254
+ say(k, right ? 'CAT!' : 'DOG?', 78, 900, 145, right ? popIn(t, 9.0) : ts);
255
+ }
256
+ // CHECK: WRONG! burst with emphasis shake
257
+ if (t > 7.45 && !right) {
258
+ const bs = PE.back(pseg(t, 7.45, 7.65)), sh = t < 7.9 ? Math.sin(t * 90) * 6 : 0;
259
+ burst(c, k, 1050 + sh, 360, 120, bs, C.red, 13);
260
+ say(k, 'WRONG!', 44, 1050 + sh, 350, bs, 'center', -0.1);
261
+ say(k, "IT'S A CAT", 26, 1050, 395, bs, 'center', -0.1);
262
+ k.save(); k.strokeStyle = C.black; k.lineWidth = 14; k.lineCap = 'round'; const u = PE.out(pseg(t, 7.5, 7.7)); k.translate(1000, 120); k.scale(u, u); k.beginPath(); k.moveTo(-40, -40); k.lineTo(40, 40); k.moveTo(40, -40); k.lineTo(-40, 40); k.stroke(); k.restore();
263
+ }
264
+ if (t > 7.5) sayK(c, k, 'CHECK', 40, 1120, 245, popIn(t, 7.5), 'center', 0.08);
265
+ // ADJUST: arrow from the burst back round to the ear bolt
266
+ fatArrow(c, k, [1030, 480], [960, 650], [740, 540], PE.inOut(pseg(t, 7.95, 8.35)), 34, C.yellow);
267
+ if (t > 8.15) sayK(c, k, 'ADJUST', 40, 930, 600, popIn(t, 8.15), 'center', 0.05);
268
+ if (t > 8.25 && t < 9.1) sayK(c, k, 'WHIRR!', 46, 790, 300, 1 + 0.08 * (Math.floor(t * 12) % 2), 'center', 0.25);
269
+ if (right) {
270
+ const u = PE.back(pseg(t, 9.15, 9.4));
271
+ c.fillStyle = C.blue; c.beginPath(); c.arc(1080, 90, 46 * u, 0, 7); c.fill();
272
+ k.save(); k.translate(1080, 90); k.scale(u, u); k.lineWidth = 7; k.strokeStyle = C.black; k.beginPath(); k.arc(0, 0, 46, 0, 7); k.stroke(); k.restore();
273
+ c.save(); c.translate(1080, 90); c.scale(u, u); c.lineWidth = 13; c.lineCap = 'round'; c.lineJoin = 'round'; c.strokeStyle = C.white; c.beginPath(); c.moveTo(-20, 0); c.lineTo(-5, 16); c.lineTo(22, -16); c.stroke(); c.restore();
274
+ // loop it again: arrow re-draws as a cycling highlight
275
+ const lp = ((t - 9.4) / 0.6) % 1;
276
+ if (t > 9.4) { k.save(); k.fillStyle = C.black; const q = [(1 - lp) * (1 - lp) * 1030 + 2 * (1 - lp) * lp * 960 + lp * lp * 740, (1 - lp) * (1 - lp) * 480 + 2 * (1 - lp) * lp * 650 + lp * lp * 540]; k.beginPath(); k.arc(q[0], q[1], 9, 0, 7); k.fill(); k.restore(); }
277
+ }
278
+ done();
279
+ },
280
+ // 4 PROGRESS
281
+ (c, k, t) => {
282
+ const done = panel(c, k, 3, t, (c) => { c.fillStyle = C.white; c.fillRect(0, 0, PPW, PPH); benday(c, C.blue, 17, 5.2, -10, -10, PPW, PPH, 21, (x, y) => 0.6 + 0.3 * H.smooth(1200, 0, x)); });
283
+ box(c, k, 22, 20, 520, 60, C.yellow, 'AFTER MILLIONS OF ROUNDS...', 32, 10.75, t);
284
+ const hit = t > 12.4;
285
+ // sunburst rays sit behind the chart board
286
+ if (hit) { c.save(); c.fillStyle = C.yellow; c.beginPath(); for (let a = 0; a < 16; a++) { const a0 = (a / 16) * Math.PI * 2 + t * 0.4; c.moveTo(950, 340); c.arc(950, 340, 700, a0, a0 + 0.1); } c.fill(); c.restore(); }
287
+ // chart board
288
+ c.fillStyle = C.white; c.fillRect(70, 120, 620, 480); k.lineWidth = 7; k.strokeStyle = C.black; k.strokeRect(70, 120, 620, 480);
289
+ k.lineWidth = 5; k.beginPath(); k.moveTo(110, 150); k.lineTo(110, 540); k.lineTo(670, 540); k.stroke();
290
+ say(k, 'TRAINING ROUNDS', 28, 390, 572, 1);
291
+ const prog = PE.inOut(pseg(t, 11.0, 12.4));
292
+ for (let b = 0; b < 8; b++) {
293
+ const hv = (61 + 36 * (1 - Math.exp(-(b + 1) * 0.45)) / (1 - Math.exp(-8 * 0.45)) - 50) / 50 * 370;
294
+ const g = pcl(prog * 8 - b), h = hv * PE.back(g);
295
+ if (h <= 0) continue;
296
+ const bx = 130 + b * 66;
297
+ c.fillStyle = b % 2 ? C.red : C.yellow; c.fillRect(bx, 540 - h, 50, h);
298
+ c.save(); c.beginPath(); c.rect(bx + 32, 540 - h, 18, h); c.clip(); benday(c, C.black, 8, 2.2, bx, 540 - h, bx + 50, 540, b); c.restore();
299
+ k.lineWidth = 5; k.strokeRect(bx, 540 - h, 50, h);
300
+ }
301
+ const v = Math.round(61 + 36 * prog);
302
+ const bs = PE.back(pseg(t, 10.9, 11.15));
303
+ burst(c, k, 950, 340, 230, bs * (hit ? 1 + 0.12 * (1 - PE.out(pseg(t, 12.4, 12.65))) : 1), hit ? C.red : C.white, 31);
304
+ if (bs > 0) { say(k, 'ACCURACY', 36, 950, 250, bs); say(k, v + '%', 150, 950, 360, bs * (hit ? 1 + 0.25 * (1 - PE.out(pseg(t, 12.4, 12.6))) : 1)); }
305
+ if (hit) sayK(c, k, 'WOW!', 46, 1100, 520, popIn(t, 12.5), 'center', -0.15);
306
+ done();
307
+ },
308
+ // 5 TAKEAWAY
309
+ (c, k, t) => {
310
+ const done = panel(c, k, 4, t, (c) => { c.fillStyle = C.yellow; c.fillRect(0, 0, PPW, PPH); benday(c, C.red, 18, 5.4, -10, -10, PPW, PPH, 41, (x, y) => H.smooth(800, 100, Math.hypot(x - 900, y - 400))); });
311
+ robot(c, k, 920, 450, 0.7, 0.06, 'happy', t, { look: [-18, -10] });
312
+ balloon(c, k, 380, 290, 330, 190, 684, 446, PE.back(pseg(t, 13.15, 13.4)));
313
+ say(k, 'MORE EXAMPLES', 74, 380, 230, popIn(t, 13.3));
314
+ if (t > 13.55) { const u = PE.out(pseg(t, 13.55, 13.75)); k.save(); k.translate(380, 300); k.fillStyle = C.black; k.beginPath(); k.moveTo(-12, -40 * u); k.lineTo(12, -40 * u); k.lineTo(12, 0); k.lineTo(30, 0); k.lineTo(0, 30 * u); k.lineTo(-30, 0); k.lineTo(-12, 0); k.closePath(); k.fill(); k.restore(); }
315
+ say(k, 'BETTER GUESSES!', 74, 380, 375, popIn(t, 13.7));
316
+ box(c, k, 22, PPH - 82, 230, 60, C.white, 'THE END!', 34, 14.2, t);
317
+ done();
318
+ },
319
+ ];
320
+
321
+ let ageCache = null;
322
+ const paperAge = (W, H_) => {
323
+ if (ageCache && ageCache.W === W && ageCache.H === H_) return ageCache;
324
+ const nz = H.makeNoise(41), LW = 160, LH = 90;
325
+ const lo = H.layer(LW, LH), lx = lo.getContext('2d'), li = lx.createImageData(LW, LH);
326
+ for (let y = 0; y < LH; y++) for (let x = 0; x < LW; x++) { const i = (y * LW + x) * 4; li.data[i] = 128 + 127 * nz.fbm2(x * 0.06, y * 0.06, 3); li.data[i + 3] = 255; }
327
+ lx.putImageData(li, 0, 0);
328
+ const big = H.layer(W, H_), bx = big.getContext('2d'); bx.drawImage(lo, 0, 0, W, H_);
329
+ const lf = bx.getImageData(0, 0, W, H_).data, r = H.rng(5), N = W * H_;
330
+ const R = new Float32Array(N), G = new Float32Array(N), B = new Float32Array(N);
331
+ for (let y = 0, p = 0; y < H_; y++) for (let x = 0; x < W; x++, p++) {
332
+ const n = (lf[p * 4] - 128) / 128, g = r() - 0.5, ex = Math.min(x, W - x, y, H_ - y);
333
+ const age = 1 - 0.06 * (1 - H.smooth(0, 160, ex)) - 0.025 * n;
334
+ R[p] = age * (1 + g * 0.05); G[p] = (age - 0.01) * (1 + g * 0.05); B[p] = (age - 0.045) * (1 + g * 0.06);
335
+ }
336
+ return (ageCache = { W, H: H_, r: R, g: G, b: B });
337
+ };
338
+
339
+ const popExplainer = (ctx, W, H_, t) => {
340
+ // camera: reads the strip panel by panel, pulling back a touch on each move
341
+ const B = [2.4, 6.0, 10.6, 13.0];
342
+ let pos = 0, dip = 0;
343
+ B.forEach((b, i) => { const u = PE.inOut(pseg(t, b - 0.4, b + 0.4)); pos += u; dip = Math.max(dip, Math.sin(Math.PI * pseg(t, b - 0.4, b + 0.4))); });
344
+ const cx = pos * (PPW + PG) + PPW / 2, cy = PPH / 2, z = 1 - 0.2 * dip;
345
+ const col = H.layer(W, H_), c = col.getContext('2d'), key = H.layer(W, H_), k = key.getContext('2d');
346
+ c.fillStyle = C.white; c.fillRect(0, 0, W, H_);
347
+ for (const x of [c, k]) { x.save(); x.translate(W / 2, H_ / 2); x.scale(z, z); x.translate(-cx, -cy); x.lineJoin = 'round'; x.lineCap = 'round'; }
348
+ const half = (W / 2) / z - 1.5;
349
+ PANELS.forEach((fn, i) => { const px = pX(i); if (px + PPW + 4.5 < cx - half || px - 4.5 > cx + half) return; fn(c, k, t); });
350
+ for (const x of [c, k]) x.restore();
351
+ ctx.fillStyle = C.white; ctx.fillRect(0, 0, W, H_);
352
+ ctx.drawImage(col, 2.5, -1.5);
353
+ ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(key, 0, 0); ctx.globalCompositeOperation = 'source-over';
354
+ // paper: the hero's low-frequency yellowing, edge ageing and fibre grain (multipliers cached per size)
355
+ const A = paperAge(W, H_), img = ctx.getImageData(0, 0, W, H_), d = img.data;
356
+ for (let i = 0, p = 0; i < d.length; i += 4, p++) { d[i] *= A.r[p]; d[i + 1] *= A.g[p]; d[i + 2] *= A.b[p]; }
357
+ ctx.putImageData(img, 0, 0);
358
+ };
359
+
360
+ registerStyle({
361
+ id: 'halftone-pop',
362
+ name: 'Halftone Pop Panel',
363
+ category: 'Print',
364
+ description: 'A Lichtenstein-style blown-up comic panel: fat tapering black brush outlines, mechanical Ben-Day dot fields in red and blue, flat primary yellow, a tearful close-up and hand-lettered balloon.',
365
+ motion: 'Blue dots pop on in a wave, the robot head springs up into frame, red skin dots wash down the face, caption and balloon slam in word by word, then a single tear wells up and rolls down during a slow push-in.',
366
+ fonts: ['400 66px "Bangers"', '400 30px "Bangers"'],
367
+ duration: 5,
368
+ frameT: 4.8,
369
+ readsPixels: true,
370
+ explainerDuration: 15,
371
+ explainerNote: 'Told as a comic strip the camera reads panel by panel (pulling back to the gutters on each move), with fat yellow comic arrows, captions and balloons, Bangers counters bursting out of explosion balloons, and a WRONG! burst with shake as the emphasis.',
372
+ explainer(ctx, W, H_, t) { popExplainer(ctx, W, H_, t); },
373
+ // Sound: a comic book read aloud by its sound effects. Lettering lands with short punchy "thwacks", explosion
374
+ // balloons go BAM, the WRONG! burst crashes and rattles with its shake, the ear bolts ratchet through WHIRR!,
375
+ // fat arrows zip on, and each camera move across the strip is a soft, smooth slide over the page.
376
+ // Timings mirror PANELS / popExplainer above (B camera beats, box/balloon/popIn/pseg windows).
377
+ // Voiceover mode: room bed drops; photo zips, counter clicks, small labels and bubbles are minor.
378
+ explainerSound(k) {
379
+ const D = 15, B = [2.4, 6.0, 10.6, 13.0];
380
+ const pan = (px) => H.clamp((px / PPW) * 2 - 1, -1, 1) * 0.6; // panel-local x -> stereo
381
+ const when = (t0, t1, ease, u) => { for (let i = 0; i <= 400; i++) { const t = t0 + ((t1 - t0) * i) / 400; if (ease(i / 400) >= u - 1e-6) return t; } return t1; };
382
+ // comic lettering landing: a short dry thwack with a low body (no pitched chirp)
383
+ const punch = (t, g, p, minor) => {
384
+ const o = { minor: !!minor };
385
+ k.tone(t, { ...o, f: [[0, 210], [0.09, 70]], dur: 0.14, gain: 0.42 * g, pan: p });
386
+ k.noise(t, { ...o, dur: 0.06, filter: 'bandpass', f: 1100, q: 0.9, gain: 0.34 * g, a: 0.001, pan: p });
387
+ k.noise(t, { ...o, dur: 0.015, filter: 'highpass', f: 2600, gain: 0.14 * g, a: 0.001, pan: p });
388
+ };
389
+ // explosion balloon: BAM (thud + a short crashing burst of noise)
390
+ const bam = (t, g, p) => {
391
+ k.play('thud', t, { f: 120, gain: 0.9 * g, pan: p });
392
+ k.noise(t, { dur: 0.42, color: 'pink', filter: 'lowpass', f: [[0, 3200], [0.4, 380]], gain: 0.34 * g, a: 0.002, verb: 0.25, pan: p });
393
+ k.noise(t, { dur: 0.05, filter: 'bandpass', f: 1400, q: 0.8, gain: 0.3 * g, a: 0.001, pan: p });
394
+ };
395
+ // fat arrow / photo whipping on: a short low zip, length = draw time
396
+ const zip = (t, d, g, p, minor) => k.noise(t, { minor: !!minor, dur: d, color: 'pink', filter: 'bandpass', f: [[0, 420], [d * 0.7, 1500], [d, 900]], q: 1.1, gain: 0.22 * g, a: d * 0.55, pan: p });
397
+ // camera glides along the strip: a soft smooth slide, centred on the beat
398
+ const glide = (b) => k.noise(b - 0.4, { dur: 0.85, color: 'brown', filter: 'lowpass', f: [[0, 260], [0.4, 700], [0.85, 240]], q: 0.7, gain: 0.2, a: 0.38 });
399
+ // counter digit flip: tiny dry click, low
400
+ const clk = (t, g, p) => k.noise(t, { minor: true, dur: 0.02, filter: 'bandpass', f: 2300, q: 1.6, gain: 0.26 * g, a: 0.001, pan: p });
401
+
402
+ k.bed('room', 0, D, { gain: 0.18 });
403
+
404
+ // ---- panel 1: HOOK
405
+ k.play('slap', when(0.1, 0.35, PE.back, 1), { gain: 0.35, pan: pan(230), minor: true }); // MEANWHILE caption box
406
+ k.play('pop', when(0.35, 0.6, PE.back, 1), { f: 330, gain: 0.7, pan: pan(330), minor: true }); // balloon inflates
407
+ punch(0.55, 0.9, pan(330)); // HOW DOES
408
+ punch(0.85, 1.05, pan(330)); // AI LEARN?
409
+ k.play('pop', 1.1, { f: 300, gain: 0.45, pan: pan(1040), minor: true }); // the two ?s
410
+ k.play('pop', 1.24, { f: 260, gain: 0.4, pan: pan(1110), minor: true });
411
+
412
+ // ---- camera to panel 2
413
+ glide(B[0]);
414
+ k.play('slap', when(2.55, 2.8, PE.back, 1), { gain: 0.3, pan: -0.4, minor: true }); // STEP 1 caption
415
+ bam(when(2.9, 3.15, PE.back, 1) - 0.12, 0.5, pan(250)); // EXAMPLES! burst springs open
416
+ // photos fly in with speed lines (same spawn schedule as panel 2)
417
+ for (let j = 0, t0 = 2.75; t0 < 5.45; j++, t0 += Math.max(0.1, 0.34 - j * 0.02)) zip(t0, 0.16, 0.6, -0.6, true);
418
+ // counter n = 1e6 * pseg(3.0, 5.4)^2.3: clicks speeding up with the count, <= ~11/s
419
+ for (let t = 3.06; t < 5.32; ) { const s = pseg(t, 3.0, 5.4); clk(t, 0.75 + 0.35 * s, pan(250)); t += 0.18 - 0.09 * s; }
420
+ bam(5.4, 0.75, pan(250)); // 1,000,000 + action lines flash
421
+ punch(5.4, 0.55, pan(250));
422
+
423
+ // ---- camera to panel 3
424
+ glide(B[1]);
425
+ k.play('pop', when(6.15, 6.4, PE.back, 1) - 0.08, { f: 240, gain: 0.7, pan: pan(165), minor: true }); // NEW PICTURE photo
426
+ punch(6.3, 0.45, pan(165), true); // NEW PICTURE label
427
+ zip(6.45, 0.4, 0.9, pan(320)); // GUESS arrow
428
+ punch(6.7, 0.65, pan(325), true); // GUESS
429
+ k.play('pop', 6.85, { f: 380, gain: 0.35, pan: pan(730), minor: true }); // thought bubbles
430
+ k.play('pop', when(6.85, 7.1, PE.back, 1) - 0.05, { f: 300, gain: 0.6, pan: pan(900) }); // DOG? balloon
431
+ // CHECK: WRONG! burst crashes in, rattles while it shakes (7.45 -> 7.9)
432
+ bam(7.45, 1.15, pan(1050));
433
+ k.play('buzz', 7.58, { f: 92, dur: 0.3, gain: 0.8, pan: pan(1050) });
434
+ for (let t = 7.5; t < 7.88; t += 0.07) k.noise(t, { minor: true, dur: 0.04, filter: 'bandpass', f: 700, q: 1.2, gain: 0.16, a: 0.001, pan: pan(1050) });
435
+ k.play('stroke', 7.56, { material: 'brush', dur: 0.16, gain: 1.1, pan: pan(1000), minor: true }); // the inked X
436
+ punch(7.5, 0.55, pan(1120), true); // CHECK
437
+ // ADJUST arrow, label, then the ear bolts ratchet round twice (spin 8.2..8.9, 4*pi) under WHIRR!
438
+ zip(7.95, 0.4, 0.9, pan(900));
439
+ punch(8.15, 0.6, pan(930), true);
440
+ k.tone(8.22, { f: [[0, 85], [0.35, 150], [0.7, 85]], type: 'sawtooth', dur: 0.75, gain: 0.045, a: 0.1, hold: 0.4, filter: 'lowpass', f2: 650, pan: pan(560) });
441
+ for (let i = 1; i <= 4; i++) k.play('click', when(8.2, 8.9, PE.inOut, i / 4), { f: 1150, gain: 1.1, pan: pan(560) + (i % 2 ? -0.15 : 0.15), minor: true });
442
+ // CORRECT: CAT! lands, then the blue tick
443
+ punch(9.0, 1.0, pan(900));
444
+ k.play('success', when(9.15, 9.4, PE.back, 1) - 0.05, { f: 'C5', gain: 0.5, pan: pan(1080) });
445
+ // the loop keeps cycling: a soft tap each time the dot sets off
446
+ for (let t = 10.0; t < 10.3; t += 0.6) k.play('tock', t, { f: 420, gain: 0.25, pan: pan(1030), minor: true });
447
+
448
+ // ---- camera to panel 4
449
+ glide(B[2]);
450
+ k.play('slap', when(10.75, 11.0, PE.back, 1), { gain: 0.3, pan: -0.4, minor: true }); // AFTER MILLIONS caption
451
+ k.play('pop', when(10.9, 11.15, PE.back, 1) - 0.08, { f: 260, gain: 0.6, pan: pan(950), minor: true }); // ACCURACY burst opens
452
+ // bars spring up one by one (bar b starts when prog = b/8), each a little higher-pitched thunk
453
+ for (let b = 0; b < 8; b++) k.tone(when(11.0, 12.4, PE.inOut, b / 8 + 0.01), { minor: true, f: [[0, 150 + b * 18], [0.08, 80 + b * 10]], dur: 0.12, gain: 0.18, pan: pan(130 + b * 66) });
454
+ // 61% -> 97% counter, throttled to ~10/s
455
+ for (let t = 11.0, last = 61, lastT = -1; t < 12.42; t += 1 / 120) { const v = Math.round(61 + 36 * PE.inOut(pseg(t, 11.0, 12.4))); if (v !== last && t - lastT >= 0.1) { clk(t, 0.9, pan(950)); last = v; lastT = t; } }
456
+ // 97%: the biggest hit. KA-POW + sunburst
457
+ bam(12.4, 1.4, pan(950));
458
+ k.play('impact', 12.4, { gain: 1.0, pan: pan(950) });
459
+ k.tone(12.4, { f: [[0, 75], [0.5, 38]], dur: 0.7, gain: 0.5, pan: pan(950) }); // sub boom under the sunburst
460
+ punch(12.5, 0.75, pan(1100)); // WOW!
461
+
462
+ // ---- camera to panel 5: TAKEAWAY
463
+ glide(B[3]);
464
+ k.play('pop', when(13.15, 13.4, PE.back, 1) - 0.06, { f: 300, gain: 0.6, pan: pan(380), minor: true });
465
+ punch(13.3, 0.8, pan(380)); // MORE EXAMPLES
466
+ zip(13.55, 0.2, 0.6, pan(380), true); // down arrow
467
+ punch(13.7, 1.0, pan(380)); // BETTER GUESSES!
468
+ ['C5', 'E5', 'G5', 'C6'].forEach((f, i) => k.play('ding', 13.8 + i * 0.07, { f, gain: 0.38 - i * 0.05, pan: (i - 1.5) * 0.2 }));
469
+ k.play('slap', when(14.2, 14.45, PE.back, 1), { gain: 0.4, pan: -0.5 }); // THE END!
470
+ },
471
+ draw(ctx, W, H_, t) {
472
+ const PX = 26, PY = 24, PW = W - 52, PH = H_ - 48; // panel
473
+ const E = H.ease;
474
+ const headIn = H.clamp((t - 0.5) / 0.5);
475
+ const hx = 880, hy = 462 + Math.sin(t * 1.3) * 3 + (1 - E.back(headIn)) * 720, hr = -0.11 + (1 - E.out(headIn)) * 0.25, hs = 1.04;
476
+ const bgT = H.clamp((t - 0.02) / 0.7), bgWave = (x, y) => H.clamp((bgT * 1900 - (x - PX) - (y - PY) * 0.5) / 160);
477
+ const faceT = H.clamp((t - 1.0) / 0.5), faceWave = (x, y) => H.clamp((faceT * 700 - (y + 330)) / 70);
478
+ const tearIn = H.clamp((t - 2.85) / 1.15), tearY = H.lerp(-40, 105, E.inOut(tearIn)), tearS = 1.3 * E.out(H.clamp((t - 2.85) / 0.3));
479
+ const capIn = E.back(H.clamp((t - 1.5) / 0.35)), capDX = (1 - capIn) * -500;
480
+ const balIn = H.clamp((t - 1.9) / 0.28), balS = balIn <= 0 ? 0 : E.back(balIn);
481
+ const pop = (t0) => (t < t0 ? 0 : 1 + 0.35 * (1 - E.out(H.clamp((t - t0) / 0.14))));
482
+ const bal = (x, fn) => { if (balS <= 0) return; x.save(); x.translate(560, 520); x.scale(balS, balS); x.translate(-560, -520); fn(); x.restore(); };
483
+ const cap = (x, fn) => { if (t < 1.5) return; x.save(); x.translate(capDX, 0); fn(); x.restore(); };
484
+ const word = (x, t0, txt, size, cx, cy) => { const sc = pop(t0); if (!sc) return; x.save(); x.translate(cx, cy); x.scale(sc, sc); x.font = `400 ${size}px "Bangers"`; x.fillText(txt, 0, 0); x.restore(); };
485
+
486
+ // ---------- COLOR PLATES ----------
487
+ const col = H.layer(W, H_), c = col.getContext('2d');
488
+ c.fillStyle = C.white; c.fillRect(0, 0, W, H_);
489
+ c.save(); c.beginPath(); c.rect(PX, PY, PW, PH); c.clip();
490
+ // background: blue Ben-Day field, denser toward the right edge
491
+ benday(c, C.blue, 17, 5.2, PX - 10, PY - 10, PX + PW, PY + PH, 3, (x, y) => (0.72 + 0.32 * H.smooth(200, 1200, x + y * 0.4)) * bgWave(x, y));
492
+ // head
493
+ if (headIn > 0) {
494
+ c.save(); c.translate(hx, hy); c.rotate(hr); c.scale(hs, hs);
495
+ c.fillStyle = C.yellow; headPath(c); c.fill();
496
+ c.save(); headPath(c); c.clip();
497
+ c.beginPath(); c.moveTo(160, -320); c.bezierCurveTo(150, -150, 150, 60, 170, 320); c.lineTo(320, 320); c.lineTo(320, -320); c.closePath(); c.clip();
498
+ benday(c, C.red, 12, 3.3, 100, -340, 320, 340, 12, faceWave);
499
+ c.restore();
500
+ c.fillStyle = C.white; c.beginPath(); c.moveTo(-226, -60); c.bezierCurveTo(-226, -200, -160, -270, -90, -300); c.bezierCurveTo(-150, -250, -196, -170, -200, -60); c.closePath(); c.fill();
501
+ c.save(); facePath(c); c.clip();
502
+ c.fillStyle = C.white; c.fillRect(-300, -300, 600, 600);
503
+ benday(c, C.red, 16, 3.7, -260, -260, 260, 300, 8, faceWave);
504
+ c.restore();
505
+ // eyes: whites + blue irises
506
+ for (const [ex, sg] of [[-88, -1], [92, 1]]) {
507
+ c.fillStyle = C.white; eye(c, ex, -30, sg); c.fill();
508
+ c.save(); eye(c, ex, -30, sg); c.clip();
509
+ c.fillStyle = C.blue; c.beginPath(); c.arc(ex + 8, -22, 34, 0, 7); c.fill();
510
+ c.restore();
511
+ }
512
+ // eye-shadow lids (blue dots like Roy's make-up)
513
+ for (const [ex, sg] of [[-88, -1], [92, 1]]) {
514
+ c.save(); c.beginPath(); c.ellipse(ex, -48, 74, 40, sg * 0.1, Math.PI, Math.PI * 2); c.closePath(); c.clip();
515
+ c.fillStyle = C.white; c.fillRect(ex - 90, -120, 180, 70);
516
+ benday(c, C.blue, 10, 3.4, ex - 100, -130, ex + 100, -40, 21 + ex);
517
+ c.restore();
518
+ }
519
+ // antenna bulb, ear bolts
520
+ c.fillStyle = C.red; c.beginPath(); c.arc(0, -392, 30, 0, 7); c.fill();
521
+ for (const sx of [-1, 1]) { c.beginPath(); c.arc(sx * 262, -10, 46, 0, 7); c.fill(); }
522
+ // tear
523
+ if (tearS > 0) {
524
+ c.fillStyle = '#5fa3ef'; tearPath(c, -128, tearY + 40, tearS); c.fill();
525
+ if (tearY > 14) { c.beginPath(); c.moveTo(-128, 4); c.lineTo(-128, tearY - 10); c.lineWidth = 12; c.strokeStyle = '#5fa3ef'; c.stroke(); }
526
+ }
527
+ c.restore();
528
+ }
529
+ // caption box + balloon
530
+ cap(c, () => { c.fillStyle = C.yellow; c.fillRect(PX + 18, PY + 16, 412, 64); });
531
+ bal(c, () => { c.fillStyle = C.white; bubblePath(c); c.fill(); });
532
+ c.restore();
533
+
534
+ // ---------- KEY (black) PLATE ----------
535
+ const key = H.layer(W, H_), k = key.getContext('2d');
536
+ k.lineJoin = 'round'; k.lineCap = 'round'; k.strokeStyle = C.black; k.fillStyle = C.black;
537
+ k.save(); k.beginPath(); k.rect(PX, PY, PW, PH); k.clip();
538
+ if (headIn > 0) {
539
+ k.save(); k.translate(hx, hy); k.rotate(hr); k.scale(hs, hs);
540
+ // antenna
541
+ k.lineWidth = 12; k.beginPath(); k.moveTo(0, -325); k.lineTo(0, -362); k.stroke();
542
+ k.lineWidth = 9; k.beginPath(); k.arc(0, -392, 30, 0, 7); k.stroke();
543
+ k.fillStyle = C.white; k.beginPath(); k.ellipse(-10, -402, 9, 6, -0.6, 0, 7); k.fill(); k.fillStyle = C.black;
544
+ // ear bolts
545
+ for (const sx of [-1, 1]) {
546
+ k.lineWidth = 9; k.beginPath(); k.arc(sx * 262, -10, 46, 0, 7); k.stroke();
547
+ k.lineWidth = 6; k.beginPath(); k.arc(sx * 262, -10, 22, 0, 7); k.stroke();
548
+ }
549
+ // chrome shading on yellow shell: hard black reflection shapes
550
+ k.save(); headPath(k); k.clip();
551
+ k.beginPath(); k.moveTo(250, -300); k.bezierCurveTo(205, -170, 196, 40, 214, 300); k.lineTo(320, 300); k.lineTo(320, -300); k.closePath(); k.fill();
552
+ k.beginPath(); k.moveTo(-150, -296); k.bezierCurveTo(-60, -318, 70, -318, 150, -290); k.bezierCurveTo(70, -300, -50, -298, -150, -282); k.closePath(); k.fill();
553
+ // shell highlight streaks (white) on the dome
554
+ k.restore();
555
+ // outlines: fat brush lines
556
+ k.lineWidth = 12; headPath(k); k.stroke();
557
+ k.save(); k.translate(5, 4); k.lineWidth = 10; headPath(k); k.stroke(); k.restore();
558
+ k.lineWidth = 8; facePath(k); k.stroke();
559
+ k.save(); k.translate(-4, 3); k.lineWidth = 6; facePath(k); k.stroke(); k.restore();
560
+ // rivets
561
+ for (const [rx, ry] of [[-208, -150], [-206, 100], [204, -150], [200, 100], [-60, -270], [60, -270]]) { k.beginPath(); k.arc(rx, ry, 7, 0, 7); k.fill(); }
562
+ // eyes
563
+ for (const [ex, sg] of [[-88, -1], [92, 1]]) {
564
+ k.lineWidth = 8; eye(k, ex, -30, sg); k.stroke();
565
+ k.save(); eye(k, ex, -30, sg); k.clip();
566
+ k.beginPath(); k.arc(ex + 8, -22, 15, 0, 7); k.fill();
567
+ k.lineWidth = 5; k.beginPath(); k.arc(ex + 8, -22, 34, 0, 7); k.stroke();
568
+ k.fillStyle = C.white;
569
+ k.beginPath(); k.ellipse(ex - 6, -40, 11, 8, -0.5, 0, 7); k.fill();
570
+ k.beginPath(); k.arc(ex + 20, -10, 5, 0, 7); k.fill();
571
+ k.fillStyle = C.black;
572
+ // heavy upper lid
573
+ k.beginPath(); k.ellipse(ex, -30, 66, 52, sg * 0.08, Math.PI * 1.02, Math.PI * 1.98); k.lineWidth = 14; k.stroke();
574
+ k.restore();
575
+ // lashes-like brush flicks at outer corner
576
+ k.lineWidth = 7;
577
+ k.beginPath(); k.moveTo(ex + sg * 58, -52); k.quadraticCurveTo(ex + sg * 80, -66, ex + sg * 92, -62); k.stroke();
578
+ }
579
+ // worried brows: tapered brush strokes
580
+ const brow = (x0, y0, x1, y1, bulge) => {
581
+ k.beginPath(); k.moveTo(x0, y0);
582
+ k.quadraticCurveTo((x0 + x1) / 2, (y0 + y1) / 2 - bulge, x1, y1);
583
+ k.quadraticCurveTo((x0 + x1) / 2, (y0 + y1) / 2 - bulge + 22, x0 + (x1 > x0 ? 6 : -6), y0 + 6); k.closePath(); k.fill();
584
+ };
585
+ brow(-168, -96, -32, -150, 18); brow(172, -96, 36, -150, 18);
586
+ // nose seam / cheek plate lines
587
+ k.lineWidth = 6; k.beginPath(); k.moveTo(0, 30); k.quadraticCurveTo(-14, 80, 4, 104); k.stroke();
588
+ // mouth grille (down-turned)
589
+ mouthPath(k); k.fill();
590
+ k.strokeStyle = C.white; k.lineWidth = 4;
591
+ k.beginPath(); k.moveTo(-50, 188); k.quadraticCurveTo(0, 168, 50, 188); k.stroke();
592
+ k.strokeStyle = C.black;
593
+ // tear outline + highlight
594
+ if (tearS > 0) {
595
+ k.lineWidth = 7; tearPath(k, -128, tearY + 40, tearS); k.stroke();
596
+ if (tearY > 14) { k.lineWidth = 5; k.beginPath(); k.moveTo(-134, 6); k.lineTo(-134, tearY - 14); k.moveTo(-122, 6); k.lineTo(-122, tearY - 14); k.stroke(); }
597
+ k.fillStyle = C.white; k.beginPath(); k.ellipse(-128 - 12 * tearS / 1.3, tearY + 40 + 18 * tearS / 1.3, 8 * tearS / 1.3, 14 * tearS / 1.3, 0.3, 0, 7); k.fill(); k.fillStyle = C.black;
598
+ }
599
+ k.restore();
600
+ }
601
+ // emanata (shock lines) around head
602
+ k.lineWidth = 8;
603
+ if (t > 1.45) {
604
+ const throb = 1 + 0.12 * (Math.floor(t * 6) % 2); // on-twos shock pulse
605
+ for (const [a, r0, r1] of [[-2.2, 380, 440], [-2.0, 390, 455], [-1.8, 384, 430], [-1.0, 395, 445], [-0.8, 380, 438]]) {
606
+ const r1b = r0 + (r1 - r0) * throb;
607
+ k.beginPath(); k.moveTo(hx + Math.cos(a) * r0, hy - 30 + Math.sin(a) * r0); k.lineTo(hx + Math.cos(a) * r1b, hy - 30 + Math.sin(a) * r1b); k.stroke();
608
+ }
609
+ }
610
+ // caption box
611
+ cap(k, () => {
612
+ k.lineWidth = 6; k.strokeRect(PX + 18, PY + 16, 412, 64);
613
+ k.font = '400 30px "Bangers"'; k.letterSpacing = '1.5px'; k.textBaseline = 'middle'; k.textAlign = 'left';
614
+ k.fillText('MEANWHILE, IN THE LAB...', PX + 36, PY + 50);
615
+ });
616
+ k.textBaseline = 'middle';
617
+ // balloon
618
+ bal(k, () => { k.lineWidth = 8; bubblePath(k); k.stroke(); });
619
+ k.textAlign = 'center'; k.letterSpacing = '2px';
620
+ word(k, 2.12, 'I THINK...', 58, 330, 250);
621
+ word(k, 2.42, 'I FEEL', 70, 330, 318);
622
+ word(k, 2.72, 'SOMETHING!', 66, 336, 386);
623
+ k.restore();
624
+ // panel border
625
+ { const per = 2 * (PW + PH), u = E.inOut(H.clamp(t / 0.4));
626
+ k.lineWidth = 9; k.setLineDash([per * u, per]); k.strokeRect(PX, PY, PW, PH); k.setLineDash([]); }
627
+
628
+ // ---------- PRINT: paper, misregistered plates, dot gain, grain ----------
629
+ ctx.drawImage(col, 0, 0);
630
+ // offset colour plate ~2px vs key (press misregistration)
631
+ ctx.fillStyle = C.white; ctx.fillRect(0, 0, W, H_);
632
+ ctx.drawImage(col, 2.5, -1.5);
633
+ ctx.globalCompositeOperation = 'multiply';
634
+ ctx.drawImage(key, 0, 0);
635
+ ctx.globalCompositeOperation = 'source-over';
636
+ const img = ctx.getImageData(0, 0, W, H_), d = img.data;
637
+ const nz = H.makeNoise(41);
638
+ const lo = H.layer(80, 45), lx = lo.getContext('2d'), li = lx.createImageData(80, 45);
639
+ for (let y = 0; y < 45; y++) for (let x = 0; x < 80; x++) { const i = (y * 80 + x) * 4; li.data[i] = 128 + 127 * nz.fbm2(x * 0.12, y * 0.12, 3); li.data[i + 3] = 255; }
640
+ lx.putImageData(li, 0, 0);
641
+ const big = H.layer(W, H_), bx = big.getContext('2d'); bx.drawImage(lo, 0, 0, W, H_);
642
+ const lf = bx.getImageData(0, 0, W, H_).data;
643
+ const r = H.rng(5);
644
+ for (let y = 0; y < H_; y++) for (let x = 0; x < W; x++) {
645
+ const i = (y * W + x) * 4;
646
+ const n = (lf[i] - 128) / 128, g = (r() - 0.5);
647
+ // paper yellowing toward edges + fibre grain
648
+ const ex = Math.min(x, W - x, y, H_ - y);
649
+ const age = 1 - 0.05 * (1 - H.smooth(0, 160, ex)) - 0.02 * n;
650
+ d[i] = d[i] * age * (1 + g * 0.05);
651
+ d[i + 1] = d[i + 1] * (age - 0.008) * (1 + g * 0.05);
652
+ d[i + 2] = d[i + 2] * (age - 0.04) * (1 + g * 0.06);
653
+ }
654
+ ctx.putImageData(img, 0, 0);
655
+ // slow camera push-in on the drama
656
+ const z = 1 + 0.02 * E.inOut(H.clamp((t - 1.9) / 3.1));
657
+ if (z > 1.0001) {
658
+ const cp = H.layer(W, H_); cp.getContext('2d').drawImage(ctx.canvas, 0, 0, W, H_);
659
+ const fx = 660, fy = 360;
660
+ ctx.drawImage(cp, fx - fx / z, fy - fy / z, W / z, H_ / z, 0, 0, W, H_);
661
+ }
662
+ },
663
+ });
664
+ }