@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": 53,
3
+ "name": "Vaporwave",
4
+ "nameZh": "蒸汽波",
5
+ "family": "Retro UI & Neon",
6
+ "familyZh": "复古界面与霓虹",
7
+ "description": "Pastel-neon dreamscape: sliced sunset sun, glowing grid floor, a glitching marble bust on a checkerboard plinth, palm silhouettes, katakana, and a chewed-up VHS tape finish.",
8
+ "motionNotes": "Motion: Tape starts on a blue VCR screen and rolls into lock, the striped sun rises, palms slide in, the bust glitches into existence slice by slice, katakana stamps on with chroma split and a tracking band crawls down.",
9
+ "explainerNotes": "Explainer: Told on a worn VHS tape: scenes change with a tape cut (VCR blue screen, fast-forward, vertical roll), the header lives on as the VCR on-screen menu, the counter is tape-counter digits, labelled polaroids slide along the neon floor into the marble-bust \"model\", a pastel guess.exe pop-up does the guessing and the chart is drawn in neon across the sky.",
10
+ "code": "code.js",
11
+ "registeredAs": "vaporwave",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }
@@ -0,0 +1,504 @@
1
+ {
2
+ // Ebru / suminagashi marbling, simulated FORWARD with the mathematical marbling transforms
3
+ // (Jaffer & Lu): ink drop p' = c + (p-c)·sqrt(1 + r²/|p-c|²)
4
+ // tine line p' = p + α·λ/(d+λ)·M (d = distance to the stylus line)
5
+ // comb = a periodic set of tine lines with alternating direction
6
+ // Every floated colour is a polygon; each later operation moves all earlier vertices, with
7
+ // adaptive re-subdivision so stretched veins stay smooth. Pigment speckle is sampled in the
8
+ // pre-comb coordinates, so it is dragged along the rake like real floated paint.
9
+ const TAU = Math.PI * 2;
10
+ const PAL = {
11
+ indigo: '#1d2a5e', red: '#a6252b', ochre: '#d89c2c', teal: '#21807c', cream: '#efe3c4',
12
+ ink: '#1b1d27', slate: '#3f5470', rose: '#cf6f5e', sky: '#7ea6c4', sand: '#d9bf86', green: '#4f6b3a',
13
+ };
14
+
15
+ // ---- transforms (forward) ----
16
+ const T = {
17
+ drop: (o) => (x, y) => { const dx = x - o.x, dy = y - o.y, d2 = dx * dx + dy * dy + 1e-9, f = Math.sqrt(1 + (o.r * o.r) / d2); return [o.x + dx * f, o.y + dy * f]; },
18
+ tine: (o) => (x, y) => { const d = Math.abs((x - o.x) * -o.my + (y - o.y) * o.mx), z = (o.a * o.l) / (d + o.l); return [x + z * o.mx, y + z * o.my]; },
19
+ // periodic comb: tines every S px along one axis, alternating push direction
20
+ comb: (o) => (x, y) => { const u = o.vertical ? x : y; const dv = combShift(u, o); return o.vertical ? [x, y + dv] : [x + dv, y]; },
21
+ wave: (o) => (x, y) => o.vertical ? [x, y + o.A * Math.sin((x / o.L) * TAU + o.ph)] : [x + o.A * Math.sin((y / o.L) * TAU + o.ph), y],
22
+ };
23
+ function combShift(u, o) {
24
+ const j0 = Math.round((u - o.off) / o.S);
25
+ let s = 0;
26
+ for (let j = j0 - 3; j <= j0 + 3; j++) { const d = Math.abs(u - (o.off + j * o.S)); s += ((j & 1) ? -1 : 1) * (o.a * o.l) / (d + o.l); }
27
+ return s;
28
+ }
29
+
30
+ function applyOp(polys, f, maxLen) {
31
+ for (const P of polys) {
32
+ if (P.dead) continue;
33
+ const src = P.pts, out = [];
34
+ const n = src.length / 2;
35
+ const tp = new Array(n);
36
+ for (let i = 0; i < n; i++) tp[i] = f(src[i * 2], src[i * 2 + 1]);
37
+ const push = (ax, ay, bx, by, A, B, depth) => {
38
+ // emits A then (recursively) the midpoints needed so that |A'B'| <= maxLen
39
+ const ddx = B[0] - A[0], ddy = B[1] - A[1];
40
+ if (depth < 5 && ddx * ddx + ddy * ddy > maxLen * maxLen) {
41
+ const mx = (ax + bx) / 2, my = (ay + by) / 2, M = f(mx, my);
42
+ push(ax, ay, mx, my, A, M, depth + 1);
43
+ push(mx, my, bx, by, M, B, depth + 1);
44
+ } else out.push(A[0], A[1]);
45
+ };
46
+ // NB: subdivision samples pre-images; we keep transformed coordinates as the new polygon
47
+ for (let i = 0; i < n; i++) {
48
+ const j = (i + 1) % n;
49
+ push(src[i * 2], src[i * 2 + 1], src[j * 2], src[j * 2 + 1], tp[i], tp[j], 0);
50
+ }
51
+ P.pts = out;
52
+ // cull polygons that have left the sheet entirely
53
+ let minx = 1e9, maxx = -1e9, miny = 1e9, maxy = -1e9;
54
+ for (let i = 0; i < out.length; i += 2) { const x = out[i], y = out[i + 1]; if (x < minx) minx = x; if (x > maxx) maxx = x; if (y < miny) miny = y; if (y > maxy) maxy = y; }
55
+ if (minx > 1500 || maxx < -220 || miny > 940 || maxy < -220) P.dead = true;
56
+ }
57
+ }
58
+ function circle(x, y, r) {
59
+ const n = Math.max(48, Math.min(360, Math.round((TAU * r) / 3)));
60
+ const pts = [];
61
+ for (let i = 0; i < n; i++) { const a = (i / n) * TAU; pts.push(x + Math.cos(a) * r, y + Math.sin(a) * r); }
62
+ return pts;
63
+ }
64
+
65
+
66
+ // =====================================================================================
67
+ // EXPLAINER — the model is the marbling bath. Examples are drops of colour landing on it
68
+ // (ochre = cat, indigo = dog). A new picture is a red probe drop; the model's "guess" is the
69
+ // way its current flow drags the probe. The error is a counter-stroke of the stylus, and the
70
+ // adjustment is a comb pass that puts the pattern in order. Scenes change the marbler's way:
71
+ // the printed sheet is lifted off the bath, revealing the next bath underneath.
72
+ // =====================================================================================
73
+ const PX = (t, a, b) => H.clamp((t - a) / (b - a));
74
+ const INKB = '#2a2018', REDI = '#a6252b';
75
+ let PM_TEX = null, PM_STILL = null, PM_SPK = null;
76
+ function pmTexture(W, H_) {
77
+ if (PM_TEX) return PM_TEX;
78
+ PM_TEX = H.layer(W, H_); const c = PM_TEX.getContext('2d'), img = c.createImageData(W, H_), d = img.data;
79
+ const nz = H.makeNoise(3), nz2 = H.makeNoise(8), cn = H.makeNoise(44), r = H.rng(9);
80
+ for (let y = 0; y < H_; y++) for (let x = 0; x < W; x++) {
81
+ const sp = nz.n2(x * 0.08, y * 0.08) * 0.5 + nz2.n2(x * 0.35, y * 0.35) * 0.5;
82
+ const laid = (x % 3 === 0 ? -0.025 : 0) + ((x + 9) % 64 < 1.5 ? -0.03 : 0);
83
+ const cock = cn.fbm2(x * 0.0044, y * 0.0044, 3) * 0.11 + cn.fbm2(x * 0.035 / 8 + 7, y * 0.035 / 8, 3) * 0.035; // broad cockle + the still's finer drying undulation
84
+ const k = H.clamp(0.93 + sp * 0.07 + laid + cock + (r() - 0.5) * 0.04, 0, 1);
85
+ const o = (y * W + x) * 4; d[o] = d[o + 1] = d[o + 2] = 255 * k; d[o + 3] = 255;
86
+ }
87
+ c.putImageData(img, 0, 0); return PM_TEX;
88
+ }
89
+ function pmStill(W, H_) {
90
+ if (PM_STILL) return PM_STILL;
91
+ PM_STILL = H.layer(W, H_); const st = window.STYLES.find((s) => s.id === 'paper-marbling'); H.render(st, PM_STILL, 7.9); return PM_STILL;
92
+ }
93
+ // pigment pooling at a floated drop's rim: the drop's own colour, darker (not a neutral ink outline)
94
+ const RIM = {}, HALO = {};
95
+ const rimOf = (col) => RIM[col] || (RIM[col] = H.rgba(H.hex(col).map((v) => v * 0.66), 0.72));
96
+ const haloOf = (col) => HALO[col] || (HALO[col] = H.rgba(H.mix(col, '#fff6e2', 0.35), 0.38));
97
+ // cockle (the hero's soft-light undulation from the wetted sheet drying), built once per size
98
+ let PM_COCK = null;
99
+ function pmCockle(W, H_) {
100
+ if (PM_COCK && PM_COCK.w === W) return PM_COCK.c;
101
+ const cn = H.makeNoise(44), sw = Math.round(W / 8), sh = Math.round(H_ / 8);
102
+ const lay = H.layer(sw, sh), lc = lay.getContext('2d'), li = lc.createImageData(sw, sh);
103
+ for (let y = 0; y < sh; y++) for (let x = 0; x < sw; x++) {
104
+ const v = cn.fbm2(x * 0.035, y * 0.035, 3), o = (y * sw + x) * 4;
105
+ li.data[o] = li.data[o + 1] = li.data[o + 2] = v > 0 ? 255 : 0; li.data[o + 3] = Math.min(255, Math.abs(v) * 110);
106
+ }
107
+ lc.putImageData(li, 0, 0); PM_COCK = { w: W, c: lay }; return lay;
108
+ }
109
+ // pigment granulation: fine dark/light speckle that only lives inside floated colour. Baked into one
110
+ // repeating tile per pigment, so filling a drop with its colour already carries the granulation.
111
+ let PM_GRAIN = null; const PM_PAT = {};
112
+ function pmGrain() {
113
+ if (PM_GRAIN) return PM_GRAIN;
114
+ const S = 512; PM_GRAIN = new Float32Array(S * S);
115
+ const nz = H.makeNoise(17), r = H.rng(23), w = (x, y, f) => nz.n2(x * f, y * f * 0.42) ; // tileable via 4-corner blend
116
+ for (let y = 0; y < S; y++) for (let x = 0; x < S; x++) {
117
+ const fx = x / S, fy = y / S, n = (xx, yy) => w(xx, yy, 0.5) * 0.6 + nz.n2(xx * 0.09 + 40, yy * 0.05) * 0.4;
118
+ const v = n(x, y) * (1 - fx) * (1 - fy) + n(x - S, y) * fx * (1 - fy) + n(x, y - S) * (1 - fx) * fy + n(x - S, y - S) * fx * fy;
119
+ PM_GRAIN[y * S + x] = H.clamp(0.9 + (v * 1.6 + (r() - 0.5) * 0.5) * 0.16, 0, 1.05);
120
+ }
121
+ return PM_GRAIN;
122
+ }
123
+ function pigment(ctx, col) {
124
+ if (PM_PAT[col]) return PM_PAT[col];
125
+ const S = 512, g = pmGrain(), c3 = H.hex(col), cv = H.layer(S, S), c = cv.getContext('2d'), img = c.createImageData(S, S), d = img.data;
126
+ for (let i = 0; i < S * S; i++) { const k = g[i]; d[i * 4] = Math.min(255, c3[0] * k); d[i * 4 + 1] = Math.min(255, c3[1] * k); d[i * 4 + 2] = Math.min(255, c3[2] * k); d[i * 4 + 3] = 255; }
127
+ c.putImageData(img, 0, 0); return (PM_PAT[col] = ctx.createPattern(cv, 'repeat'));
128
+ }
129
+ function renderPolys(ctx, W, H_, polys, bath) {
130
+ ctx.fillStyle = bath; ctx.fillRect(0, 0, W, H_);
131
+ // the bare size is never flat: a broad, faint tonal drift across the bath
132
+ const bg = ctx.createLinearGradient(0, 0, W, H_); bg.addColorStop(0, 'rgba(255,250,236,0.35)'); bg.addColorStop(0.55, 'rgba(255,250,236,0)'); bg.addColorStop(1, 'rgba(150,120,70,0.12)');
133
+ ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
134
+ for (const P of polys) {
135
+ if (P.dead) continue; const p = P.pts;
136
+ ctx.beginPath(); ctx.moveTo(p[0], p[1]); for (let i = 2; i < p.length; i += 2) ctx.lineTo(p[i], p[i + 1]); ctx.closePath();
137
+ ctx.fillStyle = pigment(ctx, P.col); ctx.fill();
138
+ if (P.halo) { ctx.save(); ctx.clip(); ctx.strokeStyle = haloOf(P.col); ctx.lineWidth = 6; ctx.stroke(); ctx.restore(); } // gall halo just inside a fresh drop
139
+ ctx.strokeStyle = rimOf(P.col); ctx.lineWidth = 1; ctx.stroke();
140
+ }
141
+ }
142
+ function finish(ctx, W, H_) {
143
+ // soften like the still (floated colour never has a vector edge): a half-pixel resample of itself,
144
+ // visually the hero's blur(0.6px) at a fraction of the cost; then laid paper, fibre and cockle (pmTexture)
145
+ ctx.save(); ctx.globalAlpha = 0.5; ctx.drawImage(ctx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height, 0.5, 0.5, W, H_); ctx.restore();
146
+ ctx.save(); ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(pmTexture(W, H_), 0, 0); ctx.restore();
147
+ H.vignette(ctx, W, H_, 0.2, '40,25,10');
148
+ }
149
+ // bookbinder's paper label: cream, double rule, serif type
150
+ function blabel(ctx, text, x, y, o = {}) {
151
+ const size = o.size || 22; ctx.save();
152
+ ctx.font = `${o.italic ? 'italic ' : ''}700 ${size}px "Libre Baskerville"`; try { ctx.letterSpacing = o.ls || '1px'; } catch (e) {}
153
+ const w = ctx.measureText(text).width + size * 1.4, h = size * 2;
154
+ const ox = o.align === 'left' ? x : o.align === 'right' ? x - w : x - w / 2;
155
+ ctx.fillStyle = 'rgba(30,20,10,0.25)'; ctx.fillRect(ox + 3, y - h / 2 + 4, w, h);
156
+ ctx.fillStyle = o.red ? '#f6e6d6' : '#f3ead2'; ctx.fillRect(ox, y - h / 2, w, h);
157
+ ctx.strokeStyle = o.red ? REDI : INKB; ctx.lineWidth = 1.6; ctx.strokeRect(ox + 4, y - h / 2 + 4, w - 8, h - 8); ctx.lineWidth = 0.6; ctx.strokeRect(ox + 7, y - h / 2 + 7, w - 14, h - 14);
158
+ ctx.fillStyle = o.red ? REDI : INKB; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillText(text, ox + w / 2, y + 1);
159
+ ctx.restore(); return w;
160
+ }
161
+ function dropLabel(ctx, t, t0, text, x, y, o) { if (t < t0) return; const k = H.ease.out(PX(t, t0, t0 + 0.25)); ctx.save(); ctx.globalAlpha = k; blabel(ctx, text, x, y + (1 - k) * -18, o); ctx.restore(); }
162
+ // inked brush arrow (stylus-drawn line with a tapered head)
163
+ function inkArrow(ctx, pts, p, col = INKB, lw = 4) {
164
+ if (p <= 0) return; let L = 0; for (let i = 1; i < pts.length; i++) L += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]);
165
+ let left = L * p, end = pts[0], prev = pts[0]; ctx.save(); ctx.strokeStyle = col; ctx.fillStyle = col; ctx.lineWidth = lw; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath(); ctx.moveTo(pts[0][0], pts[0][1]);
166
+ for (let i = 1; i < pts.length && left > 0; i++) { const d = Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]), q = Math.min(1, left / d); prev = pts[i - 1]; end = [pts[i - 1][0] + (pts[i][0] - pts[i - 1][0]) * q, pts[i - 1][1] + (pts[i][1] - pts[i - 1][1]) * q]; ctx.lineTo(end[0], end[1]); left -= d; }
167
+ ctx.stroke(); const a = Math.atan2(end[1] - prev[1], end[0] - prev[0]), hd = lw * 4.5;
168
+ ctx.beginPath(); ctx.moveTo(end[0] + Math.cos(a) * lw, end[1] + Math.sin(a) * lw); ctx.lineTo(end[0] - Math.cos(a - 0.45) * hd, end[1] - Math.sin(a - 0.45) * hd); ctx.quadraticCurveTo(end[0] - Math.cos(a) * hd * 0.6, end[1] - Math.sin(a) * hd * 0.6, end[0] - Math.cos(a + 0.45) * hd, end[1] - Math.sin(a + 0.45) * hd); ctx.closePath(); ctx.fill();
169
+ ctx.restore();
170
+ }
171
+ const HEAD = 28; // running head: a story label, so >= 28px
172
+ const pmHeader = (ctx) => blabel(ctx, 'How does AI learn?', 24, 42, { size: HEAD, italic: true, align: 'left' });
173
+ const textW = (ctx, text, size, italic) => { ctx.save(); ctx.font = `${italic ? 'italic ' : ''}700 ${size}px "Libre Baskerville"`; try { ctx.letterSpacing = '1px'; } catch (e) {} const w = ctx.measureText(text).width + size * 1.4; ctx.restore(); return w; };
174
+ // shared battal ground (the untrained model): `n` drops, landing from time t0 over dur
175
+ function battal(polys, t, n, t0, dur, ML, halo = false) {
176
+ const r = H.rng(77), cols = [PAL.ochre, PAL.indigo, PAL.sand, PAL.ochre, PAL.indigo, PAL.cream, PAL.sky];
177
+ const landed = [];
178
+ for (let i = 0; i < n; i++) {
179
+ const x = r.range(-40, 1320), y = r.range(-40, 760), rad = r.range(44, 84), kind = r() < 0.5 ? 'cat' : 'dog';
180
+ const col = kind === 'cat' ? (r() < 0.75 ? PAL.ochre : PAL.sand) : (r() < 0.75 ? PAL.indigo : PAL.sky);
181
+ const ti = t0 + dur * Math.pow(i / n, 0.85); if (t < ti) break;
182
+ const g = H.ease.out(PX(t, ti, ti + 0.3));
183
+ applyOp(polys, T.drop({ x, y, r: Math.max(0.5, rad * g) }), ML); polys.push({ pts: circle(x, y, Math.max(0.5, rad * g)), col, halo: halo && t - ti < 0.7 }); // gall halo only while the drop is fresh
184
+ landed.push({ x, y, kind, ti });
185
+ }
186
+ return landed;
187
+ }
188
+ function probe(polys, x, y, ML, g = 1) {
189
+ [PAL.red, PAL.cream, PAL.red, PAL.cream, PAL.red].forEach((col, i) => { const rad = Math.max(0.5, (i === 0 ? 40 : 19) * g); applyOp(polys, T.drop({ x, y, r: rad }), ML); polys.push({ pts: circle(x, y, rad), col, probe: true }); });
190
+ }
191
+ function centroid(polys) { let sx = 0, sy = 0, n = 0; for (const P of polys) if (P.probe) for (let i = 0; i < P.pts.length; i += 2) { sx += P.pts[i]; sy += P.pts[i + 1]; n++; } return [sx / n, sy / n]; }
192
+ function flowX(polys, t) { // horizontal tine through the probe = the model's flow
193
+ return polys;
194
+ }
195
+
196
+ // ---------- scenes ----------
197
+ function mHook(ctx, W, H_, t) {
198
+ const z = 1.08 - 0.06 * H.ease.out(PX(t, 0, 2.4));
199
+ ctx.save(); ctx.translate(W / 2, H_ / 2); ctx.scale(z, z); ctx.translate(-W / 2, -H_ / 2); ctx.drawImage(pmStill(W, H_), 0, 0); ctx.restore();
200
+ const k = H.ease.inOut(PX(t, 1.75, 2.3)), pop = H.ease.back(PX(t, 0.15, 0.5));
201
+ const hs = HEAD / 64, hcx = 24 + textW(ctx, 'How does AI learn?', HEAD, true) / 2;
202
+ if (k < 1) { ctx.save(); ctx.translate(640 + (hcx - 640) * k, 360 + (42 - 360) * k); ctx.scale(pop * (1 - (1 - hs) * k), pop * (1 - (1 - hs) * k)); blabel(ctx, 'How does AI learn?', 0, 0, { size: 64, italic: true }); ctx.restore(); }
203
+ else pmHeader(ctx);
204
+ }
205
+ function mData(ctx, W, H_, t) {
206
+ const polys = [], ML = 4;
207
+ const landed = battal(polys, t, 95, 2.5, 3.1, ML, true);
208
+ renderPolys(ctx, W, H_, polys, PAL.cream); finish(ctx, W, H_);
209
+ // the most recent drops carry their labels
210
+ const placed = [];
211
+ landed.slice(-3).forEach((d, i) => {
212
+ let lx = H.clamp(d.x, 80, 1200), ly = H.clamp(d.y - 80, 200, 470);
213
+ for (let pass = 0; pass < 3; pass++) for (const q of placed) if (Math.abs(q[0] - lx) < 60 + 50 && Math.abs(q[1] - ly) < 60) ly = q[1] - 58; // stack above a crowding label
214
+ placed.push([lx, ly]);
215
+ const a = H.clamp(1 - (t - d.ti - 0.5) / 0.4); if (a > 0) { ctx.save(); ctx.globalAlpha = a; blabel(ctx, d.kind, lx, ly, { size: 28 }); ctx.restore(); }
216
+ });
217
+ dropLabel(ctx, t, 2.9, 'the bath = the model', 960, 110, { size: 30 });
218
+ dropLabel(ctx, t, 3.2, 'ochre = cat · indigo = dog', 1000, 650, { size: 28, italic: true });
219
+ if (t > 2.65) {
220
+ const p = PX(t, 2.7, 5.0), n = p >= 1 ? 1e6 : Math.floor(Math.pow(10, 6 * H.ease.inOut(p))) - 1;
221
+ const w = blabel(ctx, Math.round(n).toLocaleString('en-US'), 40, 600, { size: 64, align: 'left' }); blabel(ctx, 'examples', 40 + w + 14, 610, { size: 30, align: 'left', italic: true });
222
+ }
223
+ pmHeader(ctx);
224
+ }
225
+ function mLoop(ctx, W, H_, t) {
226
+ const polys = [], ML = 4;
227
+ battal(polys, 99, 95, 0, 0.01, ML);
228
+ const adj = PX(t, 8.95, 9.45);
229
+ if (adj > 0) applyOp(polys, T.comb({ vertical: true, S: 90, off: 20, a: 60 * H.ease.inOut(adj), l: 22 }), ML); // adjust: the comb orders the bath
230
+ const pg = PX(t, 6.35, 6.6); if (pg > 0) probe(polys, 640, 330, ML, H.ease.out(pg));
231
+ const f1 = H.ease.inOut(PX(t, 6.8, 7.3)), back = H.ease.inOut(PX(t, 8.35, 8.85)), f2 = H.ease.inOut(PX(t, 9.45, 9.85));
232
+ if (f1 > 0) applyOp(polys, T.tine({ x: 0, y: 330, mx: 1, my: 0, a: 300 * f1, l: 40 }), ML); // guess: flow drags probe toward DOG
233
+ if (back > 0) applyOp(polys, T.tine({ x: 0, y: 330, mx: -1, my: 0, a: 300 * back, l: 40 }), ML); // error: counter-stroke drags it back
234
+ if (f2 > 0) applyOp(polys, T.tine({ x: 0, y: 330, mx: -1, my: 0, a: 330 * f2, l: 40 }), ML); // guess again: now flows to CAT
235
+ renderPolys(ctx, W, H_, polys, PAL.cream); finish(ctx, W, H_);
236
+ const right = t >= 9.85, [px, py] = pg > 0 ? centroid(polys) : [640, 330];
237
+ blabel(ctx, '◀ CAT', 30, 330, { size: 36, align: 'left', red: right });
238
+ blabel(ctx, 'DOG ▶', 1250, 330, { size: 36, align: 'right', red: t >= 7.3 && t < 8.85 });
239
+ dropLabel(ctx, t, 6.35, t >= 8.0 ? 'answer: cat' : 'new picture', 640, 450, { size: 28, italic: true, red: t >= 8.0 });
240
+ if (t >= 6.8 && t < 7.3) inkArrow(ctx, [[600, 250], [900, 250]], PX(t, 6.8, 7.2));
241
+ if (t >= 7.3 && !right) dropLabel(ctx, t, 7.3, 'guess: dog?', 1010, 215, { size: 34 });
242
+ if (t >= 7.85 && !right) { const k = H.ease.out(PX(t, 7.85, 8.05)); ctx.save(); ctx.strokeStyle = REDI; ctx.lineWidth = 6; ctx.lineCap = 'round'; ctx.beginPath(); ctx.ellipse(1150, 330, 140, 70, -0.05, -1.4, -1.4 + 6.4 * k); ctx.stroke(); ctx.restore(); dropLabel(ctx, t, 7.95, 'check: wrong ✗', 1060, 445, { size: 30, red: true }); }
243
+ if (t >= 8.35 && t < 9.45) { inkArrow(ctx, [[960, 395], [600, 395]], PX(t, 8.35, 8.8), REDI, 6); dropLabel(ctx, t, 8.4, 'error: pull back', 700, 520, { size: 28, red: true }); }
244
+ if (t >= 8.95 && t < 10.5) dropLabel(ctx, t, 8.95, 'adjust: comb the bath', 640, 120, { size: 32, red: true });
245
+ if (right) dropLabel(ctx, t, 9.85, 'cat ✓', 300, 220, { size: 44, red: true });
246
+ const words = ['guess', 'check', 'adjust'], act = t < 7.3 ? -1 : t < 7.85 ? 0 : t < 8.35 ? 1 : t < 9.45 ? 2 : Math.floor((t - 9.45) / 0.16) % 3;
247
+ let x = 120; words.forEach((w, i) => { x += blabel(ctx, w, x, 630, { size: 32, align: 'left', red: i === act }) + 12; if (i < 2) { inkArrow(ctx, [[x, 630], [x + 52, 630]], 1, INKB, 4.5); x += 68; } });
248
+ blabel(ctx, '↻ ×1,000,000', x + 6, 630, { size: 28, italic: true, align: 'left' });
249
+ pmHeader(ctx);
250
+ }
251
+ function mProg(ctx, W, H_, t) {
252
+ const polys = [], ML = 4;
253
+ battal(polys, 99, 95, 0, 0.01, ML);
254
+ const p = H.ease.inOut(PX(t, 10.85, 12.3)), rounds = Math.floor(p * 5);
255
+ // each training round is one more, finer comb pass: chaos combs into order
256
+ const combs = [{ vertical: true, S: 90, off: 20, a: 60, l: 22 }, { vertical: false, S: 120, off: 40, a: 28, l: 30 }, { vertical: true, S: 45, off: 10, a: 22, l: 12 }];
257
+ for (let k = 0; k < Math.min(3, rounds); k++) applyOp(polys, T.comb(combs[k]), ML);
258
+ const sub = H.clamp(p * 5 - rounds); if (rounds < 3) applyOp(polys, T.comb({ ...combs[rounds], a: combs[rounds].a * H.ease.inOut(sub) }), ML);
259
+ renderPolys(ctx, W, H_, polys, PAL.cream); finish(ctx, W, H_);
260
+ // card with accuracy + ink chart
261
+ ctx.save(); ctx.fillStyle = 'rgba(30,20,10,0.25)'; ctx.fillRect(84, 124, 520, 470); ctx.fillStyle = '#f3ead2'; ctx.fillRect(80, 120, 520, 470);
262
+ ctx.strokeStyle = INKB; ctx.lineWidth = 1.6; ctx.strokeRect(86, 126, 508, 458); ctx.lineWidth = 0.6; ctx.strokeRect(90, 130, 500, 450); ctx.restore();
263
+ ctx.save(); ctx.font = 'italic 700 36px "Libre Baskerville"'; ctx.fillStyle = INKB; ctx.fillText('Accuracy', 120, 188);
264
+ const v = Math.round(61 + 36 * p), punch = t >= 12.3 ? 1 + 0.12 * Math.exp(-(t - 12.3) * 9) : 1;
265
+ ctx.translate(120, 320); ctx.scale(punch, punch); ctx.font = '700 120px "Libre Baskerville"'; ctx.fillText(v + '%', 0, 0); ctx.restore();
266
+ if (t >= 12.3) { const k = H.ease.out(PX(t, 12.3, 12.55)); ctx.save(); ctx.strokeStyle = REDI; ctx.lineWidth = 6; ctx.lineCap = 'round'; ctx.beginPath(); ctx.ellipse(300, 278, 205, 85, -0.04, -2.6, -2.6 + 6.5 * k); ctx.stroke(); ctx.restore(); }
267
+ const x0 = 130, y0 = 545, cw = 420, ch = 160, n = 6, pts = [];
268
+ for (let i = 0; i < n; i++) { const acc = 61 + 36 * (1 - Math.exp(-i / 1.6)) / (1 - Math.exp(-(n - 1) / 1.6)); pts.push([x0 + (i / (n - 1)) * cw, y0 - ((acc - 55) / 45) * ch]); }
269
+ ctx.save(); ctx.strokeStyle = INKB; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x0, y0 - ch - 10); ctx.lineTo(x0, y0); ctx.lineTo(x0 + cw + 10, y0); ctx.stroke(); ctx.restore();
270
+ inkArrow(ctx, pts, PX(t, 10.9, 12.3), REDI, 3.5);
271
+ ctx.save(); ctx.font = 'italic 400 28px "Libre Baskerville"'; ctx.fillStyle = INKB; ctx.fillText('training rounds →', x0, y0 + 32); ctx.restore();
272
+ dropLabel(ctx, t, 10.75, `comb pass ${Math.min(5, rounds + 1)}: chaos → order`, 940, 640, { size: 28, italic: true });
273
+ pmHeader(ctx);
274
+ }
275
+ function mTake(ctx, W, H_, t) {
276
+ ctx.drawImage(pmStill(W, H_), 0, 0);
277
+ const k = H.ease.back(PX(t, 13.25, 13.6));
278
+ ctx.save(); ctx.translate(640, 360); ctx.scale(k, k);
279
+ blabel(ctx, 'More examples', 0, -56, { size: 56, italic: true });
280
+ blabel(ctx, '→ better guesses', 0, 60, { size: 56, italic: true, red: true });
281
+ ctx.restore();
282
+ }
283
+ function explainPM(ctx, W, H_, t) {
284
+ const seq = [[0, mHook], [2.4, mData], [6.0, mLoop], [10.5, mProg], [13.0, mTake]];
285
+ let i = 0; for (let k = 0; k < seq.length; k++) if (t >= seq[k][0]) i = k;
286
+ const t0 = seq[i][0], D = 0.55;
287
+ if (i > 0 && t < t0 + D) {
288
+ // the printed sheet is lifted off the bath, peeling from the left edge
289
+ seq[i][1](ctx, W, H_, t);
290
+ const A = H.layer(W, H_); seq[i - 1][1](A.getContext('2d'), W, H_, t0 - 0.01);
291
+ const lx = -80 + (W + 200) * H.ease.inOut(PX(t, t0, t0 + D)), curl = 90;
292
+ ctx.save(); ctx.fillStyle = 'rgba(20,10,0,0.35)'; ctx.fillRect(lx, 0, 40, H_); ctx.restore(); // shadow of the lifted sheet
293
+ ctx.drawImage(A, lx + curl, 0, W, H_, lx + curl, 0, W, H_);
294
+ const g = ctx.createLinearGradient(lx, 0, lx + curl, 0); g.addColorStop(0, '#d9ccad'); g.addColorStop(0.55, '#f5ecd6'); g.addColorStop(1, '#bfae8a');
295
+ ctx.fillStyle = g; ctx.fillRect(lx, 0, curl, H_); // the paper's back, curling up
296
+ } else seq[i][1](ctx, W, H_, t);
297
+ }
298
+
299
+ registerStyle({
300
+ id: 'paper-marbling',
301
+ name: 'Ebru Paper Marbling',
302
+ category: 'Generative & Type',
303
+ description: 'Turkish ebru on laid paper: a battal stone ground of floated pigments raked into gel-git chevrons and combed, then suminagashi ring-drops pulled by a stylus into tulips — built with the exact drop and tine transforms.',
304
+ motion: 'Colour drops rain onto the size and bloom, shoving earlier drops into veins; the rake drags the stone into gel-git flames; ring after ring is dropped at one point; then the stylus pulls the rings into a tulip, and a wet sheen glides over the finished bath.',
305
+ fonts: ['700 22px "Libre Baskerville"', 'italic 700 22px "Libre Baskerville"', 'italic 400 15px "Libre Baskerville"'],
306
+ duration: 8,
307
+ frameT: 7.9,
308
+ readsPixels: true,
309
+ explainerDuration: 14.5,
310
+ explainerNote: 'The bath is the model: examples land as colour drops, a new picture is a red probe drop that the flow drags toward its guess, the error is a stylus counter-stroke and the adjustment a comb pass that puts the pattern in order; scenes change by lifting the printed sheet off the bath, labels are bookbinder paper labels and arrows are inked stylus strokes.',
311
+ explainer(ctx, W, H_, t) { explainPM(ctx, W, H_, t); },
312
+ // Sound: the marbling bath. Colour drops land with small low water plops (pitch from drop size, pan from position,
313
+ // throttled), the stylus and its counter-stroke are soft watery drags across the size, comb passes are a fuller
314
+ // swish with the tines riffling through, scene changes are the wet sheet peeled off the bath, and the bookbinder
315
+ // labels set down with a soft paper tap. A quiet pad sits under it. Timings mirror explainPM / mHook..mTake above.
316
+ explainerSound(k) {
317
+ const D = 14.5, E = H.ease;
318
+ const pan = (px) => H.clamp((px / 1280) * 2 - 1, -1, 1) * 0.7;
319
+ const when = (t0, t1, ease, u) => { for (let i = 0; i <= 400; i++) { const t = t0 + ((t1 - t0) * i) / 400; if (ease(i / 400) >= u - 1e-6) return t; } return t1; };
320
+ // a colour drop meeting the size: short low bubble plop (stays under ~1 kHz) with a soft body
321
+ const plop = (t, f, g, p) => { k.tone(t, { f: [[0, f], [0.045, f * 2.1]], dur: 0.075, gain: 0.13 * g, a: 0.002, pan: p }); k.tone(t, { f: [[0, 140], [0.06, 90]], dur: 0.08, gain: 0.1 * g, pan: p }); k.noise(t, { dur: 0.03, filter: 'lowpass', f: 700, gain: 0.08 * g, a: 0.001, pan: p }); };
322
+ // label set down on the bench
323
+ const label = (t0, p, g = 1) => { const t = t0 + 0.06; k.withMinor(() => { k.noise(t, { dur: 0.05, filter: 'bandpass', f: 1000, q: 0.7, gain: 0.12 * g, a: 0.003, pan: p }); k.tone(t, { f: 150, dur: 0.05, gain: 0.07 * g, pan: p }); }); };
324
+ // stylus dragged through the bath from pan a to b over dur
325
+ const drag = (t, dur, a, b, g = 1) => {
326
+ const steps = 6; for (let i = 0; i < steps; i++) { const u = i / steps; k.noise(t + u * dur, { dur: dur / steps + 0.08, color: 'pink', filter: 'bandpass', f: 380 + 380 * Math.sin(Math.PI * (u + 0.08)), q: 1.6, gain: 0.13 * g * Math.sin(Math.PI * (u + 0.1)), a: 0.04, pan: a + (b - a) * u }); }
327
+ k.tone(t + dur * 0.3, { f: [[0, 220], [0.05, 420]], dur: 0.07, gain: 0.05 * g, pan: a + (b - a) * 0.3, minor: true });
328
+ k.tone(t + dur * 0.7, { f: [[0, 260], [0.05, 500]], dur: 0.07, gain: 0.04 * g, pan: a + (b - a) * 0.7, minor: true });
329
+ };
330
+ // comb pass: a swish with the tines riffling through the size
331
+ const comb = (t, dur, g = 1) => {
332
+ k.noise(t, { dur: dur + 0.1, color: 'pink', filter: 'lowpass', f: [[0, 500], [dur * 0.5, 1100], [dur + 0.1, 400]], gain: 0.13 * g, a: dur * 0.35 });
333
+ const r = H.rng(Math.round(t * 100)); k.withMinor(() => { for (let x = 0.03; x < dur; x += 0.045 + r() * 0.03) k.noise(t + x, { dur: 0.03, filter: 'bandpass', f: 600 + r() * 500, q: 2, gain: 0.07 * g * Math.sin(Math.PI * x / dur), a: 0.008, pan: (x / dur - 0.5) * 0.8 }); });
334
+ };
335
+ // wet sheet peeled off the bath, left edge first (D = 0.55, inOut)
336
+ const peel = (t0) => {
337
+ const r = H.rng(Math.round(t0 * 10));
338
+ k.noise(t0, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 500], [0.3, 1200], [0.6, 600]], q: 0.9, gain: 0.12, a: 0.25 });
339
+ k.withMinor(() => { for (let x = 0.04; x < 0.55; x += 0.025 + r() * 0.03) { const u = x / 0.55; k.noise(t0 + x, { dur: 0.025, filter: 'bandpass', f: 700 + r() * 800, q: 1.2, gain: 0.1 * Math.sin(Math.PI * u) + 0.02, a: 0.003, pan: -0.7 + 1.4 * E.inOut(u) }); } });
340
+ k.noise(t0 + 0.5, { dur: 0.12, color: 'brown', filter: 'lowpass', f: 300, gain: 0.12, a: 0.01 }); // last of it lets go of the size
341
+ };
342
+ const busy = []; // drop times: counter clicks step around them so neither masks the other
343
+ const counter = (t0, t1, ease, steps, rate, p) => { let last = -1, prev = 0;
344
+ for (let t = t0; t <= t1; t += 0.002) { const s = Math.floor(ease(H.clamp((t - t0) / (t1 - t0))) * steps); if (s !== prev && t - last >= 1 / rate && !busy.some((b) => t > b - 0.02 && t < b + 0.07)) { k.play('tock', t, { f: 700, gain: 0.38, pan: p, minor: true }); last = t; } prev = s; } };
345
+
346
+ k.bed('room', 0, D, { gain: 0.3 });
347
+ k.music('pad', 0, D, { bpm: 64, gain: 0.35, chords: [['D3', 'A3', 'C4', 'F4'], ['G2', 'D3', 'B3', 'F4'], ['C3', 'G3', 'C4', 'E4'], ['A2', 'E3', 'A3', 'C4']] });
348
+
349
+ // ---- hook: title label pops (back 0.15-0.5), slides to the corner (1.75-2.3)
350
+ label(when(0.15, 0.5, E.back, 1) - 0.06, 0, 1.6);
351
+ k.noise(1.75, { dur: 0.55, color: 'pink', filter: 'lowpass', f: [[0, 600], [0.55, 280]], gain: 0.028, a: 0.2, minor: true });
352
+
353
+ // ---- data: peel, then 95 drops at ti = 2.5 + 3.1 (i/95)^0.85 (same seeded draws as battal), labels, counter
354
+ peel(2.4);
355
+ { const r = H.rng(77); let last = -1;
356
+ for (let i = 0; i < 95; i++) {
357
+ const x = r.range(-40, 1320); r.range(-40, 760); const rad = r.range(44, 84); r(); r();
358
+ const ti = 2.5 + 3.1 * Math.pow(i / 95, 0.85);
359
+ if (ti - last < (i < 4 ? 0.1 : 0.16)) continue; last = ti; busy.push(ti);
360
+ k.withMinor(() => plop(ti, 520 - rad * 3, i < 4 ? 1.2 : 0.8, pan(x)));
361
+ } }
362
+ label(2.9, 0.5); label(3.2, 0.55);
363
+ counter(2.72, 4.98, E.inOut, 30, 8, -0.6);
364
+ label(4.94, -0.6, 1.5); k.tone(5.0, { f: 'D3', dur: 0.7, gain: 0.1, a: 0.01, verb: 0.3, pan: -0.5 }); // 1,000,000 lands
365
+
366
+ // ---- loop: peel, probe drop, guess drag, wrong, counter-stroke, comb, guess again, cat ✓
367
+ peel(6.0);
368
+ plop(6.35, 300, 1.4, 0); plop(6.42, 380, 0.7, 0); plop(6.5, 420, 0.5, 0); // the red ring drops
369
+ label(6.35, 0);
370
+ drag(6.8, 0.5, -0.1, 0.6); // the flow pulls the probe toward DOG
371
+ label(7.3, 0.5);
372
+ k.play('stroke', 7.85, { material: 'brush', dur: 0.2, gain: 1.4, pan: pan(1150), minor: true }); // red ring round DOG
373
+ label(7.95, 0.5, 1.3);
374
+ k.play('tock', 7.97, { f: 300, gain: 0.7, pan: pan(1100) }); k.play('tock', 8.13, { f: 225, gain: 0.7, pan: pan(1100) }); // check: wrong
375
+ k.play('thud', 7.97, { f: 100, gain: 0.45, pan: pan(1100) });
376
+ drag(8.35, 0.5, 0.6, -0.1, 1.1); // counter-stroke pulls it back
377
+ label(8.4, 0.1);
378
+ label(8.95, 0);
379
+ comb(8.95, 0.5, 0.75); // adjust: the comb orders the bath
380
+ drag(9.45, 0.4, 0, -0.6); // now the flow pulls it to CAT
381
+ label(9.85, -0.4, 1.3);
382
+ ['G4', 'D5'].forEach((n, i) => k.play('ding', 9.88 + i * 0.09, { f: n, gain: 0.5, pan: -0.4 })); // cat ✓
383
+
384
+ // ---- progress: peel, comb passes (rounds = floor(5 inOut(p))), red ink chart, counter, 97%
385
+ peel(10.5);
386
+ label(10.75, 0.4);
387
+ for (let r = 0; r < 3; r++) { const a = when(10.85, 12.3, E.inOut, r / 5 + 1e-4), b = when(10.85, 12.3, E.inOut, (r + 1) / 5); comb(a, b - a, 0.6 - r * 0.12); busy.push(a + (b - a) / 2); }
388
+ k.play('stroke', 10.9, { material: 'brush', dur: 1.4, gain: 0.35, pan: -0.4, minor: true });
389
+ counter(10.86, 12.28, E.inOut, 36, 8, -0.5);
390
+ k.play('impact', 12.3, { gain: 1.0, pan: -0.4 });
391
+ plop(12.3, 180, 2.2, -0.3);
392
+ ['D3', 'A3', 'F#4', 'A4'].forEach((n, i) => k.tone(12.32 + i * 0.03, { f: n, type: 'triangle', dur: 1.3, gain: 0.07, a: 0.01, verb: 0.35, filter: 'lowpass', f2: 1800, pan: (i - 1.5) * 0.25 })); // the 97% bloom
393
+ k.play('stroke', 12.32, { material: 'brush', dur: 0.24, gain: 0.8, pan: -0.4, minor: true }); // red ring
394
+
395
+ // ---- takeaway: peel back to the finished sheet, labels pop, resolve
396
+ peel(13.0);
397
+ const tk = when(13.25, 13.6, E.back, 1);
398
+ label(tk - 0.06, 0, 1.6);
399
+ ['G2', 'D3', 'B3', 'D4', 'G4'].forEach((n, i) => k.tone(tk + 0.05 + i * 0.05, { f: n, type: 'triangle', dur: 1.2, gain: 0.07, a: 0.01, verb: 0.35, filter: 'lowpass', f2: 1800, pan: (i - 2) * 0.2 }));
400
+ },
401
+ draw(ctx, W, H_, t) {
402
+ const r = H.rng(2024);
403
+ const polys = [];
404
+ const ML = 3.2;
405
+ const drop = (x, y, rad, col) => {
406
+ applyOp(polys, T.drop({ x, y, r: rad }), ML);
407
+ polys.push({ pts: circle(x, y, rad), col });
408
+ };
409
+ // a drop lands at time ti and blooms outward (radius eases to full) — later drops wait
410
+ const land = (ti, x, y, rad, col, dur = 0.3) => {
411
+ if (t < ti) return;
412
+ const g = H.ease.out(H.clamp((t - ti) / dur));
413
+ drop(x, y, Math.max(0.5, rad * g), col);
414
+ };
415
+ const P = (a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
416
+ const op = (k, f) => { if (k > 1e-4) applyOp(polys, f, ML); };
417
+ // 1) battal ground: rows of drops sprinkled from the brush
418
+ const ground = ['indigo', 'sand', 'sky', 'indigo', 'cream', 'teal', 'indigo', 'slate', 'sky', 'indigo', 'sand', 'ochre'];
419
+ for (let i = 0; i < 270; i++) {
420
+ land(0.05 + 2.1 * Math.pow(i / 270, 0.9), r.range(-80, W + 80), r.range(-80, H_ + 80), r.range(28, 64), PAL[ground[i % ground.length]]);
421
+ }
422
+ // fine 'eyes' sprinkled into the stone before raking (become streaks & veins)
423
+ for (let i = 0; i < 160; i++) land(2.15 + 0.4 * (i / 160), r.range(-40, W + 40), r.range(-40, H_ + 40), r.range(3, 11), r.chance(0.55) ? PAL.cream : r.chance(0.5) ? PAL.ochre : PAL.ink, 0.15);
424
+ // 2) gel-git: coarse alternating rake top<->bottom, then a wave, then the fine comb (tarak)
425
+ const ph = 0.36;
426
+ const rake = P(2.6, 3.7), wv = P(3.2, 4.0);
427
+ op(rake, T.comb({ vertical: true, S: 80, off: 20 + ph * 30, a: 70 * rake, l: 22 }));
428
+ op(wv, T.wave({ vertical: false, A: 18 * wv, L: 260, ph: 0.8 + ph }));
429
+
430
+ // 3) sprinkle of small dark drops (opens tiny eyes in the ground)
431
+ for (let i = 0; i < 40; i++) land(4.0 + 0.35 * (i / 40), r.range(0, W), r.range(0, H_), r.range(4, 9), r.chance(0.5) ? PAL.cream : PAL.ink, 0.12);
432
+ // 4) focal tulip: concentric suminagashi rings dropped at one point
433
+ const fx = 640, fy = 250;
434
+ const rings = ['ochre', 'red', 'cream', 'red', 'cream', 'ochre', 'cream', 'red', 'ink', 'cream', 'red', 'cream', 'teal', 'cream', 'red', 'ochre', 'cream', 'red', 'cream', 'ink', 'cream'];
435
+ rings.forEach((c, i) => land(4.35 + i * 0.045, fx, fy, i === 0 ? 34 : 24, PAL[c], 0.12));
436
+ // side buds
437
+ [[330, 420], [950, 420]].forEach(([bx, by], k) => {
438
+ ['red', 'cream', 'ochre', 'cream', 'red', 'cream', 'ink', 'cream', 'red'].forEach((c, i) => land(4.6 + k * 0.08 + i * 0.05, bx, by, i === 0 ? 20 : 15, PAL[c], 0.12));
439
+ });
440
+ // stem + leaves: a column of small green drops and two leaf drops
441
+ for (let y = 470; y < 700; y += 24) land(5.2 + (y - 470) / 230 * 0.25, fx + Math.sin(y * 0.02) * 4, y, 9, PAL.green, 0.12);
442
+ land(5.45, fx - 62, 560, 26, PAL.green, 0.2); land(5.5, fx + 62, 600, 24, PAL.green, 0.2);
443
+ // 5) stylus: pull through each flower to form the tulip; stem and leaves pulled too
444
+ const p0 = P(5.6, 6.4), p1 = P(5.9, 6.5), p2 = P(6.05, 6.6), p3 = P(6.3, 6.8), swl = P(6.4, 7.0);
445
+ op(p0, T.tine({ x: fx, y: 0, mx: 0, my: 1, a: 260 * p0, l: 16 }));
446
+ op(p1, T.tine({ x: 330, y: 0, mx: 0, my: -1, a: 120 * p1, l: 10 }));
447
+ op(p2, T.tine({ x: 950, y: 0, mx: 0, my: -1, a: 120 * p2, l: 10 }));
448
+ op(p3, T.tine({ x: fx - 62, y: 560, mx: -0.6, my: -0.8, a: 70 * p3, l: 12 }));
449
+ op(p3, T.tine({ x: fx + 62, y: 600, mx: 0.6, my: -0.8, a: 70 * p3, l: 12 }));
450
+ // a slight lateral swirl as the sheet is laid (keeps it from looking rigid)
451
+ op(swl, T.wave({ vertical: true, A: 5 * swl, L: 420, ph: 1.7 }));
452
+
453
+ // ---------- render ----------
454
+ ctx.fillStyle = PAL.sand; ctx.fillRect(0, 0, W, H_);
455
+ for (const P of polys) {
456
+ if (P.dead) continue;
457
+ const p = P.pts;
458
+ ctx.beginPath(); ctx.moveTo(p[0], p[1]);
459
+ for (let i = 2; i < p.length; i += 2) ctx.lineTo(p[i], p[i + 1]);
460
+ ctx.closePath();
461
+ ctx.fillStyle = P.col; ctx.fill();
462
+ // pigment concentrates at the rim of each floated drop: its own colour, darker
463
+ ctx.strokeStyle = rimOf(P.col); ctx.lineWidth = 1; ctx.stroke();
464
+ }
465
+
466
+ ctx.save(); ctx.filter = 'blur(0.6px)'; ctx.drawImage(ctx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height, 0, 0, W, H_); ctx.restore();
467
+ // ---------- flow-dragged pigment speckle + paper ----------
468
+ // The rake is a vertical comb, so its inverse shift depends on x only: one combShift per column, and the
469
+ // speckle noise is looked up from a cached pre-comb field (same formula as sampling it per pixel).
470
+ const SPM = 260;
471
+ if (!PM_SPK || PM_SPK.W !== W) {
472
+ const nz = H.makeNoise(3), nz2 = H.makeNoise(8), rr = H.rng(9), RH = H_ + SPM * 2;
473
+ const sp = new Float32Array(W * RH), lr = new Float32Array(W * H_);
474
+ for (let y = 0; y < RH; y++) for (let x = 0; x < W; x++) { const py = y - SPM; sp[y * W + x] = (nz.n2(x * 0.08, py * 0.08) * 0.55 + nz2.n2(x * 0.35, py * 0.35) * 0.45) * 0.12; }
475
+ for (let y = 0; y < H_; y++) for (let x = 0; x < W; x++) lr[y * W + x] = (x % 3 === 0 ? -0.025 : 0) + ((x + 9) % 64 < 1.5 ? -0.03 : 0) + (rr() - 0.5) * 0.04; // laid lines, fibre tooth
476
+ PM_SPK = { W, sp, lr };
477
+ }
478
+ const img = ctx.getImageData(0, 0, W, H_), d = img.data;
479
+ const combA = { vertical: true, S: 80, off: 20 + ph * 30, a: 70 * rake, l: 22 };
480
+ const shiftX = new Int32Array(W);
481
+ for (let x = 0; x < W; x++) shiftX[x] = Math.max(-SPM, Math.min(SPM, Math.round(-combShift(x, combA))));
482
+ const { sp, lr } = PM_SPK;
483
+ for (let y = 0; y < H_; y++) {
484
+ for (let x = 0; x < W; x++) {
485
+ const k = 1 + sp[(y + shiftX[x] + SPM) * W + x] + lr[y * W + x];
486
+ const o = (y * W + x) * 4;
487
+ d[o] *= k; d[o + 1] *= k; d[o + 2] *= k;
488
+ }
489
+ }
490
+ ctx.putImageData(img, 0, 0);
491
+ // cockle: broad soft light/dark undulation from the wetted sheet drying
492
+ ctx.save(); ctx.globalCompositeOperation = 'soft-light'; ctx.imageSmoothingQuality = 'high'; ctx.drawImage(pmCockle(W, H_), 0, 0, W, H_); ctx.restore();
493
+ // wet sheen gliding across the bath once the pattern is finished
494
+ const gs = H.clamp((t - 6.7) / 1.3);
495
+ if (gs > 0) {
496
+ const gx = -400 + 2100 * H.ease.inOut(gs);
497
+ const lg = ctx.createLinearGradient(gx - 260, 0, gx + 260, 200);
498
+ lg.addColorStop(0, 'rgba(255,250,235,0)'); lg.addColorStop(0.5, 'rgba(255,250,235,0.13)'); lg.addColorStop(1, 'rgba(255,250,235,0)');
499
+ ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.fillStyle = lg; ctx.fillRect(0, 0, W, H_); ctx.restore();
500
+ }
501
+ H.vignette(ctx, W, H_, 0.2, '40,25,10');
502
+ },
503
+ });
504
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": 54,
3
+ "name": "Ebru Paper Marbling",
4
+ "nameZh": "土耳其水拓画",
5
+ "family": "Generative & Type",
6
+ "familyZh": "生成艺术与字体",
7
+ "description": "Turkish ebru on laid paper: a battal stone ground of floated pigments raked into gel-git chevrons and combed, then suminagashi ring-drops pulled by a stylus into tulips — built with the exact drop and tine transforms.",
8
+ "motionNotes": "Motion: Colour drops rain onto the size and bloom, shoving earlier drops into veins; the rake drags the stone into gel-git flames; ring after ring is dropped at one point; then the stylus pulls the rings into a tulip, and a wet sheen glides over the finished bath.",
9
+ "explainerNotes": "Explainer: The bath is the model: examples land as colour drops, a new picture is a red probe drop that the flow drags toward its guess, the error is a stylus counter-stroke and the adjustment a comb pass that puts the pattern in order; scenes change by lifting the printed sheet off the bath, labels are bookbinder paper labels and arrows are inked stylus strokes.",
10
+ "code": "code.js",
11
+ "registeredAs": "paper-marbling",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }