@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,13 @@
1
+ {
2
+ "id": 57,
3
+ "name": "Cross-Stitch Sampler",
4
+ "nameZh": "十字绣样本",
5
+ "family": "Paper & Craft",
6
+ "familyZh": "纸艺与手工",
7
+ "description": "Pixel art worked as real counted cross-stitch on cream aida: every cell is an X of two-strand floss with sheen and twist, top legs all leaning the same way, dark backstitch outlines, all stretched in a wooden hoop.",
8
+ "motionNotes": "Motion: The design fills in stitch by stitch in chart order, the needle hopping row to row trailing its floss, while the hoop drifts gently.",
9
+ "explainerNotes": "Explainer: One long sampler slides through the hoop from motif to motif; everything is stitched cell by cell with the needle chasing the newest stitch, arrows and outlines are backstitch laid one hole at a time, numbers are re-stitched in a 3x5 sampler font, and the wrong guess gets a red running-stitch ring.",
10
+ "code": "code.js",
11
+ "registeredAs": "cross-stitch",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }
@@ -0,0 +1,540 @@
1
+ {
2
+ const fieldNoise = (w, h, seed, cell, oct, sx = 1, sy = 1) => {
3
+ const nz = H.makeNoise(seed), gw = Math.ceil(w / cell) + 2, gh = Math.ceil(h / cell) + 2, g = new Float32Array(gw * gh);
4
+ for (let j = 0; j < gh; j++) for (let i = 0; i < gw; i++) g[j * gw + i] = nz.fbm2(i * 0.35 * sx, j * 0.35 * sy, oct);
5
+ const out = new Float32Array(w * h);
6
+ for (let y = 0; y < h; y++) {
7
+ const fy = y / cell, j = fy | 0, ty = fy - j;
8
+ for (let x = 0; x < w; x++) {
9
+ const fx = x / cell, i = fx | 0, tx = fx - i, k = j * gw + i;
10
+ const a = g[k] + (g[k + 1] - g[k]) * tx, b = g[k + gw] + (g[k + gw + 1] - g[k + gw]) * tx;
11
+ out[y * w + x] = a + (b - a) * ty;
12
+ }
13
+ }
14
+ return out;
15
+ };
16
+ const inkify = (layer, seed, mottle, speck) => {
17
+ const c = layer.getContext('2d'), w = layer.width, h = layer.height, img = c.getImageData(0, 0, w, h), d = img.data;
18
+ const n1 = fieldNoise(w, h, seed, 4, 3), n2 = fieldNoise(w, h, seed + 9, 36, 3), r = H.rng(seed + 3);
19
+ for (let i = 0, p = 0; i < d.length; i += 4, p++) {
20
+ if (!d[i + 3]) continue;
21
+ let a = 1 - mottle * Math.max(0, n1[p] * 1.7 + n2[p] * 1.1);
22
+ if (r() < speck * (0.3 + Math.max(0, n2[p]) * 4)) a *= 0.2;
23
+ d[i + 3] *= H.clamp(a, 0, 1);
24
+ }
25
+ c.putImageData(img, 0, 0);
26
+ };
27
+
28
+ let PHOTO = null;
29
+ const CACHE = {}; const CF = (k, ...a) => CACHE[k] || (CACHE[k] = fieldNoise(...a));
30
+ const RED = '#c8281e', BLACK = '#1d1a17', CREAM = '#e9dcbc';
31
+
32
+ // grayscale "photograph" of a robot shouting into a megaphone (profile, facing right)
33
+ const drawPhoto = (c, t) => {
34
+ const lg = (x0, y0, x1, y1, stops) => { const g = c.createLinearGradient(x0, y0, x1, y1); stops.forEach(([o, v]) => g.addColorStop(o, `rgb(${v},${v},${v})`)); return g; };
35
+ const rg = (x, y, r0, r1, stops) => { const g = c.createRadialGradient(x, y, r0, x, y, r1); stops.forEach(([o, v]) => g.addColorStop(o, `rgb(${v},${v},${v})`)); return g; };
36
+ // torso / shoulders
37
+ c.fillStyle = lg(150, 470, 560, 720, [[0, 70], [0.45, 150], [0.75, 210], [1, 120]]);
38
+ c.beginPath(); c.moveTo(120, 760); c.lineTo(170, 520); c.quadraticCurveTo(200, 470, 290, 458); c.lineTo(470, 458); c.quadraticCurveTo(560, 470, 590, 540); c.lineTo(640, 760); c.closePath(); c.fill();
39
+ // chest plate seams + rivets
40
+ c.strokeStyle = 'rgb(40,40,40)'; c.lineWidth = 5;
41
+ c.beginPath(); c.moveTo(380, 470); c.lineTo(390, 760); c.stroke();
42
+ c.beginPath(); c.moveTo(200, 560); c.lineTo(600, 560); c.stroke();
43
+ c.fillStyle = 'rgb(30,30,30)';
44
+ for (let i = 0; i < 7; i++) { c.beginPath(); c.arc(230 + i * 55, 585, 6, 0, 7); c.fill(); }
45
+ c.fillStyle = rg(470, 640, 5, 90, [[0, 250], [1, 90]]); c.beginPath(); c.arc(470, 650, 70, 0, 7); c.fill(); // power core
46
+ c.fillStyle = 'rgb(25,25,25)'; c.beginPath(); c.arc(470, 650, 38, 0, 7); c.fill();
47
+ c.fillStyle = rg(462, 640, 2, 30, [[0, 255], [1, 120]]); c.beginPath(); c.arc(470, 650, 26, 0, 7); c.fill();
48
+ // neck rings
49
+ for (let i = 0; i < 4; i++) {
50
+ c.fillStyle = lg(320, 0, 440, 0, [[0, 50], [0.6, 200], [1, 90]]);
51
+ c.beginPath(); c.ellipse(380, 445 - i * 16, 58, 13, 0, 0, 7); c.fill();
52
+ }
53
+ // head (profile)
54
+ c.fillStyle = lg(250, 160, 520, 420, [[0, 60], [0.35, 140], [0.7, 225], [1, 160]]);
55
+ c.beginPath();
56
+ c.moveTo(270, 400); c.lineTo(250, 250); c.quadraticCurveTo(250, 150, 360, 140); c.lineTo(450, 140);
57
+ c.quadraticCurveTo(510, 145, 520, 210); c.lineTo(535, 300); c.lineTo(520, 345); c.lineTo(500, 400); c.closePath(); c.fill();
58
+ // jaw shadow
59
+ c.fillStyle = 'rgba(20,20,20,0.55)'; c.beginPath(); c.moveTo(270, 400); c.lineTo(500, 400); c.lineTo(515, 360); c.lineTo(290, 370); c.closePath(); c.fill();
60
+ // visor
61
+ c.fillStyle = lg(380, 200, 540, 260, [[0, 20], [0.7, 40], [1, 90]]);
62
+ c.beginPath(); c.moveTo(380, 205); c.lineTo(522, 205); c.lineTo(530, 262); c.lineTo(390, 262); c.closePath(); c.fill();
63
+ c.fillStyle = rg(480, 228, 2, 40, [[0, 255], [1, 40]]); c.beginPath(); c.ellipse(480, 230, 34, 14, 0, 0, 7); c.fill(); // glowing eye
64
+ c.strokeStyle = 'rgb(250,250,250)'; c.lineWidth = 4; c.beginPath(); c.moveTo(395, 212); c.lineTo(515, 212); c.stroke(); // visor glint
65
+ // ear disc
66
+ c.fillStyle = rg(330, 270, 4, 52, [[0, 240], [0.5, 150], [1, 40]]); c.beginPath(); c.arc(330, 275, 48, 0, 7); c.fill();
67
+ c.strokeStyle = 'rgb(30,30,30)'; c.lineWidth = 5; c.beginPath(); c.arc(330, 275, 30, 0, 7); c.stroke();
68
+ c.fillStyle = 'rgb(20,20,20)'; c.beginPath(); c.arc(330, 275, 10, 0, 7); c.fill();
69
+ // antenna bolts
70
+ c.fillStyle = lg(300, 100, 330, 100, [[0, 60], [1, 220]]); c.fillRect(300, 92, 18, 52);
71
+ c.fillStyle = rg(309, 88, 2, 18, [[0, 250], [1, 50]]); c.beginPath(); c.arc(309, 88, 16, 0, 7); c.fill();
72
+ // mouth grille
73
+ c.fillStyle = 'rgb(25,25,25)'; c.fillRect(440, 300, 70, 46);
74
+ c.fillStyle = 'rgb(170,170,170)'; for (let i = 0; i < 4; i++) c.fillRect(444, 304 + i * 11, 62, 4);
75
+ // megaphone cone (from mouth to upper right)
76
+ const mx0 = 500, my0 = 322, mx1 = 690, my1 = 228, a = Math.atan2(my1 - my0, mx1 - mx0), nx = -Math.sin(a), ny = Math.cos(a);
77
+ const r0 = 24, r1 = 92;
78
+ c.fillStyle = lg(mx0 + nx * r1, my0 + ny * r1, mx0 - nx * r1, my0 - ny * r1, [[0, 50], [0.35, 140], [0.62, 245], [0.8, 190], [1, 80]]);
79
+ c.beginPath(); c.moveTo(mx0 + nx * r0, my0 + ny * r0); c.lineTo(mx1 + nx * r1, my1 + ny * r1); c.lineTo(mx1 - nx * r1, my1 - ny * r1); c.lineTo(mx0 - nx * r0, my0 - ny * r0); c.closePath(); c.fill();
80
+ // bell rim
81
+ c.save(); c.translate(mx1, my1); c.rotate(a);
82
+ c.fillStyle = 'rgb(35,35,35)'; c.beginPath(); c.ellipse(0, 0, 26, r1 + 4, 0, 0, 7); c.fill();
83
+ c.fillStyle = rg(6, 0, 2, 70, [[0, 30], [1, 110]]); c.beginPath(); c.ellipse(4, 0, 18, r1 - 6, 0, 0, 7); c.fill();
84
+ c.restore();
85
+ // handle + raised arm
86
+ c.fillStyle = lg(420, 360, 520, 460, [[0, 80], [0.5, 210], [1, 70]]);
87
+ c.beginPath(); c.moveTo(560, 300); c.lineTo(588, 290); c.lineTo(560, 430); c.lineTo(520, 470); c.lineTo(470, 470); c.lineTo(530, 400); c.closePath(); c.fill();
88
+ c.fillStyle = 'rgb(40,40,40)'; c.beginPath(); c.arc(560, 330, 16, 0, 7); c.fill();
89
+ return { mx1, my1, a };
90
+ };
91
+
92
+ // cut out + halftone-screen a grayscale "photograph" (drawn by fn) once; returns photo, dot layer and cut-out rim
93
+ const screenPhoto = (fn, W, HH, seed = 19, S = 6.2, gam = 1) => {
94
+ const PH = H.layer(W, HH), pc = PH.getContext('2d');
95
+ fn(pc);
96
+ const pdat = pc.getImageData(0, 0, W, HH).data;
97
+ const HT = H.layer(W, HH), hc = HT.getContext('2d');
98
+ const th = Math.PI / 4, cs = Math.cos(th), sn = Math.sin(th);
99
+ hc.fillStyle = BLACK; hc.beginPath();
100
+ const ext = 1100;
101
+ for (let v = -ext / S; v < ext / S; v++) for (let u = -ext / S; u < ext / S * 1.6; u++) {
102
+ const x = (u * cs - v * sn) * S + 120, y = (u * sn + v * cs) * S - 400;
103
+ if (x < 0 || y < 0 || x >= W || y >= HH) continue;
104
+ const k = ((y | 0) * W + (x | 0)) * 4;
105
+ const al = pdat[k + 3] / 255; if (al < 0.5) continue;
106
+ const lum = gam === 1 ? pdat[k] / 255 : Math.pow(pdat[k] / 255, gam); // gam < 1 opens the midtones for small pastes
107
+ const dk = Math.pow(1 - lum, 0.9);
108
+ const rad = S * 0.62 * Math.sqrt(dk) + 0.35;
109
+ hc.moveTo(x + rad, y); hc.arc(x, y, rad, 0, Math.PI * 2);
110
+ }
111
+ hc.fill();
112
+ const SIL = H.layer(W, HH), sc = SIL.getContext('2d');
113
+ sc.drawImage(PH, 0, 0); sc.globalCompositeOperation = 'source-in'; sc.fillStyle = '#efe5cc'; sc.fillRect(0, 0, W, HH);
114
+ inkify(HT, seed, 0.18, 0.004);
115
+ return { PH, HT, SIL };
116
+ };
117
+
118
+ // ===================== EXPLAINER: "How does AI learn?" =====================
119
+ // Agitprop sequence: Lissitzky's red wedge drives across the sheet between scenes, slogans slam on slanted
120
+ // bars, arrows are heavy bars with wedge heads, numbers are stencil-tall Anton, photos are halftone cut-outs.
121
+ const EXC = {};
122
+ const exPaperC = (W, HH) => EXC.paper || (EXC.paper = (() => {
123
+ const cv = H.layer(W, HH), x = cv.getContext('2d'), img = x.createImageData(W, HH), pd = img.data;
124
+ const lo = CF('lo', W, HH, 41, 22, 4), fib = CF('fib', W, HH, 42, 2, 2, 4, 0.5), r0 = H.rng(5), base = H.hex(CREAM), tan = H.hex('#b99a62');
125
+ for (let y = 0, p = 0; y < HH; y++) for (let xx = 0; xx < W; xx++, p++) {
126
+ const ex = Math.min(xx, W - xx) / W, ey = Math.min(y, HH - y) / HH;
127
+ const edge = Math.pow(1 - H.clamp(Math.min(ex * 4, ey * 3) + lo[p] * 0.2, 0, 1), 3), k = 0.4 * edge + 0.16 * Math.max(0, lo[p]);
128
+ const s = 1 + fib[p] * 0.04 + (r0() - 0.5) * 0.05, i = p * 4;
129
+ pd[i] = (base[0] + (tan[0] - base[0]) * k) * s; pd[i + 1] = (base[1] + (tan[1] - base[1]) * k) * s; pd[i + 2] = (base[2] + (tan[2] - base[2]) * k) * s; pd[i + 3] = 255;
130
+ }
131
+ x.putImageData(img, 0, 0); return cv;
132
+ })());
133
+ const exInk = (layer, seed, mottle, speck = 0) => { // cached-noise version of inkify (mottle + pinhole voids)
134
+ const c = layer.getContext('2d'), w = layer.width, h = layer.height, img = c.getImageData(0, 0, w, h), d = img.data;
135
+ const n1 = CF('ei1', w, h, seed, 4, 3), n2 = CF('ei2', w, h, seed + 9, 36, 3);
136
+ const rnd = CACHE['rnd' + w] || (CACHE['rnd' + w] = (() => { const r = H.rng(seed + 3), a = new Float32Array(w * h); for (let i = 0; i < a.length; i++) a[i] = r(); return a; })());
137
+ for (let i = 0, p = 0; i < d.length; i += 4, p++) {
138
+ if (!d[i + 3]) continue;
139
+ const m = n2[p] > 0 ? n2[p] : 0;
140
+ let a = 1 - mottle * Math.max(0, n1[p] * 1.7 + n2[p] * 1.1);
141
+ if (rnd[p] < speck * (0.3 + m * 4)) a *= 0.2;
142
+ d[i + 3] *= a < 0 ? 0 : a > 1 ? 1 : a;
143
+ }
144
+ c.putImageData(img, 0, 0);
145
+ };
146
+ // grayscale studio "photos" of a cat and a dog, for the halftone screen
147
+ const drawAnimal = (kind) => (c) => {
148
+ const g = (x0, y0, x1, y1, st) => { const gr = c.createLinearGradient(x0, y0, x1, y1); st.forEach(([o, v]) => gr.addColorStop(o, `rgb(${v},${v},${v})`)); return gr; };
149
+ c.save(); c.translate(300, 300);
150
+ if (kind === 'cat') {
151
+ c.fillStyle = g(-120, -100, 120, 140, [[0, 60], [0.5, 150], [1, 230]]);
152
+ c.beginPath(); c.ellipse(0, 70, 95, 110, 0, 0, 7); c.fill();
153
+ c.beginPath(); c.arc(0, -60, 72, 0, 7); c.fill();
154
+ c.beginPath(); c.moveTo(-66, -80); c.lineTo(-58, -160); c.lineTo(-14, -118); c.closePath(); c.fill();
155
+ c.beginPath(); c.moveTo(66, -80); c.lineTo(58, -160); c.lineTo(14, -118); c.closePath(); c.fill();
156
+ c.fillStyle = 'rgb(240,240,240)'; c.beginPath(); c.ellipse(-26, -64, 14, 10, 0, 0, 7); c.ellipse(26, -64, 14, 10, 0, 0, 7); c.fill();
157
+ c.fillStyle = 'rgb(10,10,10)'; c.beginPath(); c.ellipse(-26, -64, 4, 9, 0, 0, 7); c.ellipse(26, -64, 4, 9, 0, 0, 7); c.fill();
158
+ c.beginPath(); c.moveTo(-8, -38); c.lineTo(8, -38); c.lineTo(0, -28); c.closePath(); c.fill();
159
+ c.strokeStyle = 'rgb(40,40,40)'; c.lineWidth = 12; c.beginPath(); c.moveTo(80, 150); c.quadraticCurveTo(170, 140, 140, 40); c.stroke();
160
+ } else {
161
+ c.fillStyle = g(-120, -100, 120, 140, [[0, 230], [0.5, 160], [1, 70]]);
162
+ c.beginPath(); c.ellipse(0, 80, 100, 100, 0, 0, 7); c.fill();
163
+ c.beginPath(); c.arc(0, -55, 75, 0, 7); c.fill();
164
+ c.fillStyle = 'rgb(50,50,50)'; c.beginPath(); c.ellipse(-78, -40, 26, 62, 0.35, 0, 7); c.ellipse(78, -40, 26, 62, -0.35, 0, 7); c.fill();
165
+ c.fillStyle = 'rgb(225,225,225)'; c.beginPath(); c.ellipse(0, -18, 40, 30, 0, 0, 7); c.fill();
166
+ c.fillStyle = 'rgb(10,10,10)'; c.beginPath(); c.arc(-28, -70, 9, 0, 7); c.arc(28, -70, 9, 0, 7); c.fill();
167
+ c.beginPath(); c.ellipse(0, -30, 15, 10, 0, 0, 7); c.fill();
168
+ c.fillStyle = 'rgb(120,40,40)'; c.beginPath(); c.ellipse(0, 4, 12, 16, 0, 0, Math.PI); c.fill();
169
+ }
170
+ c.restore();
171
+ };
172
+ const exPhotos = (W, HH) => EXC.ph || (EXC.ph = {
173
+ robot: PHOTO || (PHOTO = screenPhoto((pc) => drawPhoto(pc, 0), W, HH)),
174
+ cat: screenPhoto(drawAnimal('cat'), 600, 600, 23), dog: screenPhoto(drawAnimal('dog'), 600, 600, 29),
175
+ catS: screenPhoto(drawAnimal('cat'), 600, 600, 23, 11, 0.7), dogS: screenPhoto(drawAnimal('dog'), 600, 600, 29, 11, 0.7),
176
+ });
177
+ // paste a screened photo: cream cut-out rim, faint tone, dots (all multiplied on paper)
178
+ const paste = (ctx, P, x, y, s, rot = 0) => {
179
+ ctx.save(); ctx.translate(x, y); ctx.rotate(rot); ctx.scale(s, s);
180
+ const ro = Math.max(1, 3.2 / Math.max(0.05, s) / 4);
181
+ for (const [dx, dy] of [[-4, 0], [4, 0], [0, -4], [0, 4], [-3, -3], [3, 3], [3, -3], [-3, 3]]) ctx.drawImage(P.SIL, dx * ro, dy * ro);
182
+ ctx.drawImage(P.SIL, 0, 0);
183
+ ctx.globalAlpha = 0.18; ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(P.PH, 0, 0);
184
+ ctx.globalAlpha = 1; ctx.drawImage(P.HT, 1.5, -1);
185
+ ctx.restore();
186
+ };
187
+ // heavy bar arrow with a wedge head
188
+ const barArrow = (c, x0, y0, x1, y1, p, w, col) => {
189
+ if (p <= 0) return; const a = Math.atan2(y1 - y0, x1 - x0), l = Math.hypot(x1 - x0, y1 - y0) * p;
190
+ c.save(); c.translate(x0, y0); c.rotate(a); c.fillStyle = col; c.fillRect(0, -w / 2, Math.max(0, l - w * 2.2), w);
191
+ c.beginPath(); c.moveTo(l, 0); c.lineTo(l - w * 2.6, -w * 1.4); c.lineTo(l - w * 2.6, w * 1.4); c.closePath(); c.fill(); c.restore();
192
+ };
193
+ const slab = (c, txt, x, y, ang, font, fg, bg, padX = 14, padY = 10, p = 1) => { // slanted bar with lettering
194
+ if (p <= 0) return; c.save(); c.translate(x, y); c.rotate(ang); c.font = font; c.letterSpacing = '3px';
195
+ const m = c.measureText(txt), w = m.width + padX * 2, h = (m.actualBoundingBoxAscent + m.actualBoundingBoxDescent) + padY * 2;
196
+ if (bg) { c.fillStyle = bg; c.fillRect(-padX, -m.actualBoundingBoxAscent - padY, w * p, h); }
197
+ if (p >= 1) { c.fillStyle = fg; c.fillText(txt, 0, 0); }
198
+ c.restore(); c.letterSpacing = '0px';
199
+ };
200
+ const cog = (c, x, y, r, teeth, a) => {
201
+ c.save(); c.translate(x, y); c.rotate(a); c.beginPath();
202
+ for (let i = 0; i < teeth * 2; i++) { const rr = i % 2 ? r : r * 1.22, a0 = (i / (teeth * 2)) * Math.PI * 2; c.lineTo(Math.cos(a0 - 0.12) * rr, Math.sin(a0 - 0.12) * rr); c.lineTo(Math.cos(a0 + 0.12) * rr, Math.sin(a0 + 0.12) * rr); }
203
+ c.closePath(); c.arc(0, 0, r * 0.38, 0, 7, true); c.fill(); c.restore();
204
+ };
205
+
206
+ // worn-print finish: fine cream scratches through the ink (same seed in the still and the explainer)
207
+ const scratches = (ctx, W, HH) => {
208
+ const rs = H.rng(88);
209
+ ctx.save(); ctx.strokeStyle = 'rgba(250,244,228,0.35)'; ctx.lineWidth = 1;
210
+ for (let i = 0; i < 40; i++) { const x = rs.range(0, W), y = rs.range(0, HH), l = rs.range(4, 30), a2 = rs.range(0, 3); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + Math.cos(a2) * l, y + Math.sin(a2) * l); ctx.stroke(); }
211
+ ctx.restore();
212
+ };
213
+
214
+ const explainConstructivist = (ctx, W, HH, t) => {
215
+ const E = H.ease, cl = H.clamp, S = (a, b) => cl((t - a) / (b - a), 0, 1);
216
+ const tq = Math.floor(t * 15) / 15; // agitprop staccato: punches land on twos
217
+ const Sq = (a, b) => cl((tq - a) / (b - a), 0, 1);
218
+ const WEDGES = [5.6, 10.0, 12.55], swapAt = (w) => w + 0.5;
219
+ const scene = t < swapAt(WEDGES[0]) ? 1 : t < swapAt(WEDGES[1]) ? 2 : t < swapAt(WEDGES[2]) ? 3 : 4;
220
+ const PH = exPhotos(W, HH);
221
+ ctx.drawImage(exPaperC(W, HH), 0, 0);
222
+ const L = H.layer(W, HH), c = L.getContext('2d'); c.textBaseline = 'alphabetic';
223
+ const pastes = []; // photos to paste after the flat plates
224
+ const R0 = { x: 14, y: 66, s: 0.85 }, bell = [14 + 690 * 0.85, 66 + 228 * 0.85];
225
+
226
+ // ---------- hook (scene 1 opening) ----------
227
+ const hp = E.inOut(S(1.6, 2.3));
228
+ if (hp < 1) {
229
+ c.save(); c.globalAlpha = 1;
230
+ const disc = E.out(Sq(0, 0.4)); c.fillStyle = RED; c.beginPath(); c.arc(980 - (1 - disc) * 600, 300, 230, 0, 7); c.fill();
231
+ const beams = [[-2.6, 0.06], [-2.3, 0.1], [-1.95, 0.04], [-1.7, 0.12]];
232
+ beams.forEach(([a, w], i) => { const p = Sq(0.2 + i * 0.08, 0.3 + i * 0.08); if (p <= 0) return; c.fillStyle = i % 2 ? RED : BLACK; c.beginPath(); c.moveTo(980, 300); c.lineTo(980 + Math.cos(a - w) * 1400 * p, 300 + Math.sin(a - w) * 1400 * p); c.lineTo(980 + Math.cos(a + w) * 1400 * p, 300 + Math.sin(a + w) * 1400 * p); c.closePath(); c.fill(); });
233
+ c.restore();
234
+ }
235
+ // the slogan collapses into a slanted corner banner
236
+ {
237
+ const x = H.lerp(110, 30, hp), y = H.lerp(430, 52, hp), s = H.lerp(1, 0.3, hp), ang = H.lerp(-0.2, -0.06, hp);
238
+ c.save(); c.translate(x, y); c.scale(s, s);
239
+ slab(c, 'HOW DOES', 0, 0, ang, `400 ${H.lerp(64, 96, hp).toFixed(2)}px "Russo One"`, CREAM, BLACK, 20, 16, Sq(0.35, 0.5));
240
+ if (tq >= 0.6) { c.save(); c.rotate(ang); const k = cl((tq - 0.6) / 0.2, 0, 1), sc = 1 + 0.6 * (1 - k); c.translate(0, 150); c.scale(sc, sc); c.fillStyle = RED; c.font = '400 170px "Anton"'; c.letterSpacing = '2px'; c.fillText('AI LEARN?', 0, 0); c.letterSpacing = '0px'; c.restore(); }
241
+ c.restore();
242
+ }
243
+
244
+ // ---------- scene 1: data poured into the megaphone ----------
245
+ if (scene === 1 && t > 2.0) {
246
+ const k = E.out(S(2.0, 2.6));
247
+ pastes.push(['robot', R0.x - (1 - k) * 500, R0.y + (1 - k) * 200, R0.s]);
248
+ // conveyor: heavy diagonal bar from upper right down to the bell
249
+ const cx0 = 1300, cy0 = 40, cx1 = bell[0] + 60, cy1 = bell[1] - 20;
250
+ c.save(); c.fillStyle = BLACK; c.translate(cx1, cy1); c.rotate(Math.atan2(cy0 - cy1, cx0 - cx1)); c.fillRect(0, 70, Math.hypot(cx0 - cx1, cy0 - cy1) * E.out(S(2.4, 2.8)), 26); c.restore();
251
+ // examples ride the bar into the bell (photos)
252
+ for (let i = 0; i < 9; i++) {
253
+ const u = (t - 2.7 - i * 0.42) * 0.55; if (u < 0 || u > 1) continue;
254
+ const kind = H.rng(i * 11 + 3)() < 0.5 ? 'cat' : 'dog', sh = 1 - E.in(cl((u - 0.75) / 0.25, 0, 1));
255
+ const x = H.lerp(cx0 - 60, cx1, u), y = H.lerp(cy0 + 40, cy1 + 20, u) - 80, s = 0.33 * sh;
256
+ pastes.push([kind + 'S', x - 300 * s, y - 300 * s, s, -0.25]);
257
+ if (sh > 0.6) slab(c, kind.toUpperCase(), x - 44, y + 72 * sh, -0.25, '700 28px "Oswald"', CREAM, RED, 8, 5); // label stamped on the card
258
+ }
259
+ // red arrow into the bell + label
260
+ barArrow(c, 1000, 420, bell[0] + 30, bell[1] + 40, E.out(Sq(3.0, 3.3)), 22, RED);
261
+ slab(c, 'TRAINING DATA', 840, 505, -0.24, '700 30px "Oswald"', CREAM, BLACK, 10, 6, Sq(3.2, 3.35));
262
+ // counter: stencil numerals in a slanted black box
263
+ const cp = S(3.0, 5.4), n = cp < 1 ? Math.pow(cp, 3) * 1e6 : 1e6;
264
+ c.save(); c.translate(640, 640); c.rotate(-0.12);
265
+ c.fillStyle = BLACK; c.fillRect(-20, -100, 600 * E.out(Sq(2.8, 3.0)), 118);
266
+ if (tq >= 3.0) { c.fillStyle = CREAM; c.font = '400 100px "Anton"'; c.letterSpacing = '4px'; c.fillText(Math.round(n).toLocaleString('en-US'), 0, 0); c.letterSpacing = '0px'; c.fillStyle = RED; c.font = '400 30px "Russo One"'; c.fillText('EXAMPLES', 440, 40); }
267
+ c.restore();
268
+ }
269
+
270
+ // ---------- scene 2: guess -> check -> adjust ----------
271
+ if (scene === 2) {
272
+ const t0 = swapAt(WEDGES[0]);
273
+ pastes.push(['robot', R0.x, R0.y, R0.s]);
274
+ // new picture pinned top right
275
+ pastes.push(['catS', 760 - 300 * 0.28, 105 - 300 * 0.28 - (1 - E.out(Sq(t0, t0 + 0.3))) * 300, 0.28, 0.08]);
276
+ slab(c, 'NEW PICTURE', 470, 74, 0.08, '700 28px "Oswald"', CREAM, BLACK, 8, 5, Sq(t0 + 0.3, t0 + 0.45));
277
+ // the guess is shouted through the megaphone
278
+ const shout = (txt, ts, col) => {
279
+ const beams = [[-0.3, 0.1], [-0.08, 0.08], [0.16, 0.06]];
280
+ beams.forEach(([a, w], i) => { const p = cl(Math.floor((t - ts - i * 0.07) * 15) / 2, 0, 1); if (p <= 0) return; c.fillStyle = i % 2 ? BLACK : RED; c.beginPath(); c.moveTo(bell[0] + 20, bell[1]); c.lineTo(bell[0] + Math.cos(a - w) * 700 * p, bell[1] + Math.sin(a - w) * 700 * p); c.lineTo(bell[0] + Math.cos(a + w) * 700 * p, bell[1] + Math.sin(a + w) * 700 * p); c.closePath(); c.fill(); });
281
+ if (t > ts + 0.25) { c.save(); c.translate(720, 300); c.rotate(-0.2); const k = cl(Math.floor((t - ts - 0.25) * 15) / 3, 0, 1), sc = 1 + 0.5 * (1 - k); c.scale(sc, sc); c.font = '400 120px "Anton"'; c.fillStyle = BLACK; c.fillText(txt, 4, 4); c.fillStyle = col; c.fillText(txt, 0, 0); c.restore(); }
282
+ };
283
+ if (t < 9.3) {
284
+ shout('DOG?', 6.75, CREAM);
285
+ const xp = Sq(7.45, 7.6); // CHECK: a red cross is stamped over it
286
+ if (xp > 0) { c.save(); c.translate(1040, 220); c.fillStyle = RED; c.rotate(0.785); c.fillRect(-70 * xp, -14, 140 * xp, 28); c.fillRect(-14, -70 * xp, 28, 140 * xp); c.restore(); }
287
+ const rp = S(7.55, 7.85); if (rp > 0) { c.strokeStyle = BLACK; c.lineWidth = 12; c.beginPath(); c.ellipse(900, 250, 240, 120, -0.2, -2, -2 + Math.PI * 2 * rp); c.stroke(); }
288
+ if (tq > 7.7) slab(c, 'WRONG! IT IS A CAT', 700, 440, -0.2, '400 28px "Russo One"', CREAM, RED, 12, 8);
289
+ } else {
290
+ shout('CAT!', 9.35, RED);
291
+ const cp = Sq(9.7, 9.85); if (cp > 0) { c.save(); c.translate(1010, 210); c.rotate(-0.2); c.fillStyle = BLACK; c.fillRect(-16, -16, 30, 70 * cp); c.fillRect(-16, 38, 110 * cp, 30); c.restore(); }
292
+ }
293
+ // ADJUST: error bar flies back to the robot's gears; the cogs turn
294
+ barArrow(c, 760, 470, 390, 560, E.inOut(S(7.9, 8.35)), 20, RED);
295
+ if (t > 8.35 && t < 9.3) slab(c, 'ERROR', 545, 470, -0.24, '700 30px "Oswald"', CREAM, RED, 8, 5);
296
+ const turn = E.inOut(S(8.35, 9.2)) * 2.4 + (t > 9.3 ? (t - 9.3) * 0.8 : 0);
297
+ c.fillStyle = BLACK; cog(c, 300, 560, 40 * E.back(Sq(t0 + 0.2, t0 + 0.4)), 10, turn); cog(c, 368, 512, 26 * E.back(Sq(t0 + 0.3, t0 + 0.5)), 8, -turn * 1.5);
298
+ c.fillStyle = RED; cog(c, 248, 500, 20 * E.back(Sq(t0 + 0.35, t0 + 0.55)), 7, -turn * 2);
299
+ // the loop: three slanted bars wired into a circuit
300
+ const steps = [['GUESS', 760, 610], ['CHECK', 960, 560], ['ADJUST', 1110, 660]], act = t < 7.45 ? 0 : t < 8.3 ? 1 : t < 9.3 ? 2 : Math.floor((t - 9.3) * 3) % 3;
301
+ steps.forEach(([s, x, y], i) => slab(c, s, x, y, -0.2, '400 30px "Russo One"', CREAM, i === act && t > 6.7 ? RED : BLACK, 12, 8, Sq(t0 + 0.2 + i * 0.12, t0 + 0.32 + i * 0.12)));
302
+ barArrow(c, 890, 590, 950, 575, Sq(t0 + 0.6, t0 + 0.7), 10, BLACK);
303
+ barArrow(c, 1100, 575, 1150, 625, Sq(t0 + 0.7, t0 + 0.8), 10, BLACK);
304
+ barArrow(c, 1090, 680, 900, 640, Sq(t0 + 0.8, t0 + 0.9), 10, BLACK);
305
+ }
306
+
307
+ // ---------- scene 3: progress ----------
308
+ if (scene === 3) {
309
+ const t0 = swapAt(WEDGES[1]), prog = E.inOut(S(t0 + 0.2, 12.0));
310
+ // emphasis: beams burst behind the final number
311
+ const burst = Sq(12.05, 12.25);
312
+ if (burst > 0) { for (let i = 0; i < 12; i++) { const a = i / 12 * Math.PI * 2 + 0.1; c.fillStyle = i % 2 ? RED : BLACK; c.beginPath(); c.moveTo(1010, 300); c.lineTo(1010 + Math.cos(a - 0.08) * 460 * burst, 300 + Math.sin(a - 0.08) * 460 * burst); c.lineTo(1010 + Math.cos(a + 0.08) * 460 * burst, 300 + Math.sin(a + 0.08) * 460 * burst); c.closePath(); c.fill(); } }
313
+ c.save(); c.translate(90, 640); c.rotate(-0.12);
314
+ c.fillStyle = BLACK; c.fillRect(0, 0, 700 * E.out(Sq(t0, t0 + 0.2)), 14);
315
+ for (let i = 0; i < 9; i++) { const acc = 61 + 36 * (1 - Math.pow(1 - i / 8, 2)), bp = E.out(cl(prog * 9 - i, 0, 1)), h = (acc - 45) * 7.5 * bp; c.fillStyle = i === 8 ? RED : i % 2 ? BLACK : RED; c.fillRect(20 + i * 76, -h, 56, h); }
316
+ slab(c, 'TRAINING ROUNDS →', 330, 62, 0, '700 30px "Oswald"', CREAM, BLACK, 8, 5, Sq(t0 + 0.2, t0 + 0.35));
317
+ c.restore();
318
+ c.save(); c.translate(820, 380); c.rotate(-0.12);
319
+ c.fillStyle = BLACK; c.fillRect(-20, -200, 420, 240);
320
+ c.fillStyle = burst > 0 ? RED : CREAM; c.font = '400 210px "Anton"'; c.fillText(Math.round(H.lerp(61, 97, prog)) + '%', 0, 0);
321
+ c.restore();
322
+ slab(c, 'ACCURACY', 820, 140, -0.12, '400 40px "Russo One"', CREAM, RED, 12, 8, Sq(t0 + 0.1, t0 + 0.25));
323
+ slab(c, '61% → 97%', 860, 505, -0.12, '700 36px "Oswald"', CREAM, RED, 12, 8, Sq(t0 + 0.4, t0 + 0.5));
324
+ }
325
+
326
+ // ---------- scene 4: takeaway slogan ----------
327
+ if (scene === 4) {
328
+ const t0 = swapAt(WEDGES[2]);
329
+ c.fillStyle = RED; c.beginPath(); c.arc(1080, 560, 260, 0, 7); c.fill();
330
+ slab(c, 'MORE EXAMPLES', 90, 260, -0.15, '400 76px "Russo One"', CREAM, BLACK, 22, 16, Sq(t0, t0 + 0.15));
331
+ barArrow(c, 200, 320, 300, 455, E.out(Sq(t0 + 0.2, t0 + 0.4)), 28, RED);
332
+ if (tq >= t0 + 0.35) { c.save(); c.translate(320, 610); c.rotate(-0.15); const k = cl((tq - t0 - 0.35) / 0.2, 0, 1), sc = 1 + 0.5 * (1 - k); c.scale(sc, sc); c.font = '400 132px "Anton"'; c.fillStyle = RED; c.fillText('BETTER GUESSES', 7, 7); c.fillStyle = BLACK; c.fillText('BETTER GUESSES', 0, 0); c.restore(); }
333
+ }
334
+
335
+ // ---------- Lissitzky's red wedge drives across between scenes ----------
336
+ const WL = H.layer(W, HH), wc = WL.getContext('2d'); let wedgeOn = false;
337
+ for (const w of WEDGES) {
338
+ const u = S(w, w + 0.95); if (u <= 0 || u >= 1) continue; wedgeOn = true; const c = wc;
339
+ const tx = H.lerp(-100, 3900, E.inOut(u));
340
+ c.fillStyle = RED; c.beginPath(); c.moveTo(tx, HH / 2); c.lineTo(tx - 2400, -1400); c.lineTo(tx - 2400, HH + 1400); c.closePath(); c.fill();
341
+ c.fillStyle = BLACK; c.beginPath(); c.moveTo(tx + 60, HH / 2 + 30); c.lineTo(tx - 2400, -1360); c.lineTo(tx - 2400, -1330); c.closePath(); c.fill();
342
+ }
343
+
344
+ // ---------- print: plates multiply, then pasted photos, then grain ----------
345
+ exInk(L, 7, 0.26, 0.006);
346
+ const pasteAll = () => pastes.forEach(([k, x, y, s, rot]) => paste(ctx, PH[k], x, y, s, rot || 0));
347
+ // photos sit under the slogans/wedge: paste first, then plates on top (multiply keeps dots visible under ink)
348
+ pasteAll();
349
+ ctx.save(); ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(L, 0, 0); ctx.restore();
350
+ if (wedgeOn) { exInk(WL, 7, 0.12, 0.004); ctx.drawImage(WL, 0, 0); }
351
+ scratches(ctx, W, HH);
352
+ H.grain(ctx, W, HH, 0.08, 4 + Math.floor(t * 12), true);
353
+ H.vignette(ctx, W, HH, 0.25, '50,30,10');
354
+ };
355
+
356
+ registerStyle({
357
+ id: 'constructivist',
358
+ name: 'Constructivist Propaganda',
359
+ category: 'Art & Design Movements',
360
+ description: 'A 1920s Rodchenko / El Lissitzky agitprop poster: red, black and cream only, a halftone photomontage robot bellowing through a megaphone, beams radiating from its voice, a red disc and hard diagonal bars, and heavy condensed lettering set on the slant — litho ink mottle on cheap paper.',
361
+ motion: 'Agitprop staccato: the red disc rolls in, the halftone robot slides up the diagonal, beams punch out of the megaphone two frames at a time, the slogan bars shoot across and the words slam down on the beat; in the hold the beam fan pulses with each shout.',
362
+ fonts: ['400 150px "Anton"', '400 40px "Russo One"', '700 18px "Oswald"'],
363
+ frameT: 4.6,
364
+ duration: 5,
365
+ readsPixels: true,
366
+ explainerDuration: 14.5,
367
+ explainerNote: "Agitprop sequence: Lissitzky's red wedge drives across the sheet between scenes, slogans and labels slam onto slanted bars on twos, arrows are heavy bars with wedge heads, the counter is stencil-tall Anton, and the key moments get a stamped red cross or a burst of beams.",
368
+ explainer(ctx, W, HH, t) { explainConstructivist(ctx, W, HH, t); },
369
+ // Sound: a print works on agitprop duty. Slogans and slabs slam down like a press, the red wedge is a heavy iron
370
+ // ram driving through with a low roar, the megaphone shouts are short brass blasts through a horn, beams crack out,
371
+ // the counter is stencil-stamped and the cogs ratchet. Times copied from explainConstructivist(); its slabs land
372
+ // "on twos" (t quantised to 1/15 s), so q(b) gives the first frame a slab or slogan is actually complete.
373
+ explainerSound(k) {
374
+ const D = 14.5, E = H.ease, cl = H.clamp;
375
+ const S = (t, a, b) => cl((t - a) / (b - a), 0, 1);
376
+ const q = (b) => Math.ceil(b * 15 - 1e-6) / 15;
377
+ const pan = (px) => cl((px / 1280) * 2 - 1, -1, 1) * 0.7;
378
+ const when = (a, b, ease, u) => { for (let i = 0; i <= 400; i++) if (ease(i / 400) >= u - 1e-6) return a + ((b - a) * i) / 400; return b; };
379
+ const WEDGES = [5.6, 10.0, 12.55], swapAt = (w) => w + 0.5;
380
+ // a slab or slogan slammed down: paper slap with a press thump (big = louder, heavier)
381
+ const slam = (t, g, p, minor) => { k.play('slap', t, { gain: 0.55 * g, pan: p, minor }); k.play('thud', t, { f: 110, gain: 0.5 * g, pan: p, minor }); };
382
+ // megaphone shout: detuned saws honking through a horn-shaped band
383
+ const horn = (t, notes, dur, g, p, minor) => notes.forEach((n, i) => { for (const det of [-9, 9])
384
+ k.tone(t, { f: n, type: 'sawtooth', detune: det, dur, gain: 0.035 * g, a: 0.02, hold: dur * 0.6, filter: 'bandpass', f2: [[0, 700], [0.05, 1500], [dur, 1000]], q: 0.9, pan: p + (i - (notes.length - 1) / 2) * 0.15, verb: 0.2, minor }); });
385
+ // a beam cracking out: short punchy noise burst
386
+ const beam = (t, g, p) => k.noise(t, { dur: 0.09, filter: 'bandpass', f: [[0, 600], [0.09, 1800]], q: 0.8, gain: 0.22 * g, a: 0.004, pan: p, minor: true });
387
+ // stencil-stamped counter digit
388
+ const numb = (t, g, p) => k.withMinor(() => { k.noise(t, { dur: 0.03, filter: 'bandpass', f: 1500, q: 1.2, gain: 0.32 * g, a: 0.001, pan: p }); k.tone(t, { f: 130, dur: 0.05, gain: 0.12 * g, pan: p }); });
389
+ // the red wedge: an iron ram driving left to right (inOut over 0.95 s, fastest at the middle)
390
+ const ram = (w) => {
391
+ k.noise(w, { dur: 0.95, color: 'brown', filter: 'lowpass', f: [[0, 150], [0.47, 900], [0.95, 200]], gain: 0.45, a: 0.4, pan: -0.3 });
392
+ k.noise(w + 0.2, { dur: 0.75, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.27, 1300], [0.75, 400]], q: 0.8, gain: 0.16, a: 0.25, pan: 0.3 });
393
+ k.tone(w, { f: [[0, 45], [0.47, 72], [0.95, 40]], type: 'sawtooth', dur: 0.95, gain: 0.1, a: 0.4, filter: 'lowpass', f2: 300 });
394
+ };
395
+
396
+ k.bed('hum', 0, D, { gain: 0.35 }); // the print works
397
+ k.bed('room', 0, D, { gain: 0.4 });
398
+
399
+ // ---- hook: disc drives in (0-0.4), four beams crack out, HOW DOES slab, AI LEARN? slams (0.6 -> 0.8)
400
+ k.noise(0.0, { dur: 0.4, color: 'brown', filter: 'lowpass', f: [[0, 200], [0.4, 700]], gain: 0.3, a: 0.3, pan: 0.2 });
401
+ k.play('thud', q(0.4), { f: 90, gain: 0.6, pan: pan(980) });
402
+ [0, 1, 2, 3].forEach((i) => beam(q(0.2 + i * 0.08) + 1 / 15, 1, pan(700 - i * 100)));
403
+ slam(q(0.5), 0.8, -0.4, true);
404
+ k.play('stamp', 0.8, { gain: 0.65, pan: -0.2 }); k.play('impact', 0.8, { gain: 0.3, pan: -0.2 });
405
+ // slogan collapses into the corner banner (1.6-2.3): a soft sheet slide
406
+ k.noise(1.6, { dur: 0.7, color: 'pink', filter: 'bandpass', f: [[0, 400], [0.7, 900]], q: 0.7, gain: 0.08, a: 0.3, pan: -0.5, minor: true });
407
+
408
+ // ---- scene 1: DATA poured into the megaphone
409
+ k.noise(2.0, { dur: 0.5, color: 'brown', filter: 'lowpass', f: [[0, 250], [0.5, 600]], gain: 0.25, a: 0.15, pan: -0.5, minor: true }); // robot photo slides in
410
+ k.play('thud', when(2.0, 2.6, E.out, 0.95), { f: 100, gain: 0.4, pan: -0.5, minor: true });
411
+ k.noise(2.4, { dur: 0.4, color: 'brown', filter: 'bandpass', f: 350, q: 1.2, gain: 0.2, a: 0.05, pan: 0.4, minor: true }); // conveyor bar runs out
412
+ slam(3.0, 0.6, pan(900), true); // counter box set
413
+ const cards = []; for (let i = 0; 2.7 + i * 0.42 < swapAt(WEDGES[0]) - 0.1; i++) cards.push(2.7 + i * 0.42);
414
+ cards.forEach((tc) => k.play('stamp', tc, { gain: 0.3, pan: pan(1200), minor: true })); // each card arrives with its label stamped on
415
+ slam(q(3.3), 0.7, pan(820), true); // red arrow into the bell; TRAINING DATA lands one frame later on the same slam
416
+ // counter n = S(3.0,5.4)^3 * 1e6, stencil-stamped, speeding up, off the card stamps
417
+ for (let t = 3.12; t < 5.32; ) { const s = S(t, 3.0, 5.4); if (!cards.some((c) => t > c - 0.06 && t < c + 0.08)) numb(t, 0.7 + 0.4 * s, pan(900)); t += 0.16 - 0.075 * s; }
418
+ k.play('stamp', 5.4, { gain: 0.6, pan: pan(900) }); k.play('thud', 5.4, { f: 90, gain: 0.4, pan: pan(900) }); // 1,000,000
419
+
420
+ // ---- the red wedge drives across: 5.6, 10.0, 12.55
421
+ WEDGES.forEach(ram);
422
+
423
+ // ---- scene 2: GUESS / CHECK / ADJUST (t0 = 6.1)
424
+ const t0 = swapAt(WEDGES[0]);
425
+ slam(q(t0 + 0.3), 0.7, pan(680), true); // cat picture drops in
426
+ k.play('tock', q(t0 + 0.4), { f: 300, gain: 0.4, pan: pan(300), minor: true }); // cogs bolt on
427
+ slam(q(t0 + 0.45), 0.55, pan(800), true); // NEW PICTURE + CHECK slabs land on the same frame (GUESS/ADJUST ride along)
428
+ // the guess is shouted through the megaphone (beams 6.82+, DOG? slams at 7.2)
429
+ horn(6.82, ['D3', 'Ab3'], 0.38, 1, pan(800));
430
+ [0, 1, 2].forEach((i) => beam(6.75 + i * 0.07 + 1 / 15, 0.8, pan(800)));
431
+ slam(7.2, 0.9, pan(800), false);
432
+ // CHECK: red cross stamped on (7.47 -> 7.6), black ring brushed round it, WRONG slab
433
+ k.play('stamp', 7.6, { gain: 1.0, pan: pan(1040) });
434
+ horn(7.62, ['C3', 'Db3'], 0.45, 1.1, pan(1040));
435
+ k.play('stroke', 7.58, { material: 'brush', dur: 0.3, gain: 0.8, pan: pan(900), minor: true });
436
+ slam(q(7.71), 0.55, pan(800), true);
437
+ // ADJUST: the error bar drives back to the cogs (inOut 7.9-8.35), cogs ratchet round (inOut 8.35-9.2)
438
+ k.noise(7.9, { dur: 0.45, color: 'brown', filter: 'lowpass', f: [[0, 200], [0.25, 800], [0.45, 250]], gain: 0.3, a: 0.2, pan: -0.2 });
439
+ k.play('thud', 8.35, { f: 120, gain: 0.5, pan: pan(390) });
440
+ for (let i = 1, lt = 8.35; i <= 9; i++) { const t = when(8.35, 9.2, E.inOut, i / 9); if (t - lt < 0.09) continue; lt = t; k.play('tock', t, { f: 360 + (i % 2) * 70, gain: 0.45, pan: pan(300), minor: true }); k.play('click', t + 0.006, { f: 900, gain: 0.5, pan: pan(300), minor: true }); }
441
+ // the correction: CAT! shouted (9.35) on a bright major blast, slams at 9.8, check mark at 9.87
442
+ horn(9.42, ['C4', 'E4', 'G4'], 0.5, 1.1, pan(800));
443
+ [0, 1, 2].forEach((i) => beam(9.35 + i * 0.07 + 1 / 15, 0.8, pan(800)));
444
+ slam(9.8, 0.9, pan(800), false);
445
+ k.play('stamp', q(9.85), { gain: 0.5, pan: pan(1010) }); // check mark stamped
446
+
447
+ // ---- scene 3: PROGRESS (t0 = 10.5)
448
+ const t3 = swapAt(WEDGES[1]);
449
+ slam(q(t3 + 0.2), 0.5, -0.3, true); // base bar
450
+ slam(q(t3 + 0.25), 0.5, pan(820), true); // ACCURACY
451
+ // bars rise as prog = inOut(S(10.7,12.0)) passes i/9: a heavy chunk each (at least 100 ms apart), rising a little
452
+ const bars = []; for (let i = 1, lt = 0; i < 9; i++) { const t = when(t3 + 0.2, 12.0, E.inOut, i / 9); if (t - lt < 0.1) continue; lt = t; bars.push(t); k.play('thud', t, { f: 80 + i * 12, gain: 0.3, pan: pan(110 + i * 76), minor: true }); }
453
+ for (let t = t3 + 0.25, last = 61, lt = -1; t < 11.97; t += 1 / 120) {
454
+ const v = Math.round(H.lerp(61, 97, E.inOut(S(t, t3 + 0.2, 12.0))));
455
+ if (v !== last && t - lt >= 1 / 11 && !bars.some((b) => Math.abs(t - b) < 0.05)) { numb(t, 0.9, pan(1000)); last = v; lt = t; }
456
+ }
457
+ // 97%: beams burst behind the number (from 12.067), the biggest hit
458
+ const bt = q(12.05 + 1e-3);
459
+ k.play('impact', bt, { gain: 1.7, pan: pan(1010) }); k.play('thud', bt, { f: 70, gain: 0.9 });
460
+ k.play('stamp', bt, { gain: 0.9, pan: pan(1010) });
461
+ horn(bt + 0.01, ['C3', 'G3', 'C4', 'E4'], 0.7, 1.2, pan(1010));
462
+
463
+ // ---- scene 4: TAKEAWAY (t0 = 13.05): MORE EXAMPLES slab, arrow, BETTER GUESSES slams (13.4 -> 13.6)
464
+ const t4 = swapAt(WEDGES[2]);
465
+ slam(q(t4 + 0.15), 0.8, -0.4, false);
466
+ k.noise(t4 + 0.2, { dur: 0.25, color: 'brown', filter: 'lowpass', f: [[0, 250], [0.2, 700]], gain: 0.2, a: 0.1, pan: -0.3, minor: true });
467
+ k.play('stamp', 13.6, { gain: 0.6, pan: -0.1 }); k.play('thud', 13.6, { f: 80, gain: 0.35 });
468
+ horn(13.62, ['F3', 'A3', 'C4'], 0.25, 0.8, 0, true);
469
+ horn(13.9, ['C3', 'G3', 'C4', 'E4'], 0.55, 0.9, 0); // the resolve
470
+ },
471
+ draw(ctx, W, HH, t) {
472
+ const P = (d, dur = 0.5) => H.ease.out(H.clamp((t - d) / dur, 0, 1));
473
+
474
+ // ---------- paper ----------
475
+ ctx.drawImage(exPaperC(W, HH), 0, 0); // same paper maths as before, built once and cached
476
+
477
+ // ---------- flat ink plates ----------
478
+ const L = H.layer(W, HH), c = L.getContext('2d');
479
+ // megaphone geometry (needed for beams)
480
+ const mx1 = 690, my1 = 228;
481
+ // red disc behind the head
482
+ // disc rolls in from the left edge and lands
483
+ { const p = H.ease.out(H.clamp(t / 0.7, 0, 1)); c.fillStyle = RED; c.beginPath(); c.arc(360 - (1 - p) * 640, 300, 235, 0, 7); c.fill(); }
484
+ // radiating beams from the bell
485
+ const beams = [[-0.62, 0.05, BLACK], [-0.5, 0.16, RED], [-0.28, 0.035, BLACK], [-0.19, 0.12, RED], [-0.02, 0.03, BLACK], [0.06, 0.07, RED], [0.18, 0.02, BLACK]];
486
+ beams.forEach(([ang, wid, col], i) => {
487
+ wid = wid;
488
+ // staccato: each beam punches out in two frames, then the whole fan "shouts" on a beat
489
+ const p = H.clamp(Math.floor((t - (1.0 + i * 0.1)) * 15) / 2, 0, 1), len = 1500 * p;
490
+ if (p <= 0) return;
491
+ const beat = t > 2.8 ? Math.exp(-((t - 2.8) % 0.5) * 9) : 0;
492
+ wid *= 1 + 0.22 * beat;
493
+ c.fillStyle = col; c.beginPath(); c.moveTo(mx1 + 20, my1);
494
+ c.lineTo(mx1 + Math.cos(ang - wid / 2) * len, my1 + Math.sin(ang - wid / 2) * len);
495
+ c.lineTo(mx1 + Math.cos(ang + wid / 2) * len, my1 + Math.sin(ang + wid / 2) * len); c.closePath(); c.fill();
496
+ });
497
+ // heavy diagonal bars
498
+ const ang = -0.24;
499
+ c.save(); c.translate(650, 580); c.rotate(ang);
500
+ c.fillStyle = BLACK; c.fillRect(-60, -54, 820 * P(1.55, 0.3), 92);
501
+ c.fillStyle = RED; c.fillRect(-700, 62, 1500 * P(1.45, 0.35), 22);
502
+ // lettering on the slant
503
+ c.fillStyle = CREAM; c.font = '400 38px "Russo One"'; c.letterSpacing = '5px';
504
+ if (t >= 1.95) c.fillText(t >= 2.1 ? 'BUILD THE' : 'BUILD', 6, 8); // words punched in on the beat
505
+ if (t >= 2.25) { // FUTURE slams down from oversize, stepped on twos
506
+ const k = H.clamp(Math.floor((t - 2.25) * 15) / 3, 0, 1), sc = 1 + 0.6 * (1 - k);
507
+ c.save(); c.translate(262 + 200, -10); c.scale(sc, sc); c.translate(-(262 + 200), 10);
508
+ c.fillStyle = RED; c.font = '400 132px "Anton"'; c.letterSpacing = '2px';
509
+ c.fillText('FUTURE', 262, 30); c.restore();
510
+ }
511
+ { const txt = 'ALL POWER TO THE MACHINES · 2026', n = H.clamp(Math.floor((t - 2.55) * 40), 0, txt.length); // typed like a ticker
512
+ c.fillStyle = BLACK; c.font = '700 18px "Oswald"'; c.letterSpacing = '5px';
513
+ if (n > 0) c.fillText(txt.slice(0, n), 4, 118); }
514
+ c.restore();
515
+ // small black square + circle (Lissitzky accent)
516
+ c.letterSpacing = '0px';
517
+ exInk(L, 7, 0.26, 0.006);
518
+
519
+ // ---------- halftone photomontage ----------
520
+ // the photograph is a fixed object (cut out once, screened once) that then slides in as a whole
521
+ if (!PHOTO) PHOTO = screenPhoto((pc) => drawPhoto(pc, t), W, HH);
522
+ const { PH, HT, SIL } = PHOTO;
523
+ const slide = (1 - H.ease.out(H.clamp((t - 0.25) / 0.75, 0, 1))) * -420, ox = slide, oy = -slide * 0.4;
524
+ // composite: rim (paper-coloured under-layer, covering beams), then ink plates multiply, then halftone multiply
525
+ ctx.save(); ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(L, 0, 0); ctx.restore();
526
+ ctx.save();
527
+ for (const [dx, dy] of [[-4, 0], [4, 0], [0, -4], [0, 4], [-3, -3], [3, 3], [3, -3], [-3, 3]]) ctx.drawImage(SIL, dx + ox, dy + oy);
528
+ ctx.drawImage(SIL, ox, oy);
529
+ ctx.restore();
530
+ // light tonal wash under the dots so the photo reads as a print on toned paper
531
+ ctx.save(); ctx.globalAlpha = 0.18; ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(PH, ox, oy); ctx.restore();
532
+ ctx.save(); ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(HT, 1.5 + ox, -1 + oy); ctx.restore();
533
+
534
+ // ---------- finish ----------
535
+ scratches(ctx, W, HH);
536
+ H.grain(ctx, W, HH, 0.08, 4 + Math.floor(t * 12), true);
537
+ H.vignette(ctx, W, HH, 0.25, '50,30,10');
538
+ },
539
+ });
540
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": 58,
3
+ "name": "Constructivist Propaganda",
4
+ "nameZh": "构成主义宣传画",
5
+ "family": "Art & Design Movements",
6
+ "familyZh": "艺术与设计流派",
7
+ "description": "A 1920s Rodchenko / El Lissitzky agitprop poster: red, black and cream only, a halftone photomontage robot bellowing through a megaphone, beams radiating from its voice, a red disc and hard diagonal bars, and heavy condensed lettering set on the slant — litho ink mottle on cheap paper.",
8
+ "motionNotes": "Motion: Agitprop staccato: the red disc rolls in, the halftone robot slides up the diagonal, beams punch out of the megaphone two frames at a time, the slogan bars shoot across and the words slam down on the beat; in the hold the beam fan pulses with each shout.",
9
+ "explainerNotes": "Explainer: Agitprop sequence: Lissitzky's red wedge drives across the sheet between scenes, slogans and labels slam onto slanted bars on twos, arrows are heavy bars with wedge heads, the counter is stencil-tall Anton, and the key moments get a stamped red cross or a burst of beams.",
10
+ "code": "code.js",
11
+ "registeredAs": "constructivist",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }