@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": 70,
3
+ "name": "Topographic Survey",
4
+ "nameZh": "地形测绘",
5
+ "family": "Data & Information",
6
+ "familyZh": "数据与信息",
7
+ "description": "A vintage survey quadrangle of a \"loss landscape\": brown contour lines with heavier labelled index contours, soft hillshade and woodland tint, a water-lined lake, a red dashed switchback trail and a full printed collar with scale bar and north arrow.",
8
+ "motionNotes": "Motion: Printed plate by plate: the black frame and collar, then the grey hillshade, the green woodland sliding into register, the blue lake, then brown contours rising from the shore like a tide; streams trace downhill, names land, and the red trail walks itself down the switchbacks while the camera eases in.",
9
+ "explainerNotes": "Explainer: The loss landscape is literally the map: survey points (examples) raise the terrain, a red hiker guesses, checks the slope and steps down a dashed switchback trail while the camera pans along it, and accuracy is lettered in a map cartouche like an elevation figure.",
10
+ "code": "code.js",
11
+ "registeredAs": "topographic",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }
@@ -0,0 +1,563 @@
1
+ {
2
+ // ---------------------------------------------------------------
3
+ // Pointillism — pure-hue dabs that mix in the eye, on a woven canvas
4
+ // ---------------------------------------------------------------
5
+ const PIG = [
6
+ [250, 246, 230], // lead white
7
+ [248, 216, 64], // lemon yellow
8
+ [240, 150, 46], // orange
9
+ [214, 66, 44], // vermilion
10
+ [232, 138, 156], // rose
11
+ [126, 82, 168], // violet
12
+ [44, 62, 168], // ultramarine
13
+ [74, 146, 214], // cerulean
14
+ [36, 148, 96], // emerald
15
+ [158, 196, 62], // yellow-green
16
+ [28, 84, 66], // viridian deep
17
+ [204, 150, 72], // ochre
18
+ [36, 34, 92], // deep ultramarine (no black on the palette)
19
+ ];
20
+ const css = (c) => `rgb(${c[0]},${c[1]},${c[2]})`;
21
+ // Seurat's vibration: each pigment's complement (index into PIG), laid in beside it now and then
22
+ // white lemon orange verm. rose violet ultra cerul. emer. y-grn viri. ochre deep
23
+ const COMP = [0, 5, 7, 8, 9, 1, 2, 2, 4, 5, 3, 6, 11];
24
+
25
+
26
+ // =================================================================
27
+ // EXPLAINER — "How does AI learn?" in divisionist dots. Every frame re-dabs a fixed field of dots against
28
+ // a colour study of the current scene; scene changes are the painter re-dabbing the canvas in a diagonal
29
+ // sweep (dots pop to their new hue behind the front). Lettering, arrows and numbers are fine dabs.
30
+ // =================================================================
31
+ let PXC = null;
32
+ const clP = (v) => (v < 0 ? 0 : v > 1 ? 1 : v);
33
+ const ezP = (u) => H.ease.inOut(clP(u));
34
+ const wP = (t, a, b) => clP((t - a) / (b - a));
35
+ const lerpP = (a, b, u) => a + (b - a) * u;
36
+ const PAL_INK = [6, 6, 6, 6, 12, 12, 5, 6, 3], PAL_RED = [3, 3, 3, 2, 4, 3], PAL_GREEN = [8, 8, 10, 10, 6, 9], PAL_GOLD = [1, 1, 2, 0, 11];
37
+ function pxCache(W, H_) {
38
+ if (PXC) return PXC;
39
+ const C = { glyph: new Map() };
40
+ const tex = H.layer(W, H_), tc = tex.getContext('2d');
41
+ tc.fillStyle = 'rgb(232,222,198)'; tc.fillRect(0, 0, W, H_);
42
+ tc.globalAlpha = 0.16; tc.fillStyle = 'rgb(150,130,100)'; for (let y = 0; y < H_; y += 3) tc.fillRect(0, y, W, 1);
43
+ tc.globalAlpha = 0.1; for (let x = 0; x < W; x += 3) tc.fillRect(x, 0, 1, H_);
44
+ C.tex = tex;
45
+ // brush touches: larger oval dabs laid along a slow flow field (the painter's hand), not random round dots
46
+ const rp = H.rng(2025), nzf = H.makeNoise(2026), sp = 5.6, pos = [];
47
+ for (let y = 6; y < H_ - 4; y += sp * 0.87) for (let x = 6; x < W - 4; x += sp) {
48
+ const px = x + rp.range(-1.6, 1.6) + ((y / sp) & 1) * sp * 0.5, py = y + rp.range(-1.4, 1.4);
49
+ pos.push(px, py, rp.range(2.6, 3.4), -0.6 + 0.5 * nzf.n2(px * 0.004, py * 0.004) + rp.range(-0.18, 0.18), rp());
50
+ }
51
+ C.pos = new Float32Array(pos); C.n = pos.length / 5;
52
+ C.study = H.layer(W, H_); C.study2 = H.layer(W, H_);
53
+ // the painted border of dots (hero step 5), baked once: darker complementary dabs travelling round the frame
54
+ C.border = H.layer(W, H_); { const bc = C.border.getContext('2d'), br = H.rng(66), bl = PIG.map(() => []);
55
+ for (let k = 0; k < 2600; k++) {
56
+ const side = br.int(0, 3), along = br(), depth = br.range(2, 16);
57
+ const x = side === 0 || side === 1 ? along * W : side === 2 ? depth : W - depth, y = side === 0 ? depth : side === 1 ? H_ - depth : along * H_;
58
+ bl[br.pick([6, 6, 12, 5, 2, 3, 6, 12])].push([x, y, br.range(2, 2.8), br() * 3]);
59
+ }
60
+ bc.shadowColor = 'rgba(52,38,74,0.42)'; bc.shadowOffsetX = 1.2; bc.shadowOffsetY = 1.6; batchFill(bc, bl); }
61
+ PXC = C;
62
+ return C;
63
+ }
64
+ // fine-dab glyphs, cached per (char,size)
65
+ function glyph(ch, size) {
66
+ const C = PXC, key = ch + '|' + size;
67
+ let g = C.glyph.get(key);
68
+ if (g) return g;
69
+ const font = `800 italic ${size}px "Playfair Display"`, m = H.layer(4, 4).getContext('2d'); m.font = font;
70
+ const adv = m.measureText(ch).width, w = Math.ceil(adv + size * 0.5), h = Math.ceil(size * 1.4);
71
+ const cv = H.layer(w, h), c = cv.getContext('2d'); c.font = font; c.fillStyle = '#000'; c.fillText(ch, size * 0.15, size * 1.05);
72
+ const d = c.getImageData(0, 0, w, h).data, r = H.rng(ch.charCodeAt(0) * 131 + size), st = Math.max(1.35, size / 30), dots = [];
73
+ for (let y = 0; y < h; y += st) for (let x = 0; x < w; x += st) {
74
+ const xx = x + r.range(-0.5, 0.5), yy = y + r.range(-0.5, 0.5), k = ((yy | 0) * w + (xx | 0)) * 4 + 3;
75
+ if (xx >= 0 && yy >= 0 && xx < w && yy < h && d[k] > 110) dots.push([xx - size * 0.15, yy - size * 1.05, st * r.range(0.72, 0.92), r(), r()]);
76
+ }
77
+ g = { adv, dots };
78
+ C.glyph.set(key, g);
79
+ return g;
80
+ }
81
+ function batchFill(ctx, lists, ratio = 0.8) {
82
+ lists.forEach((list, p) => {
83
+ if (!list.length) return;
84
+ ctx.fillStyle = `rgb(${PIG[p][0]},${PIG[p][1]},${PIG[p][2]})`;
85
+ // fill in chunks: one huge multi-subpath path rasterises far slower than many modest ones
86
+ for (let s = 0; s < list.length; s += 600) {
87
+ ctx.beginPath();
88
+ for (let i = s, e = Math.min(list.length, s + 600); i < e; i++) { const [x, y, rr, a] = list[i]; ctx.moveTo(x + rr * Math.cos(a), y + rr * Math.sin(a)); ctx.ellipse(x, y, rr, rr * ratio, a, 0, 6.2832); }
89
+ ctx.fill();
90
+ }
91
+ });
92
+ }
93
+ const textW = (str, size) => { let w = 0; for (const ch of str) w += glyph(ch, size).adv; return w; };
94
+ // lettering dabbed in fine dots; reveal sweeps left to right, each dot popping on in turn
95
+ function dotText(ctx, str, x, y, size, { align = 'left', reveal = 1, pal = PAL_INK, scale = 1 } = {}) {
96
+ if (reveal <= 0) return;
97
+ const lists = PIG.map(() => []), tw = textW(str, size);
98
+ let cx = align === 'center' ? -tw / 2 : align === 'right' ? -tw : 0;
99
+ const n = [...str].length;
100
+ [...str].forEach((ch, i) => {
101
+ const g = glyph(ch, size), lr = clP(reveal * (n + 2) - i);
102
+ if (lr > 0) for (const [dx, dy, rr, q, q2] of g.dots) if (q < lr) lists[pal[(q2 * pal.length) | 0]].push([x + (cx + dx) * scale, y + dy * scale, rr * scale, q * 3]);
103
+ cx += g.adv;
104
+ });
105
+ batchFill(ctx, lists);
106
+ }
107
+ // dotted arrow / line, dabbed along the path as it is drawn on
108
+ function dotPath(ctx, pts, prog, width, pal, seed, head = true) {
109
+ if (prog <= 0) return;
110
+ const lists = PIG.map(() => []), r = H.rng(seed);
111
+ let L = 0; const seg = []; for (let i = 1; i < pts.length; i++) { const l = Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]); seg.push(l); L += l; }
112
+ const upto = L * clP(prog);
113
+ let acc = 0, ang = 0, ex = pts[0][0], ey = pts[0][1];
114
+ for (let i = 1; i < pts.length && acc < upto; i++) {
115
+ const a = pts[i - 1], b = pts[i], l = seg[i - 1]; ang = Math.atan2(b[1] - a[1], b[0] - a[0]);
116
+ for (let s = 0; s < l && acc + s < upto; s += 2.4) {
117
+ const u = s / l, x = a[0] + (b[0] - a[0]) * u, y = a[1] + (b[1] - a[1]) * u; ex = x; ey = y;
118
+ for (let k = 0; k < Math.max(1, Math.round(width / 2.4)); k++) { const o = (k + 0.5 - width / 4.8) * 2.4 + r.range(-0.7, 0.7); lists[pal[(r() * pal.length) | 0]].push([x - Math.sin(ang) * o, y + Math.cos(ang) * o, r.range(1.1, 1.5), r() * 3]); }
119
+ }
120
+ acc += l;
121
+ }
122
+ if (head && prog >= 1) for (const sgn of [-1, 1]) for (let s = 0; s < width * 3.2; s += 2.2) { const a = ang + Math.PI + sgn * 0.55; for (let k = 0; k < 2; k++) lists[pal[(r() * pal.length) | 0]].push([ex + Math.cos(a) * s + r.range(-0.8, 0.8), ey + Math.sin(a) * s + r.range(-0.8, 0.8), r.range(1.1, 1.5), r() * 3]); }
123
+ batchFill(ctx, lists);
124
+ }
125
+ const bezP = (p0, p1, p2, p3, n = 30) => { const o = []; for (let i = 0; i <= n; i++) { const u = i / n, a = 1 - u; o.push([a * a * a * p0[0] + 3 * a * a * u * p1[0] + 3 * a * u * u * p2[0] + u * u * u * p3[0], a * a * a * p0[1] + 3 * a * a * u * p1[1] + 3 * a * u * u * p2[1] + u * u * u * p3[1]]); } return o; };
126
+ const ringP = (cx, cy, rx, ry, a0 = -0.6, turns = 1.08, n = 50) => { const o = []; for (let i = 0; i <= n; i++) { const a = a0 + i / n * Math.PI * 2 * turns; o.push([cx + Math.cos(a) * rx, cy + Math.sin(a) * ry]); } return o; };
127
+ const fmtP = (n) => Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
128
+
129
+ // ----- the colour studies (what the dots are trying to mix to) -----
130
+ function studyScene(u, sc, t, W, H_) {
131
+ const lg = (y0, y1, stops) => { const g = u.createLinearGradient(0, y0, 0, y1); stops.forEach(([o, c]) => g.addColorStop(o, c)); return g; };
132
+ u.fillStyle = lg(0, 300, [[0, 'rgb(86,142,214)'], [0.6, 'rgb(158,190,226)'], [1, 'rgb(238,214,184)']]); u.fillRect(0, 0, W, 300);
133
+ u.fillStyle = 'rgb(130,152,148)'; u.beginPath(); u.moveTo(0, 310); for (let x = 0; x <= W; x += 20) u.lineTo(x, 286 - 16 * Math.abs(Math.sin(x * 0.021)) - 8 * Math.sin(x * 0.007)); u.lineTo(W, 310); u.fill();
134
+ u.fillStyle = lg(300, 390, [[0, 'rgb(150,186,214)'], [1, 'rgb(70,124,184)']]); u.fillRect(0, 300, W, 90);
135
+ // warm haze where sky meets the far bank, sun glints on the water (light from the left)
136
+ u.fillStyle = lg(230, 330, [[0, 'rgba(250,214,170,0)'], [0.55, 'rgba(250,214,170,0.2)'], [1, 'rgba(250,214,170,0)']]); u.fillRect(0, 230, W, 100);
137
+ u.fillStyle = 'rgba(252,238,196,0.7)'; for (let k = 0; k < 7; k++) u.fillRect(120 + Math.sin(k * 2.3) * 50, 318 + k * 10, 170 - k * 14, 3);
138
+ u.fillStyle = lg(380, 720, [[0, 'rgb(164,200,82)'], [0.55, 'rgb(124,174,64)'], [0.8, 'rgb(70,132,80)'], [1, 'rgb(40,92,96)']]); u.fillRect(0, 386, W, 340);
139
+ { const g = u.createLinearGradient(0, 0, W, 0); g.addColorStop(0, 'rgba(250,226,140,0.28)'); g.addColorStop(0.5, 'rgba(250,226,140,0)'); g.addColorStop(1, 'rgba(60,70,150,0.16)'); u.fillStyle = g; u.fillRect(0, 386, W, 340); }
140
+ // violet cast shadow, thrown down-right (light from the upper left), soft-edged
141
+ const shadow = (fn) => { u.save(); u.filter = 'blur(10px)'; u.fillStyle = 'rgba(96,70,150,0.55)'; fn(); u.restore(); };
142
+ // labels sit on feathered pale-peach grounds so the dots thin out at the edges (no sticker boxes)
143
+ const ribbon = (x, y, w, h) => { u.save(); u.shadowColor = 'rgb(250,230,212)'; u.shadowBlur = 14; u.fillStyle = 'rgb(250,232,214)'; u.beginPath(); u.roundRect(x + 4, y + 4, Math.max(0, w - 8), h - 8, 14); u.fill(); u.restore(); };
144
+ const orb = (x, y, r, flash, spin) => {
145
+ shadow(() => { u.beginPath(); u.ellipse(x + r * 0.55, y + r + 92, r * 0.95, 20, 0, 0, 7); u.fill(); });
146
+ u.fillStyle = 'rgb(70,56,110)'; u.fillRect(x - 18, y + r - 6, 36, 90); u.beginPath(); u.ellipse(x, y + r + 86, 60, 14, 0, 0, 7); u.fill();
147
+ const g = u.createRadialGradient(x - r * 0.2, y - r * 0.2, 4, x, y, r * (1 + flash * 0.25)); g.addColorStop(0, flash > 0.2 ? 'rgb(255,250,226)' : 'rgb(252,232,140)'); g.addColorStop(0.55, 'rgb(242,168,66)'); g.addColorStop(1, 'rgb(150,70,120)');
148
+ u.fillStyle = g; u.beginPath(); u.arc(x, y, r * (1 + flash * 0.12), 0, 7); u.fill();
149
+ u.strokeStyle = 'rgb(40,36,96)'; u.lineWidth = 7; u.lineCap = 'round';
150
+ for (let k = 0; k < 3; k++) { const a = -2.4 + k * 1.0 + spin * (k === 1 ? -0.9 : 0.8); u.beginPath(); u.moveTo(x - 46 + k * 46, y + 30); u.lineTo(x - 46 + k * 46 + Math.cos(a) * 30, y + 30 + Math.sin(a) * 30); u.stroke(); u.beginPath(); u.arc(x - 46 + k * 46, y + 30, 18, 0, 7); u.lineWidth = 3; u.stroke(); u.lineWidth = 7; }
151
+ };
152
+ const card = (x, y, s, dog) => {
153
+ u.fillStyle = 'rgba(96,70,150,0.5)'; u.fillRect(x - s / 2 + s * 0.09, y - s / 2 + s * 0.11, s, s);
154
+ u.fillStyle = 'rgb(248,242,224)'; u.fillRect(x - s / 2, y - s / 2, s, s);
155
+ if (dog) { u.fillStyle = 'rgb(150,96,60)'; u.beginPath(); u.ellipse(x, y + s * 0.04, s * 0.28, s * 0.27, 0, 0, 7); u.fill(); u.fillStyle = 'rgb(90,56,40)'; for (const k of [-1, 1]) { u.beginPath(); u.ellipse(x + k * s * 0.3, y + s * 0.06, s * 0.09, s * 0.2, k * 0.3, 0, 7); u.fill(); } }
156
+ else { u.fillStyle = 'rgb(236,130,50)'; u.beginPath(); [[-0.32, 0], [-0.3, -0.36], [-0.1, -0.2], [0.1, -0.2], [0.3, -0.36], [0.32, 0], [0.22, 0.24], [0, 0.32], [-0.22, 0.24]].forEach(([a, b], i) => (i ? u.lineTo(x + a * s, y + b * s) : u.moveTo(x + a * s, y + b * s))); u.fill(); }
157
+ u.fillStyle = 'rgb(30,30,80)'; u.beginPath(); u.arc(x - s * 0.1, y, s * 0.04, 0, 7); u.arc(x + s * 0.1, y, s * 0.04, 0, 7); u.fill();
158
+ };
159
+ if (t > 1.9) ribbon(18, 14, 500 * clP((t - 1.9) / 0.3), 76);
160
+ if (sc === 1) {
161
+ // sandy path to the model
162
+ u.strokeStyle = 'rgb(226,200,150)'; u.lineWidth = 34; u.lineCap = 'round'; u.beginPath(); u.moveTo(-30, 540); u.bezierCurveTo(300, 580, 600, 470, 960, 400); u.stroke();
163
+ if (t > 1.0) orb(1080, 300, 100 * H.ease.back(wP(t, 1.0, 1.6)), 0, 0);
164
+ if (t > 2.4) ribbon(1000, 548, 162, 52);
165
+ for (const c of P_cards(t)) card(c.x, c.y, c.s, c.dog);
166
+ if (t > 2.9) ribbon(46, 600, 720, 104);
167
+ }
168
+ if (sc === 2) {
169
+ const flash = Math.sin(Math.PI * wP(t, 8.6, 9.6)), spin = ezP((t - 8.7) / 0.6);
170
+ orb(290, 370, 135, flash, spin); ribbon(205, 618, 172, 54);
171
+ card(1060, 250, 220, false); if (t > 6.8) ribbon(960, 376, 200, 52);
172
+ u.fillStyle = 'rgb(248,242,224)'; u.beginPath(); u.ellipse(660, 196, 196, 104, 0, 0, 7); u.fill(); u.beginPath(); u.moveTo(540, 260); u.lineTo(500, 330); u.lineTo(600, 280); u.fill();
173
+ if (t > 6.9) ribbon(330, 112, 140, 54); if (t > 7.6) ribbon(830, 72, 140, 54); if (t > 8.45) ribbon(722, 560, 166, 56);
174
+ if (t > 7.95 && t < 9.4) ribbon(950, 430, 220, 54);
175
+ }
176
+ if (sc === 3) {
177
+ const pr = wP(t, 11.2, 12.7), N = 24, pts = P_chart();
178
+ const lx = lerpP(160, 980, pr), area = () => { u.beginPath(); u.moveTo(160, 560); for (let i = 0; i <= N * pr; i++) u.lineTo(pts[i][0], pts[i][1]); u.lineTo(lx, 560); u.closePath(); };
179
+ if (pr > 0) shadow(() => { u.translate(16, 14); area(); u.fill(); });
180
+ u.fillStyle = lg(170, 560, [[0, 'rgb(254,228,96)'], [0.6, 'rgb(246,172,64)'], [0.88, 'rgb(232,130,70)'], [1, 'rgb(140,100,178)']]); area(); u.fill();
181
+ ribbon(170, 98, 560, 76); ribbon(400, 584, 340, 50);
182
+ if (t > 12.7) { const k = Math.sin(Math.PI * wP(t, 12.7, 13.4)); const g = u.createRadialGradient(980, pts[N][1], 2, 980, pts[N][1], 120); g.addColorStop(0, `rgba(255,246,190,${k})`); g.addColorStop(1, 'rgba(255,246,190,0)'); u.fillStyle = g; u.fillRect(840, 40, 280, 300); }
183
+ if (t > 13.15) ribbon(110, 628, 1060, 84);
184
+ }
185
+ }
186
+ function P_cards(t) {
187
+ const out = [];
188
+ for (let i = 0; i < 24; i++) {
189
+ const s0 = 2.5 + i * 0.15, v = (t - s0) / 1.5;
190
+ if (v <= 0 || v >= 1) continue;
191
+ const a = 1 - v, x = a * a * a * -30 + 3 * a * a * v * 300 + 3 * a * v * v * 600 + v * v * v * 980, y = a * a * a * 540 + 3 * a * a * v * 580 + 3 * a * v * v * 470 + v * v * v * 330;
192
+ out.push({ x, y: y - 64, s: v > 0.8 ? 92 * (1 - (v - 0.8) / 0.2 * 0.9) : 92, dog: (i % 2 === 1) !== (i % 5 === 0) });
193
+ }
194
+ return out;
195
+ }
196
+ function P_chart() { const N = 24, pts = []; for (let i = 0; i <= N; i++) { const acc = 61 + 36 * (1 - Math.exp(-i / 5.5)) / (1 - Math.exp(-N / 5.5)) + (i > 0 && i < N ? Math.sin(i * 2.7) * 1.6 : 0); pts.push([160 + i / N * 820, 560 - (acc - 50) / 50 * 380]); } return pts; }
197
+
198
+ // ----- the fine-dab layer for each scene (text, arrows, numbers, emphasis) -----
199
+ function overlayScene(ctx, sc, t) {
200
+ if (sc === 1) {
201
+ if (t > 2.4) dotText(ctx, 'the model', 1081, 584, 32, { align: 'center', reveal: wP(t, 2.4, 2.9) });
202
+ dotPath(ctx, bezP([140, 450], [400, 500], [640, 400], [950, 320]), wP(t, 2.3, 2.9), 5, PAL_INK, 11);
203
+ for (const c of P_cards(t)) if (c.s > 60) dotText(ctx, c.dog ? 'dog' : 'cat', c.x, c.y + c.s / 2 + 30, 28, { align: 'center', pal: c.dog ? PAL_INK : PAL_RED });
204
+ if (t > 2.9) {
205
+ const n = t > 5.6 ? 1e6 : Math.round(1e6 * H.ease.in(wP(t, 3.0, 5.6)) / 1000) * 1000;
206
+ dotText(ctx, fmtP(n), 70, 676, 66, { reveal: wP(t, 2.9, 3.3) });
207
+ dotText(ctx, 'examples', 84 + textW(fmtP(n), 66), 672, 44, { reveal: wP(t, 3.0, 3.4) });
208
+ if (t > 5.6) dotPath(ctx, [[70, 692], [400, 696], [720, 690]], wP(t, 5.6, 5.95), 5, PAL_RED, 12, false);
209
+ }
210
+ }
211
+ if (sc === 2) {
212
+ const second = t > 9.4;
213
+ dotText(ctx, 'the model', 291, 656, 34, { align: 'center' });
214
+ dotText(ctx, 'new picture', 1060, 413, 32, { align: 'center', reveal: wP(t, 6.8, 7.2) });
215
+ dotPath(ctx, bezP([395, 270], [420, 210], [440, 200], [470, 196]), wP(t, 6.9, 7.2), 6, PAL_INK, 21);
216
+ dotText(ctx, 'Guess', 400, 150, 38, { align: 'center', reveal: wP(t, 6.9, 7.3) });
217
+ if (!second) dotText(ctx, 'dog?', 660, 226, 88, { align: 'center', reveal: wP(t, 7.1, 7.5), pal: PAL_INK });
218
+ dotPath(ctx, bezP([945, 230], [910, 190], [890, 180], [860, 178]), wP(t, 7.5, 7.8), 6, PAL_INK, 22);
219
+ dotText(ctx, 'Check', 900, 110, 38, { align: 'center', reveal: wP(t, 7.6, 8.0) });
220
+ if (t > 7.9 && !second) {
221
+ dotPath(ctx, [[570, 136], [750, 250]], wP(t, 7.9, 8.05), 12, PAL_RED, 23, false);
222
+ dotPath(ctx, [[750, 136], [570, 250]], wP(t, 8.05, 8.2), 12, PAL_RED, 24, false);
223
+ dotPath(ctx, ringP(660, 196, 214, 120), wP(t, 8.0, 8.45), 7, PAL_RED, 25, false);
224
+ dotText(ctx, "it's a cat!", 1060, 469, 34, { align: 'center', reveal: wP(t, 8.0, 8.4), pal: PAL_RED });
225
+ }
226
+ dotPath(ctx, bezP([700, 318], [780, 600], [520, 600], [440, 440]), wP(t, 8.25, 8.8), 7, PAL_RED, 26);
227
+ dotText(ctx, 'Adjust', 805, 600, 38, { align: 'center', reveal: wP(t, 8.5, 8.9), pal: PAL_RED });
228
+ if (second) {
229
+ dotText(ctx, 'cat', 640, 226, 88, { align: 'center', reveal: wP(t, 9.4, 9.7), pal: PAL_INK });
230
+ dotPath(ctx, [[730, 196], [752, 226], [806, 150]], wP(t, 9.7, 9.95), 11, PAL_GREEN, 27, false);
231
+ }
232
+ }
233
+ if (sc === 3) {
234
+ const pts = P_chart(), pr = wP(t, 11.2, 12.7);
235
+ dotPath(ctx, [[160, 170], [160, 560], [1010, 560]], wP(t, 10.9, 11.3), 4, PAL_INK, 31, false);
236
+ dotText(ctx, 'training rounds', 570, 620, 34, { align: 'center', reveal: wP(t, 11.0, 11.4) });
237
+ dotPath(ctx, pts, pr, 6, PAL_INK, 32, false);
238
+ const acc = Math.round(t < 11.2 ? 61 : t > 12.7 ? 97 : lerpP(61, 97, H.ease.out(pr)));
239
+ dotText(ctx, 'Accuracy', 186, 154, 48, { reveal: wP(t, 10.9, 11.3) });
240
+ dotText(ctx, acc + '%', 470, 160, 68, { reveal: wP(t, 11.1, 11.3), pal: acc >= 97 ? PAL_RED : PAL_INK });
241
+ dotText(ctx, '61%', 92, pts[0][1] + 10, 32, { reveal: wP(t, 11.2, 11.5) });
242
+ if (t > 12.7) { dotPath(ctx, ringP(536, 138, 112, 58), wP(t, 12.7, 13.1), 6, PAL_RED, 33, false); dotPath(ctx, ringP(pts[24][0], pts[24][1], 26, 26, 0, 1.05, 30), wP(t, 12.9, 13.2), 5, PAL_RED, 34, false); }
243
+ if (t > 13.15) dotText(ctx, 'More examples → better guesses', 640, 688, 54, { align: 'center', reveal: ezP((t - 13.15) / 0.8) });
244
+ }
245
+ }
246
+
247
+ function explainPointillism(ctx, W, H_, t) {
248
+ const C = pxCache(W, H_);
249
+ ctx.drawImage(C.tex, 0, 0);
250
+ // which scene(s): the canvas is re-dabbed in a diagonal sweep between scenes
251
+ const wipes = [[5.95, 6.75], [10.35, 11.15]];
252
+ let sc = t < 6.35 ? 1 : t < 10.75 ? 2 : 3, sweep = null;
253
+ wipes.forEach(([a, b], i) => { if (t > a && t < b) sweep = { from: i + 1, to: i + 2, front: lerpP(-120, 1620, ezP((t - a) / (b - a))) }; });
254
+ const study = (cv, s, tt) => { const u = cv.getContext('2d'); studyScene(u, s, tt, W, H_); return u.getImageData(0, 0, W, H_).data; };
255
+ const TA = study(C.study, sweep ? sweep.from : sc, sweep ? Math.min(t, sweep.from === 1 ? 6.3 : 10.7) : t);
256
+ const TB = sweep ? study(C.study2, sweep.to, Math.max(t, sweep.to === 2 ? 6.4 : 10.8)) : null;
257
+ // greedy optical mixing per cell
258
+ const CELL = 8, cw = Math.ceil(W / CELL), ch = Math.ceil(H_ / CELL), acc = new Float32Array(cw * ch * 3), cnt = new Uint16Array(cw * ch);
259
+ const lists = PIG.map(() => []), sh = H.rng(7000 + Math.floor(t * 6)), P = C.pos;
260
+ for (let i = 0; i < C.n; i++) {
261
+ const x = P[i * 5], y = P[i * 5 + 1], q = P[i * 5 + 4];
262
+ let rr = P[i * 5 + 2], T = TA;
263
+ if (sweep) { const d = sweep.front - (x + y * 0.35); if (d > 0) { T = TB; if (d < 90) rr *= H.ease.back(clP(d / 90)); } else if (d > -50) rr *= 1 - 0.5 * clP((d + 50) / 50); }
264
+ const o = ((y | 0) * W + (x | 0)) * 4, ci = ((y / CELL) | 0) * cw + ((x / CELL) | 0), n = cnt[ci], k = ci * 3;
265
+ const w0 = T[o] * (n + 1) - acc[k], w1 = T[o + 1] * (n + 1) - acc[k + 1], w2 = T[o + 2] * (n + 1) - acc[k + 2];
266
+ const dark = T[o] + T[o + 1] + T[o + 2] < 330; // in shadow, reach for ultramarine/violet, not a darker same-hue
267
+ let best = 0, bd = 1e12, second = 0, sd = 1e12;
268
+ for (let p = 0; p < PIG.length; p++) { const Q = PIG[p]; let dd = (Q[0] - w0) ** 2 + (Q[1] - w1) ** 2 * 1.2 + (Q[2] - w2) ** 2 + ((q * 977 + p * 0.37) % 1) * 2600; if (dark && (p === 5 || p === 6 || p === 12)) dd *= 0.78; if (dd < bd) { second = best; sd = bd; best = p; bd = dd; } else if (dd < sd) { second = p; sd = dd; } }
269
+ const qq = (q * 7919) % 1;
270
+ let pick = qq < 0.16 ? second : qq > 0.93 && COMP[best] !== best ? COMP[best] : best;
271
+ acc[k] += PIG[pick][0]; acc[k + 1] += PIG[pick][1]; acc[k + 2] += PIG[pick][2]; cnt[ci]++;
272
+ if (sh() < 0.05) pick = pick === best ? second : best;
273
+ lists[pick].push([x, y, rr, P[i * 5 + 3]]);
274
+ }
275
+ // paint on canvas: every dab throws a hard little shadow onto the weave down-right (light from the upper left)
276
+ ctx.save(); ctx.shadowColor = 'rgba(52,38,74,0.42)'; ctx.shadowOffsetX = 1.2; ctx.shadowOffsetY = 1.6;
277
+ batchFill(ctx, lists, 0.6);
278
+ ctx.restore();
279
+ ctx.drawImage(C.border, 0, 0);
280
+ // fine dabs: the explanation itself (split along the sweep front during a change)
281
+ const clipSide = (ahead) => { ctx.beginPath(); const f = sweep.front; if (ahead) { ctx.moveTo(f, 0); ctx.lineTo(W + 400, 0); ctx.lineTo(W + 400, H_); ctx.lineTo(f - H_ * 0.35, H_); } else { ctx.moveTo(-400, 0); ctx.lineTo(f, 0); ctx.lineTo(f - H_ * 0.35, H_); ctx.lineTo(-400, H_); } ctx.closePath(); ctx.clip(); };
282
+ if (sweep) {
283
+ ctx.save(); clipSide(true); overlayScene(ctx, sweep.from, sweep.from === 1 ? 6.3 : 10.7); ctx.restore();
284
+ ctx.save(); clipSide(false); overlayScene(ctx, sweep.to, Math.max(t, sweep.to === 2 ? 6.4 : 10.8)); ctx.restore();
285
+ } else overlayScene(ctx, sc, t);
286
+ // hook -> header
287
+ {
288
+ const k = ezP((t - 1.5) / 0.6);
289
+ if (k < 1) { const s = lerpP(1, 44 / 84, k), tw = textW('How does AI learn?', 84); dotText(ctx, 'How does AI learn?', lerpP(640 - tw / 2, 36, k), lerpP(190, 66, k), 84, { reveal: wP(t, 0.1, 1.2), scale: s }); }
290
+ else dotText(ctx, 'How does AI learn?', 36, 66, 44);
291
+ }
292
+ ctx.strokeStyle = 'rgba(40,40,90,0.35)'; ctx.lineWidth = 1; ctx.strokeRect(19.5, 19.5, W - 39, H_ - 39);
293
+ }
294
+
295
+ registerStyle({
296
+ id: 'pointillism',
297
+ name: 'Pointillist Afternoon',
298
+ category: 'Painterly',
299
+ description: 'Neo-Impressionist divisionism: every form is built from small dabs of unmixed pigment — complementary hues side by side that blend optically — with the canvas weave peeking between the dots and a dotted painted border.',
300
+ motion: 'Dots shimmer as individual dabs re-pick their hue each frame while the overall colour holds, like light vibrating on the water; the sailboat drifts and the parasol sways.',
301
+ fonts: ['800 italic 50px "Playfair Display"'],
302
+ duration: 5.5,
303
+ frameT: 5.4,
304
+ readsPixels: true,
305
+ explainerDuration: 15,
306
+ explainerNote: 'Every frame re-dabs one fixed field of pure-pigment dots against the current scene, so moving things shimmer as dots re-pick their hue; scene changes are the canvas being re-dabbed in a diagonal sweep, and lettering, arrows, counters and the emphasis rings are all fine dabs.',
307
+ explainer(ctx, W, H_, t) { explainPointillism(ctx, W, H_, t); },
308
+ explainerSound(k) {
309
+ // all times copied from explainPointillism()/overlayScene()/studyScene() above (their wP() windows)
310
+ const D = 15, px = (x) => H.clamp(x / 1280 * 2 - 1, -1, 1) * 0.7;
311
+ const when = (f, v, a, b) => { for (let t = a; t <= b; t += 0.001) if (f(t) >= v) return t; return b; };
312
+ // one dab: a round brush tip tapped onto canvas. a soft muted tap with a little body, nothing bright.
313
+ const dab = (t, g = 1, pan = 0, f = 1000, minor = true) => {
314
+ k.noise(t, { dur: 0.018, filter: 'bandpass', f, q: 1.4, gain: 0.28 * g, a: 0.002, pan, minor });
315
+ k.noise(t, { dur: 0.025, color: 'brown', filter: 'lowpass', f: 450, gain: 0.2 * g, a: 0.002, pan, minor });
316
+ };
317
+ // dabbing a line of dots on: a patter of taps at `rate` per second, panned along the path
318
+ const patter = (a, b, rate, g = 1, p0 = 0, p1 = p0, minor = true) => {
319
+ const d = b - a, r = H.rng(Math.round(a * 1009) + 5);
320
+ for (let x = 0; x < d; x += (0.6 + r() * 0.8) / rate) dab(a + x, g * (0.55 + r() * 0.5), p0 + (p1 - p0) * x / d, 750 + r() * 650, minor);
321
+ };
322
+ // the whole canvas re-dabbed in a diagonal sweep: a dense wave of taps travelling left to right, over a soft swell
323
+ const redab = (a, b) => { const d = b - a;
324
+ patter(a, b, 34, 0.85, -0.8, 0.8, false);
325
+ k.noise(a, { dur: d, color: 'pink', filter: 'bandpass', f: [[0, 350], [d * 0.5, 900], [d, 500]], q: 0.7, gain: 0.09, a: d * 0.45, verb: 0.2 }); };
326
+ const counter = (val, a, b, pan) => { let last = val(a), lastT = -1;
327
+ for (let t = a; t <= b; t += 1 / 240) { const v = val(t); if (v !== last && t - lastT >= 1 / 12) { dab(t, 1.1, pan, 900); lastT = t; } last = v; } };
328
+
329
+ k.bed('room', 0, D, { gain: 0.4 });
330
+ // a sunny afternoon pad (Gmaj7, Em7, Cmaj7, D6; 3.75 s bars)
331
+ k.music('pad', 0, D, { bpm: 64, gain: 0.3, chords: [['G2', 'D3', 'F#3', 'B3'], ['E2', 'B2', 'D3', 'G3'], ['C3', 'E3', 'G3', 'B3'], ['D3', 'F#3', 'A3', 'B3']] });
332
+
333
+ // ---- 1 HOOK: title dabbed in 0.1-1.2, shrinks to the header 1.5-2.1, ribbon 1.9-2.2
334
+ patter(0.1, 1.2, 16, 1.0, -0.5, 0.5, false);
335
+ k.play('tock', 1.0, { f: 260, gain: 0.35, pan: px(1080) }); // the model's orb blooms in (ease.back 1.0-1.6)
336
+ k.noise(1.0, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.4, 800], [0.6, 500]], q: 0.8, gain: 0.08, a: 0.25, pan: px(1080), verb: 0.25 });
337
+ k.noise(1.5, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.6, 650]], q: 0.7, gain: 0.06, a: 0.2, minor: true }); // title slides to the corner
338
+ patter(1.9, 2.2, 12, 0.6, -0.9, -0.3);
339
+
340
+ // ---- 2 DATA: dotted arrow 2.3-2.9, 'the model' 2.4-2.9, counter 2.9-3.4
341
+ patter(2.3, 2.9, 14, 0.9, px(140), px(950), false);
342
+ patter(2.45, 2.9, 8, 0.5, px(1080));
343
+ patter(2.9, 3.35, 12, 0.8, -0.7, -0.3);
344
+ // counter: n = 1e6 * ease.in(wP(3.0, 5.6)), rounded to thousands; one firm dab per change, at most 12/s
345
+ counter((t) => Math.round(1e6 * H.ease.in(wP(t, 3.0, 5.6)) / 1000), 3.4, 5.58, -0.6);
346
+ k.play('tock', 5.6, { f: 420, gain: 0.55, pan: -0.6 }); // 1,000,000 lands
347
+ patter(5.6, 5.95, 16, 1.0, -0.8, 0.0, false); // red dotted underline
348
+
349
+ // ---- re-dab sweep 5.95-6.75
350
+ redab(5.95, 6.75);
351
+
352
+ // ---- 3 GUESS -> CHECK -> ADJUST
353
+ patter(6.85, 7.2, 14, 0.9, px(395), px(470), false); // guess arrow (+ 'Guess', 'new picture')
354
+ patter(7.2, 7.5, 12, 0.9, px(600), px(720)); // 'dog?'
355
+ patter(7.5, 7.85, 14, 0.9, px(945), px(860), false); // check arrow (+ 'Check')
356
+ // the wrong guess: two heavy red dotted slashes hammered on, a low "nope" under them
357
+ patter(7.9, 8.05, 30, 1.6, px(570), px(750), false); patter(8.05, 8.2, 30, 1.6, px(750), px(570), false);
358
+ k.play('thud', 7.9, { f: 115, gain: 0.45, pan: px(660) });
359
+ k.tone(7.92, { f: 'A3', type: 'triangle', dur: 0.2, gain: 0.17, a: 0.01, hold: 0.07, verb: 0.2, pan: px(660) });
360
+ k.tone(8.1, { f: 'F#3', type: 'triangle', dur: 0.38, gain: 0.17, a: 0.01, hold: 0.12, verb: 0.2, pan: px(660) });
361
+ patter(8.2, 8.45, 12, 0.7, px(800), px(560)); // the red ring closes
362
+ patter(8.45, 8.8, 14, 1.0, px(600), px(440), false); // adjust arrow back to the model
363
+ // the orb flashes and its dials turn (8.6-9.6 / ezP over 8.7-9.3): a soft warm swell and three detents
364
+ k.noise(8.6, { dur: 1.0, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.5, 800], [1.0, 380]], q: 0.8, gain: 0.08, a: 0.45, pan: px(290), verb: 0.3, minor: true });
365
+ const sp = (t) => ezP((t - 8.7) / 0.6);
366
+ for (let s = 1; s <= 3; s++) k.play('tock', when((t) => sp(t) * 4, s, 8.7, 9.3), { f: 600 + s * 30, gain: 0.5, pan: px(290), minor: true });
367
+ patter(9.4, 9.7, 12, 1.0, px(600), px(700)); // 'cat'
368
+ patter(9.7, 9.95, 22, 1.1, px(730), px(806), false); // green check
369
+ k.play('success', 9.95, { f: 'G5', gain: 0.45, pan: px(770) });
370
+
371
+ // ---- re-dab sweep 10.35-11.15
372
+ redab(10.35, 11.15);
373
+
374
+ // ---- 4 PROGRESS: axes and labels 10.9-11.4, dotted line 11.2-12.7
375
+ patter(11.15, 12.7, 10, 0.55, px(160), px(980));
376
+ const acc = (t) => Math.round(61 + 36 * H.ease.out(wP(t, 11.2, 12.7)));
377
+ const t97 = when(acc, 97, 11.2, 12.7);
378
+ counter(acc, 11.25, t97 - 0.05, px(470));
379
+ // 97% lands, re-dabbed in red: the biggest hit
380
+ k.play('impact', t97, { gain: 0.85, pan: px(470) });
381
+ patter(t97, t97 + 0.18, 40, 1.6, px(420), px(560), false);
382
+ k.play('ding', t97 + 0.02, { f: 'D5', gain: 0.5 });
383
+ ['G4', 'B4'].forEach((f, i) => k.tone(t97 + 0.05 + i * 0.03, { f, dur: 1.3, gain: 0.07, verb: 0.3, pan: (i - 0.5) * 0.5 }));
384
+ patter(12.7, 13.1, 18, 1.1, px(430), px(650), false); // red ring around 97%, while the summit glows
385
+ k.noise(12.7, { dur: 0.7, color: 'pink', filter: 'bandpass', f: [[0, 400], [0.35, 1000], [0.7, 500]], q: 0.8, gain: 0.07, a: 0.3, pan: px(980), verb: 0.3 });
386
+ patter(12.95, 13.2, 14, 0.7, px(980)); // small ring on the summit
387
+
388
+ // ---- 5 TAKEAWAY (13.15-13.95), then the resolve
389
+ patter(13.15, 13.95, 18, 1.1, -0.7, 0.7, false);
390
+ k.play('ding', 14.0, { f: 'G4', gain: 0.45 });
391
+ ['B4', 'D5', 'G5'].forEach((f, i) => k.tone(14.06 + i * 0.06, { f, dur: 1.2, gain: 0.065 - i * 0.008, verb: 0.3, pan: (i - 1) * 0.3 }));
392
+ },
393
+ draw(ctx, W, H_, t) {
394
+ // ---------- 1. the scene as a soft colour study (sampled, never shown) ----------
395
+ const U = H.layer(W, H_), u = U.getContext('2d');
396
+ const lg = (y0, y1, stops) => { const g = u.createLinearGradient(0, y0, 0, y1); stops.forEach(([o, c]) => g.addColorStop(o, c)); return g; };
397
+ u.fillStyle = lg(0, 290, [[0, 'rgb(104,150,214)'], [0.55, 'rgb(170,196,226)'], [1, 'rgb(240,214,186)']]); u.fillRect(0, 0, W, 300);
398
+ const sg = u.createRadialGradient(980, 200, 10, 980, 200, 380); sg.addColorStop(0, 'rgba(255,236,190,0.9)'); sg.addColorStop(1, 'rgba(255,236,190,0)'); u.fillStyle = sg; u.fillRect(0, 0, W, 300);
399
+ // far bank: hazy trees + a low data-centre with mast
400
+ u.fillStyle = 'rgb(126,150,146)'; u.beginPath(); u.moveTo(0, 300);
401
+ for (let x = 0; x <= W; x += 20) u.lineTo(x, 270 - 18 * Math.abs(Math.sin(x * 0.021)) - 10 * Math.sin(x * 0.007)); u.lineTo(W, 300); u.fill();
402
+ u.fillStyle = 'rgb(150,128,186)'; u.fillRect(450, 226, 200, 62); u.fillRect(510, 204, 70, 24);
403
+ u.fillStyle = 'rgb(120,104,170)'; u.fillRect(650, 248, 100, 40); u.fillStyle = 'rgb(214,170,150)'; u.fillRect(450, 222, 200, 6); u.fillRect(650, 244, 100, 5);
404
+ u.fillStyle = 'rgb(96,84,150)'; u.fillRect(593, 130, 6, 100); u.fillRect(582, 154, 28, 4); u.fillRect(586, 176, 20, 3);
405
+ u.fillStyle = 'rgb(235,100,70)'; u.fillRect(590, 124, 12, 10);
406
+ u.fillStyle = 'rgb(246,190,110)'; for (let x = 462; x < 640; x += 16) { u.fillRect(x, 240, 9, 7); u.fillRect(x, 260, 9, 7); }
407
+ // river
408
+ u.fillStyle = lg(288, 430, [[0, 'rgb(150,186,214)'], [1, 'rgb(64,118,180)']]); u.fillRect(0, 288, W, 150);
409
+ u.fillStyle = 'rgba(250,236,200,0.75)'; for (let k = 0; k < 9; k++) u.fillRect(880 + Math.sin(k * 2.1) * 60, 300 + k * 12, 150 - k * 8, 3);
410
+ // sailboat (drifts with t)
411
+ const bx = 760 + t * 12;
412
+ u.fillStyle = 'rgb(250,246,232)'; u.beginPath(); u.moveTo(bx, 250); u.lineTo(bx + 4, 340); u.lineTo(bx + 58, 334); u.closePath(); u.fill();
413
+ u.fillStyle = 'rgb(236,232,214)'; u.beginPath(); u.moveTo(bx - 4, 270); u.lineTo(bx - 4, 338); u.lineTo(bx - 40, 336); u.closePath(); u.fill();
414
+ u.fillStyle = 'rgb(150,60,50)'; u.beginPath(); u.moveTo(bx - 50, 340); u.lineTo(bx + 66, 338); u.lineTo(bx + 52, 352); u.lineTo(bx - 40, 352); u.fill();
415
+ u.fillStyle = 'rgba(80,70,120,0.5)'; u.fillRect(bx - 44, 354, 100, 10);
416
+ // near bank: sunlit grass
417
+ u.fillStyle = lg(420, 720, [[0, 'rgb(150,190,76)'], [1, 'rgb(118,170,60)']]);
418
+ u.beginPath(); u.moveTo(0, 440); u.bezierCurveTo(300, 418, 700, 432, 1280, 412); u.lineTo(1280, 720); u.lineTo(0, 720); u.fill();
419
+ // tree shadow pooled across the grass (cool)
420
+ u.fillStyle = 'rgb(42,96,86)'; u.beginPath(); u.moveTo(0, 470); u.bezierCurveTo(260, 470, 520, 520, 740, 560); u.bezierCurveTo(800, 600, 760, 720, 620, 720); u.lineTo(0, 720); u.fill();
421
+ u.fillStyle = 'rgb(62,120,86)'; u.beginPath(); u.ellipse(420, 600, 160, 30, 0, 0, 7); u.fill();
422
+ // tree: slender trunk + clumped canopy, lit warm on the right, cool in shadow
423
+ u.fillStyle = 'rgb(86,66,60)'; u.beginPath(); u.moveTo(158, 600); u.bezierCurveTo(172, 460, 150, 300, 186, 110); u.lineTo(202, 110); u.bezierCurveTo(176, 300, 198, 460, 190, 604); u.fill();
424
+ u.fillStyle = 'rgb(206,150,90)'; u.fillRect(187, 300, 5, 280);
425
+ u.strokeStyle = 'rgb(70,60,60)'; u.lineWidth = 7; u.beginPath(); u.moveTo(186, 200); u.quadraticCurveTo(260, 150, 330, 140); u.stroke(); u.beginPath(); u.moveTo(182, 240); u.quadraticCurveTo(110, 200, 60, 196); u.stroke();
426
+ const cr = H.rng(44);
427
+ for (let k = 0; k < 26; k++) {
428
+ const x = cr.range(10, 420), y = cr.range(-10, 200 - Math.abs(x - 200) * 0.25), rx = cr.range(40, 80), ry = cr.range(22, 40);
429
+ const lit = H.clamp((x - 120) / 300) * (1 - y / 260);
430
+ const c = H.mix([24, 60, 56], [126, 172, 64], lit * cr.range(0.6, 1));
431
+ u.fillStyle = `rgb(${c[0] | 0},${c[1] | 0},${c[2] | 0})`; u.beginPath(); u.ellipse(x, y, rx, ry, cr.range(-0.3, 0.3), 0, 7); u.fill();
432
+ }
433
+ // seated figure in profile with a laptop (sits in the sun at the shadow's edge)
434
+ const fx = 780, fy = 600;
435
+ u.save(); u.translate(fx, fy); u.scale(1.35, 1.35); u.translate(-fx, -fy);
436
+ // halo of complementary warmth around the cool screen glow
437
+ { const g = u.createRadialGradient(fx + 78, fy - 64, 4, fx + 78, fy - 64, 60); g.addColorStop(0, 'rgba(250,190,110,0.85)'); g.addColorStop(1, 'rgba(250,190,110,0)'); u.fillStyle = g; u.fillRect(fx, fy - 140, 160, 150); }
438
+ u.fillStyle = 'rgb(46,44,88)';
439
+ u.beginPath(); u.moveTo(fx - 30, fy); u.lineTo(fx - 24, fy - 86); u.quadraticCurveTo(fx - 4, fy - 102, fx + 12, fy - 84); u.lineTo(fx + 20, fy - 40); u.lineTo(fx + 70, fy - 34); u.lineTo(fx + 76, fy); u.closePath(); u.fill();
440
+ u.fillStyle = 'rgb(228,176,146)'; u.beginPath(); u.arc(fx - 6, fy - 112, 15, 0, 7); u.fill();
441
+ u.fillStyle = 'rgb(60,44,40)'; u.beginPath(); u.ellipse(fx - 10, fy - 120, 17, 9, -0.2, 0, 7); u.fill();
442
+ u.fillStyle = 'rgb(220,96,70)'; u.fillRect(fx - 30, fy - 94, 40, 6);
443
+ u.fillStyle = 'rgb(170,170,186)'; u.beginPath(); u.moveTo(fx + 14, fy - 46); u.lineTo(fx + 68, fy - 44); u.lineTo(fx + 68, fy - 38); u.lineTo(fx + 14, fy - 40); u.fill();
444
+ u.fillStyle = 'rgb(214,240,252)'; u.beginPath(); u.moveTo(fx + 62, fy - 44); u.lineTo(fx + 84, fy - 92); u.lineTo(fx + 94, fy - 88); u.lineTo(fx + 72, fy - 42); u.fill();
445
+ u.fillStyle = 'rgb(28,70,70)'; u.beginPath(); u.ellipse(fx + 20, fy + 6, 80, 10, 0, 0, 7); u.fill();
446
+ u.restore();
447
+ // standing figure with parasol (right)
448
+ const sx = 1080, sy = 640, sway = Math.sin(t * 1.2) * 0.05;
449
+ u.fillStyle = 'rgb(58,48,96)'; u.beginPath(); u.moveTo(sx - 18, sy - 150); u.lineTo(sx + 14, sy - 150); u.quadraticCurveTo(sx + 26, sy - 60, sx + 60, sy); u.lineTo(sx - 30, sy); u.quadraticCurveTo(sx - 26, sy - 80, sx - 18, sy - 150); u.fill();
450
+ u.fillStyle = 'rgb(226,170,140)'; u.beginPath(); u.arc(sx - 2, sy - 166, 13, 0, 7); u.fill();
451
+ u.fillStyle = 'rgb(40,34,70)'; u.beginPath(); u.ellipse(sx - 2, sy - 178, 20, 6, 0, 0, 7); u.fill();
452
+ u.save(); u.translate(sx + 6, sy - 140); u.rotate(-0.25 + sway);
453
+ u.strokeStyle = 'rgb(60,40,40)'; u.lineWidth = 3; u.beginPath(); u.moveTo(0, 0); u.lineTo(0, -70); u.stroke();
454
+ u.fillStyle = 'rgb(222,84,64)'; u.beginPath(); u.moveTo(-66, -60); u.quadraticCurveTo(0, -116, 66, -60); u.quadraticCurveTo(0, -70, -66, -60); u.fill();
455
+ u.restore();
456
+ u.fillStyle = 'rgb(30,72,70)'; u.beginPath(); u.ellipse(sx + 50, sy + 4, 90, 9, 0, 0, 7); u.fill();
457
+ // title in the sky
458
+ u.fillStyle = 'rgb(38,40,112)'; u.font = '800 italic 50px "Playfair Display"'; u.textBaseline = 'alphabetic';
459
+ // (lettering is NOT in the colour study: it is dabbed on top later with a finer brush)
460
+ const TM = H.layer(W, H_), tm = TM.getContext('2d');
461
+ tm.fillStyle = '#000'; tm.font = '800 italic 50px "Playfair Display"'; tm.fillText('A Sunday with the Machine', 540, 96);
462
+ const textMask = tm.getImageData(0, 0, W, H_).data;
463
+ u.filter = 'blur(2px)'; u.drawImage(U, 0, 0); u.filter = 'none';
464
+ const T = u.getImageData(0, 0, W, H_).data;
465
+
466
+ // ---------- 2. canvas ----------
467
+ ctx.fillStyle = 'rgb(232,222,198)'; ctx.fillRect(0, 0, W, H_);
468
+ ctx.save(); ctx.globalAlpha = 0.16; ctx.fillStyle = 'rgb(150,130,100)';
469
+ for (let y = 0; y < H_; y += 3) ctx.fillRect(0, y, W, 1);
470
+ ctx.globalAlpha = 0.1; for (let x = 0; x < W; x += 3) ctx.fillRect(x, 0, 1, H_);
471
+ ctx.restore();
472
+
473
+ // ---------- 3. dabs: greedy optical mixing per cell ----------
474
+ const D = H.layer(W, H_), dx = D.getContext('2d');
475
+ const CELL = 8, cw = Math.ceil(W / CELL), ch = Math.ceil(H_ / CELL);
476
+ const acc = new Float32Array(cw * ch * 3), cnt = new Uint16Array(cw * ch);
477
+ const r = H.rng(2025); // the colour decisions are stable...
478
+ const sh = H.rng(7000 + Math.floor(t * 6)); // ...but a few dabs flick to their runner-up hue: the shimmer
479
+ const live = H.clamp((t - 3.3) / 0.6);
480
+ const rp = H.rng(2025); // positions stay put
481
+ const dots = [];
482
+ // directional brush touches: oval dabs laid along a slow flow field (the hand's rhythm), slightly larger than before
483
+ const sp = 4.3, nzf = H.makeNoise(2026);
484
+ for (let y = 22; y < H_ - 20; y += sp * 0.87) for (let x = 22; x < W - 20; x += sp) { const px = x + rp.range(-1.6, 1.6) + ((y / sp) & 1) * sp * 0.5, py = y + rp.range(-1.4, 1.4), rr = rp.range(2.05, 2.7); rp();
485
+ dots.push([px, py, rr, -0.6 + 0.5 * nzf.n2(px * 0.004, py * 0.004) + nzf.n2(px * 0.3, py * 0.3) * 0.18]); }
486
+ // each dab gets its moment: the painter works down from the sky, dabbing all over each band
487
+ dots.forEach((d) => { d.push(0.12 + 2.7 * (0.45 * rp() + 0.55 * (d[1] / H_))); });
488
+ dots.sort((a, b) => a[4] - b[4]);
489
+ function choose(tc, ci, temp) {
490
+ const n = cnt[ci], k = ci * 3;
491
+ const want = [0, 1, 2].map((c) => tc[c] * (n + 1) - acc[k + c]);
492
+ let best = 0, bd = 1e12, second = 0, sd = 1e12;
493
+ const dark = tc[0] + tc[1] + tc[2] < 330; // shadows reach for ultramarine/violet rather than a darker same hue
494
+ for (let p = 0; p < PIG.length; p++) {
495
+ const P = PIG[p];
496
+ let d = (P[0] - want[0]) ** 2 + (P[1] - want[1]) ** 2 * 1.2 + (P[2] - want[2]) ** 2 + r() * temp;
497
+ if (dark && (p === 5 || p === 6 || p === 12)) d *= 0.78;
498
+ if (d < bd) { second = best; sd = bd; best = p; bd = d; } else if (d < sd) { second = p; sd = d; }
499
+ }
500
+ const rq = r(), pick = rq < 0.16 ? second : rq > 0.93 && COMP[best] !== best ? COMP[best] : best; // ~7% complements: the vibration
501
+ acc[k] += PIG[pick][0]; acc[k + 1] += PIG[pick][1]; acc[k + 2] += PIG[pick][2]; cnt[ci]++;
502
+ return live > 0 && sh() < 0.07 * live ? (pick === best ? second : best) : pick;
503
+ }
504
+ const batches = PIG.map(() => []);
505
+ const flush = () => {
506
+ batches.forEach((list, p) => {
507
+ if (!list.length) return;
508
+ dx.fillStyle = css(PIG[p]); dx.beginPath();
509
+ for (const [x, y, rr, a] of list) { dx.moveTo(x + rr * Math.cos(a), y + rr * Math.sin(a)); dx.ellipse(x, y, rr, rr * (rr > 1.7 ? 0.62 : 0.78), a, 0, 6.2832); }
510
+ dx.fill(); list.length = 0;
511
+ });
512
+ };
513
+ dots.forEach((d0, i) => {
514
+ if (d0[4] > t) return;
515
+ const pop = H.ease.back(H.clamp((t - d0[4]) / 0.09));
516
+ const d = pop >= 1 ? d0 : [d0[0], d0[1], d0[2] * pop, d0[3]];
517
+ const [x, y] = d, xi = Math.min(W - 1, x | 0), yi = Math.min(H_ - 1, y | 0), o = (yi * W + xi) * 4;
518
+ const tc = [T[o], T[o + 1], T[o + 2]];
519
+ const ci = Math.min(ch - 1, (y / CELL) | 0) * cw + Math.min(cw - 1, (x / CELL) | 0);
520
+ batches[choose(tc, ci, 2600)].push(d);
521
+ if (i % 9000 === 8999) flush();
522
+ });
523
+ flush();
524
+ // finer dabs for the lettering and the figures' faces: keeps edges crisp
525
+ const fine = [];
526
+ for (let y = 40; y < 115; y += 1.7) for (let x = 500; x < 1220; x += 1.8) {
527
+ const xi = (x + rp.range(-0.8, 0.8)) | 0, yi = (y + rp.range(-0.8, 0.8)) | 0;
528
+ const fr = rp.range(1.2, 1.55), fa = rp() * 3, fb = 2.85 + (x - 500) / 720 * 0.55 + rp() * 0.08;
529
+ if (textMask[(yi * W + xi) * 4 + 3] > 120 && t >= fb) fine.push([xi + 0.5, yi + 0.5, fr * H.ease.out(H.clamp((t - fb) / 0.08)), fa]);
530
+ }
531
+ fine.forEach((d) => { const q = H.rng((d[0] * 31 + d[1] * 7919) | 0); const pick = q() < 0.55 ? 6 : q() < 0.7 ? 12 : q() < 0.6 ? 5 : 3; batches[pick].push(d); });
532
+ flush();
533
+
534
+ // ---------- 4. dab relief: each dab catches light on its upper-left rim ----------
535
+ ctx.drawImage(D, 0, 0);
536
+ const img = ctx.getImageData(0, 0, W, H_), d = img.data, a = dx.getImageData(0, 0, W, H_).data;
537
+ for (let y = 1; y < H_ - 1; y++) for (let x = 1; x < W - 1; x++) {
538
+ const i = (y * W + x) * 4;
539
+ const g = (a[i + 3 - 4 - W * 4] - a[i + 3 + 4 + W * 4]);
540
+ if (g === 0) continue;
541
+ const f = 1 + (g < 0 ? 0.2 : -0.17) * Math.abs(g) / 255; // lit upper-left rim, shadowed lower-right rim
542
+ d[i] *= f; d[i + 1] *= f; d[i + 2] *= f;
543
+ }
544
+ ctx.putImageData(img, 0, 0);
545
+
546
+ // ---------- 5. painted border of dots (complementary, darker) ----------
547
+ const br = H.rng(66);
548
+ const border = PIG.map(() => []);
549
+ for (let k = 0; k < 2600; k++) {
550
+ const side = br.int(0, 3), along = br();
551
+ let x, y; const depth = br.range(2, 16);
552
+ if (side === 0) { x = along * W; y = depth; } else if (side === 1) { x = along * W; y = H_ - depth; } else if (side === 2) { x = depth; y = along * H_; } else { x = W - depth; y = along * H_; }
553
+ const p = br.pick([6, 6, 12, 5, 2, 3, 6, 12]), rr = br.range(2, 2.8), an = br() * 3;
554
+ // the border is dabbed on last, travelling clockwise round the frame
555
+ const pos = side === 0 ? along * 0.25 : side === 3 ? 0.25 + along * 0.25 : side === 1 ? 0.5 + (1 - along) * 0.25 : 0.75 + (1 - along) * 0.25;
556
+ const bb = 3.2 + pos * 0.8;
557
+ if (t >= bb) border[p].push([x, y, rr * H.ease.out(H.clamp((t - bb) / 0.08)), an]);
558
+ }
559
+ border.forEach((list, p) => { if (!list.length) return; ctx.fillStyle = css(PIG[p]); ctx.beginPath(); for (const [x, y, rr, an] of list) { ctx.moveTo(x + rr, y); ctx.ellipse(x, y, rr, rr * 0.8, an, 0, 6.2832); } ctx.fill(); });
560
+ ctx.strokeStyle = `rgba(40,40,90,${0.35 * H.clamp((t - 3.9) / 0.3)})`; ctx.lineWidth = 1; ctx.strokeRect(19.5, 19.5, W - 39, H_ - 39);
561
+ }
562
+ });
563
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": 71,
3
+ "name": "Pointillist Afternoon",
4
+ "nameZh": "点彩午后",
5
+ "family": "Painterly",
6
+ "familyZh": "绘画质感",
7
+ "description": "Neo-Impressionist divisionism: every form is built from small dabs of unmixed pigment — complementary hues side by side that blend optically — with the canvas weave peeking between the dots and a dotted painted border.",
8
+ "motionNotes": "Motion: Dots shimmer as individual dabs re-pick their hue each frame while the overall colour holds, like light vibrating on the water; the sailboat drifts and the parasol sways.",
9
+ "explainerNotes": "Explainer: Every frame re-dabs one fixed field of pure-pigment dots against the current scene, so moving things shimmer as dots re-pick their hue; scene changes are the canvas being re-dabbed in a diagonal sweep, and lettering, arrows, counters and the emphasis rings are all fine dabs.",
10
+ "code": "code.js",
11
+ "registeredAs": "pointillism",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }