@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,645 @@
1
+ {
2
+ // String art: brass-nail lightbulb on a stained walnut board. Threads are straight chords between
3
+ // nails; curves emerge only as envelopes (cardioid filament, chord-ring halo, cross-hatched base).
4
+ const TAU = Math.PI * 2;
5
+
6
+ function woodBoard(ctx, W, H_, t) {
7
+ const img = ctx.createImageData(W, H_), d = img.data;
8
+ const nz = H.makeNoise(23), nz2 = H.makeNoise(91), nz3 = H.makeNoise(57), r = H.rng(4);
9
+ const planks = [0, 236, 486, 720];
10
+ const dark = [50, 27, 14], mid = [116, 70, 39], light = [186, 128, 78];
11
+ const yh = (n) => { const v = Math.sin(n * 12.9898 + 78.233) * 43758.5453; return v - Math.floor(v); };
12
+ for (let p = 0; p < 3; p++) {
13
+ const y0 = planks[p], y1 = planks[p + 1], off = p * 137.3, tone = [0.95, 1.05, 0.9][p];
14
+ for (let y = y0; y < y1; y++) {
15
+ for (let x = 0; x < W; x++) {
16
+ // flat-sawn figure: parabolic "cathedral" arches around a per-plank pith, warped by noise
17
+ const px = [380, 900, 560][p], c = [0.0011, -0.0008, 0.0013][p];
18
+ const wv = nz.n2(x * 0.0014 + off, y * 0.008) * 30 + nz.n2(x * 0.006 + off, y * 0.02) * 2.5 + nz2.n2(x * 0.0007 + off, y * 0.003) * 40;
19
+ let gg = (y - y0 - (y1 - y0) * 0.5) + (x - px) * (x - px) * c + wv;
20
+ // growth rate wanders, so the rings bunch up and spread out instead of marching evenly
21
+ const rate = 0.8 + 0.45 * (nz3.n2(x * 0.0011 + off, y * 0.006) * 0.5 + 0.5);
22
+ gg = Math.abs(gg) * 0.055 * rate + 0.6 * Math.sin(gg * 0.013 + p) + 0.25 * nz2.n2(x * 0.004, y * 0.05);
23
+ const yr = Math.floor(gg), fr = gg - yr, h1 = yh(yr + p * 31), h2 = yh(yr * 1.7 + p * 13 + 5);
24
+ // asymmetric ring: earlywood darkens gradually into a latewood band that ends sharply; width and
25
+ // darkness vary year to year
26
+ const lw = 0.5 + 0.32 * h1;
27
+ let ring = H.smooth(lw - 0.22, 0.95, fr) * (1 - H.smooth(0.965, 1, fr)) * (0.45 + 0.65 * h2);
28
+ const fine = nz2.n2(x * 0.015, y * 0.7 + off) * 0.5 + 0.5; // fine streaks along the grain
29
+ const pore = H.smooth(0.42, 0.72, nz3.n2(x * 0.045 + off, y * 1.25 + off)); // short open-pore flecks
30
+ const fig = nz.n2(x * 0.0012 + 3, y * 0.006 + off) * 0.5 + 0.5;
31
+ const streak = nz3.n2(x * 0.0022 + off, y * 0.03 + 7); // long colour streaks along the plank
32
+ ring *= 0.6 + 0.6 * fig;
33
+ let k = 0.47 + fig * 0.46 + streak * 0.09 - ring * 0.27 - fine * 0.1 - pore * (0.1 + ring * 0.1);
34
+ k = H.clamp(k, 0, 1);
35
+ const c0 = k < 0.5 ? dark : mid, c1 = k < 0.5 ? mid : light, kk = k < 0.5 ? k * 2 : (k - 0.5) * 2;
36
+ const o = (y * W + x) * 4;
37
+ // edge darkening toward plank seams (bevel)
38
+ const e = Math.min(y - y0, y1 - 1 - y);
39
+ const bev = e < 3 ? 0.45 + e * 0.15 : e < 7 ? 0.9 + (e - 3) * 0.025 : 1;
40
+ const sh = tone * bev * (1 + (r() - 0.5) * 0.04);
41
+ d[o] = (c0[0] + (c1[0] - c0[0]) * kk) * sh;
42
+ d[o + 1] = (c0[1] + (c1[1] - c0[1]) * kk) * sh;
43
+ d[o + 2] = (c0[2] + (c1[2] - c0[2]) * kk) * sh;
44
+ d[o + 3] = 255;
45
+ }
46
+ }
47
+ }
48
+ ctx.putImageData(img, 0, 0);
49
+ // satin finish: soft broad sheen from upper left
50
+ const g = ctx.createRadialGradient(330, 120, 40, 330, 120, 900);
51
+ g.addColorStop(0, 'rgba(255,220,170,0.14)'); g.addColorStop(1, 'rgba(255,220,170,0)');
52
+ ctx.fillStyle = g; ctx.fillRect(0, 0, W, H_);
53
+ }
54
+
55
+ function thread(ctx, segs, col, alpha, width) {
56
+ if (!segs.length) return;
57
+ ctx.save();
58
+ ctx.lineCap = 'round';
59
+ const path = (dx, dy) => { ctx.beginPath(); for (const [a, b] of segs) { ctx.moveTo(a[0] + dx, a[1] + dy); ctx.lineTo(b[0] + dx, b[1] + dy); } };
60
+ // shadow cast onto the board (thread floats ~2mm above the wood, light from top-left): soft penumbra + tight core
61
+ path(2.8, 3.6); ctx.strokeStyle = 'rgba(15,6,2,0.1)'; ctx.lineWidth = width * 3.4; ctx.stroke();
62
+ path(1.6, 2.2); ctx.strokeStyle = 'rgba(15,6,2,0.3)'; ctx.lineWidth = width * 1.5; ctx.stroke();
63
+ // body, with a darker under-edge so pale thread separates from the wood
64
+ path(0.35, 0.45); ctx.strokeStyle = H.rgba(H.mix(col, '#2a1408', 0.55), alpha * 0.5); ctx.lineWidth = width * 1.05; ctx.stroke();
65
+ path(0, 0); ctx.strokeStyle = H.rgba(col, alpha); ctx.lineWidth = width; ctx.stroke();
66
+ // sheen (mercerised cotton): bright thin core on the lit side
67
+ path(-0.3, -0.3); ctx.strokeStyle = H.rgba(H.mix(col, '#ffffff', 0.65), Math.min(1, alpha * 0.65)); ctx.lineWidth = width * 0.38; ctx.stroke();
68
+ ctx.restore();
69
+ }
70
+
71
+ function nail(ctx, x, y, r, seed) {
72
+ const rr = H.rng(seed);
73
+ const dx = rr.range(-0.6, 0.6), dy = rr.range(-0.6, 0.6);
74
+ // contact shadow + long soft shadow (light from top-left)
75
+ let g = ctx.createRadialGradient(x + 3.5, y + 4.5, 0, x + 3.5, y + 4.5, r * 2.4);
76
+ g.addColorStop(0, 'rgba(10,4,0,0.55)'); g.addColorStop(1, 'rgba(10,4,0,0)');
77
+ ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x + 3.5, y + 4.5, r * 2.4, 0, TAU); ctx.fill();
78
+ // head
79
+ g = ctx.createRadialGradient(x - r * 0.4 + dx, y - r * 0.45 + dy, 0.2, x, y, r);
80
+ g.addColorStop(0, '#fff6dc'); g.addColorStop(0.25, '#d8c08a'); g.addColorStop(0.7, '#8c6d3a'); g.addColorStop(1, '#3d2c14');
81
+ ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x, y, r, 0, TAU); ctx.fill();
82
+ ctx.strokeStyle = 'rgba(30,18,6,0.7)'; ctx.lineWidth = 0.6; ctx.stroke();
83
+ }
84
+
85
+ let WOOD = null; // the board never changes: build it once (deterministic), reuse every frame
86
+ // wind(list, p): chords wound so far; the chord currently being wound is drawn partially
87
+ function wind(list, p) {
88
+ if (p <= 0) return [];
89
+ const f = H.clamp(p) * list.length, n = Math.floor(f), out = list.slice(0, n);
90
+ if (n < list.length) { const [a, b] = list[n], k = f - n; out.push([a, [a[0] + (b[0] - a[0]) * k, a[1] + (b[1] - a[1]) * k]]); }
91
+ return out;
92
+ }
93
+ const ph = (t, t0, t1) => H.clamp((t - t0) / (t1 - t0));
94
+
95
+
96
+ // =====================================================================================
97
+ // EXPLAINER — the neural network IS a string-art board: nails are neurons, threads are
98
+ // connections, and a connection's weight is how many times the thread is wound. Learning
99
+ // = unwinding wrong paths and adding wraps to right ones. The camera travels along one
100
+ // long board; labels are kraft tags nailed on; counters are nail-and-thread 7-segment digits.
101
+ // =====================================================================================
102
+ const AX = (t, a, b) => H.clamp((t - a) / (b - a));
103
+ const BROWN = '#2a1307';
104
+ function woodWorld(ctx, x0, x1) {
105
+ if (!WOOD) { WOOD = H.layer(1280, 720); woodBoard(WOOD.getContext('2d'), 1280, 720, 0); }
106
+ for (let i = Math.floor(x0 / 1280); i <= Math.floor(x1 / 1280); i++) {
107
+ ctx.drawImage(WOOD, i * 1280, 0);
108
+ ctx.fillStyle = 'rgba(20,8,2,0.55)'; ctx.fillRect(i * 1280 - 1.5, 0, 3, 720); // butt joint
109
+ }
110
+ }
111
+ // kraft paper tag nailed at the top, hand-lettered
112
+ function ktag(ctx, text, x, y, o = {}) {
113
+ const size = o.size || 24;
114
+ ctx.save(); ctx.font = `700 ${size}px "Caveat"`;
115
+ const w = Math.max(o.minW || 0, ctx.measureText(text).width + size * 1.0), h = size * 1.5 + 10;
116
+ ctx.translate(x, y); ctx.rotate(o.rot || 0);
117
+ const ox = o.align === 'left' ? 0 : -w / 2;
118
+ ctx.fillStyle = 'rgba(10,4,0,0.35)'; ctx.fillRect(ox + 4, 6, w, h);
119
+ const g = ctx.createLinearGradient(0, 0, 0, h); g.addColorStop(0, o.red ? '#d0503a' : '#d4b27a'); g.addColorStop(1, o.red ? '#b23a28' : '#c09858');
120
+ ctx.fillStyle = g; ctx.beginPath(); ctx.moveTo(ox + 8, 0); ctx.lineTo(ox + w - 8, 0); ctx.lineTo(ox + w, 8); ctx.lineTo(ox + w, h); ctx.lineTo(ox, h); ctx.lineTo(ox, 8); ctx.closePath(); ctx.fill();
121
+ ctx.fillStyle = o.red ? '#fff3e0' : BROWN; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(text, ox + w / 2, h / 2 + 5);
122
+ ctx.restore();
123
+ nail(ctx, x + (o.align === 'left' ? 14 : 0), y + 7, 2.8, 900 + (x | 0));
124
+ return w;
125
+ }
126
+ function droppedTag(ctx, t, t0, text, x, y, o) { if (t < t0) return; const k = H.ease.back(AX(t, t0, t0 + 0.28)); ktag(ctx, text, x, y - (1 - k) * 60, { ...o, rot: (o && o.rot || 0) + (1 - k) * 0.25 }); }
127
+ // inked doodle on a tag
128
+ function petTag(ctx, kind, x, y, s, label) {
129
+ ctx.save();
130
+ ctx.fillStyle = 'rgba(10,4,0,0.35)'; ctx.fillRect(x + 4, y + 5, s, s * 1.2);
131
+ ctx.fillStyle = '#ead9b4'; ctx.fillRect(x, y, s, s * 1.2);
132
+ ctx.strokeStyle = BROWN; ctx.lineWidth = Math.max(1.5, s * 0.03); ctx.lineCap = 'round'; ctx.lineJoin = 'round';
133
+ const cx = x + s / 2, cy = y + s * 0.5, r = s * 0.25;
134
+ ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2);
135
+ if (kind === 'cat') { ctx.moveTo(cx - r * 0.8, cy - r * 0.5); ctx.lineTo(cx - r * 0.75, cy - r * 1.35); ctx.lineTo(cx - r * 0.2, cy - r * 0.95); ctx.moveTo(cx + r * 0.8, cy - r * 0.5); ctx.lineTo(cx + r * 0.75, cy - r * 1.35); ctx.lineTo(cx + r * 0.2, cy - r * 0.95);
136
+ ctx.moveTo(cx - r * 1.5, cy + r * 0.2); ctx.lineTo(cx - r * 0.5, cy + r * 0.3); ctx.moveTo(cx + r * 1.5, cy + r * 0.2); ctx.lineTo(cx + r * 0.5, cy + r * 0.3); }
137
+ else { ctx.moveTo(cx - r * 0.9, cy - r * 0.6); ctx.quadraticCurveTo(cx - r * 1.6, cy, cx - r * 1.1, cy + r * 0.9); ctx.moveTo(cx + r * 0.9, cy - r * 0.6); ctx.quadraticCurveTo(cx + r * 1.6, cy, cx + r * 1.1, cy + r * 0.9); }
138
+ ctx.stroke();
139
+ ctx.fillStyle = BROWN; ctx.beginPath(); ctx.arc(cx - r * 0.35, cy - r * 0.1, s * 0.025, 0, 7); ctx.arc(cx + r * 0.35, cy - r * 0.1, s * 0.025, 0, 7); ctx.fill();
140
+ if (label) { ctx.font = `700 ${Math.max(28, s * 0.24)}px "Caveat"`; ctx.textAlign = 'center'; ctx.fillText(label, cx, y + s * 1.12); }
141
+ ctx.restore();
142
+ nail(ctx, x + s / 2, y + 6, 2.6, 500 + (x | 0) + (y | 0));
143
+ }
144
+ // nail-and-thread 7-segment digits
145
+ const SEG = { '0': 'abcdef', '1': 'bc', '2': 'abged', '3': 'abgcd', '4': 'fgbc', '5': 'afgcd', '6': 'afgedc', '7': 'abc', '8': 'abcdefg', '9': 'abcdfg' };
146
+ function sevenSeg(ctx, str, x, y, w, h, col, seed = 1) {
147
+ const segs = [], nails = []; let xx = x;
148
+ for (const ch of str) {
149
+ if (ch === ',' || ch === '.') { nails.push([xx + 4, y + h]); xx += w * 0.45; continue; }
150
+ if (ch === '%') { const a = [xx + w * 0.15, y + h * 0.1], b = [xx + w * 0.85, y + h * 0.9], c1 = [xx + w * 0.85, y + h * 0.1], c2 = [xx + w * 0.15, y + h * 0.9];
151
+ for (const o of [-1.5, 0, 1.5]) segs.push([[c2[0] + o, c2[1]], [c1[0] + o, c1[1]]]); nails.push(c1, c2); [[xx + w * 0.22, y + h * 0.2], [xx + w * 0.78, y + h * 0.8]].forEach(([cx, cy]) => { const rr = w * 0.22; for (let k = 0; k < 8; k++) { const a0 = (k / 8) * 6.283, a1 = ((k + 1) / 8) * 6.283; for (const o of [0, 1.6]) segs.push([[cx + Math.cos(a0) * (rr - o), cy + Math.sin(a0) * (rr - o)], [cx + Math.cos(a1) * (rr - o), cy + Math.sin(a1) * (rr - o)]]); nails.push([cx + Math.cos(a0) * rr, cy + Math.sin(a0) * rr]); } });
152
+ xx += w * 1.3; continue; }
153
+ const P = [[xx, y], [xx + w, y], [xx, y + h / 2], [xx + w, y + h / 2], [xx, y + h], [xx + w, y + h]];
154
+ const S = { a: [0, 1], b: [1, 3], c: [3, 5], d: [4, 5], e: [2, 4], f: [0, 2], g: [2, 3] };
155
+ for (const sg of SEG[ch] || '') { const [i, j] = S[sg]; for (let k = -2; k <= 2; k++) segs.push([[P[i][0] + k * 1.6, P[i][1] + k * 1.2], [P[j][0] + k * 1.6, P[j][1] + k * 1.2]]); }
156
+ nails.push(...P); xx += w * 1.45;
157
+ }
158
+ thread(ctx, segs, col, 0.95, 1.8);
159
+ nails.forEach((p, i) => nail(ctx, p[0], p[1], 3, seed + i));
160
+ return xx - x;
161
+ }
162
+ // thread arrow: shaft + two barbs, three nails
163
+ function tArrow(ctx, x0, y0, x1, y1, p, col = '#efe3c8', seed = 7) {
164
+ if (p <= 0) return; const x = x0 + (x1 - x0) * p, y = y0 + (y1 - y0) * p, a = Math.atan2(y1 - y0, x1 - x0), L = 18;
165
+ const b1 = [x - Math.cos(a - 0.5) * L, y - Math.sin(a - 0.5) * L], b2 = [x - Math.cos(a + 0.5) * L, y - Math.sin(a + 0.5) * L];
166
+ const segs = [[[x0, y0], [x, y]], [[x0 + 1, y0 + 1], [x + 1, y + 1]]]; if (p >= 1) segs.push([[x, y], b1], [[x, y], b2]);
167
+ thread(ctx, segs, col, 0.85, 1.3); nail(ctx, x0, y0, 3, seed); if (p >= 1) { nail(ctx, x, y, 3, seed + 1); nail(ctx, b1[0], b1[1], 2.6, seed + 2); nail(ctx, b2[0], b2[1], 2.6, seed + 3); }
168
+ }
169
+ // pyrography burn of a line of text, revealed left->right with a glowing ember
170
+ let BURNTEX = null; // uneven pen pressure: a static mottling texture, built once
171
+ function burnTex() {
172
+ if (BURNTEX) return BURNTEX;
173
+ BURNTEX = H.layer(256, 256); const c = BURNTEX.getContext('2d'), bn = H.makeNoise(5);
174
+ for (let y = 0; y < 256; y += 4) for (let x = 0; x < 256; x += 4) { const v = bn.n2(x * 0.03, y * 0.03) * 0.7 + bn.n2(x * 0.11, y * 0.11) * 0.3; if (v > 0.05) { c.fillStyle = `rgba(120,62,24,${Math.min(0.55, v * 1.1)})`; c.fillRect(x, y, 4, 4); } }
175
+ return BURNTEX;
176
+ }
177
+ const BURNC = {}; // finished burns never change: keep their pre-blurred layers
178
+ const blurred = (src, px, col) => { const P = px * 2, c = H.layer(src.width + P * 2, src.height + P * 2), x = c.getContext('2d'); x.filter = `blur(${px}px)`; x.drawImage(col ? tint(src, col) : src, P, P); return c; };
179
+ const tint = (src, col) => { const c = H.layer(src.width, src.height), x = c.getContext('2d'); x.drawImage(src, 0, 0); x.globalCompositeOperation = 'source-in'; x.fillStyle = col; x.fillRect(0, 0, c.width, c.height); return c; };
180
+ function burnText(ctx, text, x, y, font, prog, t) {
181
+ if (prog <= 0) return; ctx.save(); ctx.font = font; const w = ctx.measureText(text).width, asc = parseFloat(font.match(/(\d+)px/)[1]); ctx.restore();
182
+ const PAD = 40, ox = x - w / 2 - PAD, oy = y - asc * 1.1, key = text + '|' + font;
183
+ let B = prog >= 1 ? BURNC[key] : null;
184
+ if (!B) {
185
+ const L = H.layer(Math.ceil(w + PAD * 2), Math.ceil(asc * 1.6)), lc = L.getContext('2d');
186
+ lc.font = font; lc.textBaseline = 'alphabetic'; lc.fillStyle = '#000'; lc.fillText(text, PAD, asc * 1.1);
187
+ // reveal mask: once finished it covers the italic overhang too
188
+ lc.globalCompositeOperation = 'destination-in'; lc.fillRect(0, 0, prog >= 1 ? L.width : PAD + w * prog, L.height);
189
+ const toast = blurred(L, 12, 'rgb(214,150,80)'), halo = blurred(L, 5, 'rgb(40,16,4)');
190
+ // charred core with uneven pressure
191
+ lc.globalCompositeOperation = 'source-in'; lc.fillStyle = '#1c0a03'; lc.fillRect(0, 0, L.width, L.height);
192
+ lc.globalCompositeOperation = 'source-atop'; lc.globalAlpha = 0.7; lc.fillStyle = lc.createPattern(burnTex(), 'repeat'); lc.fillRect(0, 0, L.width, L.height); lc.globalAlpha = 1;
193
+ B = { toast, halo, core: L };
194
+ if (prog >= 1) BURNC[key] = B;
195
+ }
196
+ // toasted rim: the wood around the burn is singed paler and golden, so the letters sit in a lit patch
197
+ ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.globalAlpha = 0.5; ctx.drawImage(B.toast, ox - 24, oy - 24); ctx.restore();
198
+ // scorched halo right at the stroke edges, then the core
199
+ ctx.save(); ctx.globalAlpha = 0.5; ctx.drawImage(B.halo, ox - 10, oy - 10); ctx.globalAlpha = 0.95; ctx.drawImage(B.core, ox, oy); ctx.restore();
200
+ if (prog < 1) { const bx = ox + PAD + w * prog, by = y - asc * 0.35 + Math.sin(t * 40) * asc * 0.25, g = ctx.createRadialGradient(bx, by, 0, bx, by, 34);
201
+ g.addColorStop(0, 'rgba(255,225,150,1)'); g.addColorStop(0.18, 'rgba(255,170,70,0.75)'); g.addColorStop(0.45, 'rgba(255,100,25,0.32)'); g.addColorStop(1, 'rgba(255,80,20,0)');
202
+ ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = g; ctx.fillRect(bx - 36, by - 36, 72, 72); ctx.restore();
203
+ const sm = ctx.createRadialGradient(bx + 6, by - 44, 0, bx + 6, by - 44, 40);
204
+ sm.addColorStop(0, 'rgba(220,210,200,0.14)'); sm.addColorStop(1, 'rgba(220,210,200,0)');
205
+ ctx.fillStyle = sm; ctx.fillRect(bx - 40, by - 100, 96, 100); }
206
+ }
207
+ // ---------- network on region B (world x 1280..2560) ----------
208
+ const NB = 1280;
209
+ const IN = [[NB + 470, 250], [NB + 470, 360], [NB + 470, 470]];
210
+ const HID = [0, 1, 2, 3, 4].map((k) => [NB + 740, 170 + k * 95]);
211
+ const OUT = { cat: [NB + 1010, 270], dog: [NB + 1010, 460] };
212
+ const HC = 1, HD = 3; // hidden neurons on the cat / dog path
213
+ function weights(t) {
214
+ const r = H.rng(808), w1 = [], w2 = [];
215
+ const adj = H.ease.inOut(AX(t, 8.95, 9.45));
216
+ for (let i = 0; i < 3; i++) for (let j = 0; j < 5; j++) { let w = r.range(0.1, 0.6); if (i === 1 && j === HD) w = 0.95; if (i === 1 && j === HC) w = 0.35 + 0.6 * adj; if (i === 1 && j === HD) w = 0.95 - 0.7 * adj; w1.push(w); }
217
+ for (let j = 0; j < 5; j++) for (const o of ['cat', 'dog']) { let w = r.range(0.1, 0.5); if (j === HD && o === 'dog') w = 0.95 - 0.75 * adj; if (j === HC && o === 'cat') w = 0.3 + 0.65 * adj; w2.push(w); }
218
+ return { w1, w2 };
219
+ }
220
+ function wrapSegs(a, b, w) { const n = 1 + Math.round(w * 5), dx = b[0] - a[0], dy = b[1] - a[1], L = Math.hypot(dx, dy), nx = -dy / L, ny = dx / L, out = [];
221
+ for (let k = 0; k < n; k++) { const o = (k - (n - 1) / 2) * 1.5; out.push([[a[0] + nx * o, a[1] + ny * o], [b[0] + nx * o, b[1] + ny * o]]); } return out; }
222
+ function drawNetwork(ctx, t, build) {
223
+ const { w1, w2 } = weights(t);
224
+ let segs = [];
225
+ let q = 0;
226
+ for (let i = 0; i < 3; i++) for (let j = 0; j < 5; j++) { segs = segs.concat(wrapSegs(IN[i], HID[j], w1[q++])); }
227
+ const s2 = []; q = 0;
228
+ for (let j = 0; j < 5; j++) for (const o of ['cat', 'dog']) s2.push(...wrapSegs(HID[j], OUT[o], w2[q++]));
229
+ thread(ctx, wind(segs, build), '#f4ead2', 0.78, 1.3);
230
+ thread(ctx, wind(s2, H.clamp(build * 1.3 - 0.3)), '#f4ead2', 0.78, 1.3);
231
+ IN.concat(HID).concat([OUT.cat, OUT.dog]).forEach((p, i) => nail(ctx, p[0], p[1], 4.2, 60 + i));
232
+ }
233
+ function pathSegs(a, b, c) { return [[a, b], [b, c]]; }
234
+ function sceneB(ctx, t) {
235
+ // examples zip along threads into the input nails, a counter in nail-and-thread digits
236
+ const lines = [[NB + 40, 250], [NB + 40, 360], [NB + 40, 470]];
237
+ lines.forEach((p, i) => { if (t > 2.4) { thread(ctx, [[p, IN[i]]], '#c9ccd2', 0.7, 1.1); nail(ctx, p[0], p[1], 3, 40 + i); } });
238
+ drawNetwork(ctx, t, AX(t, 2.6, 4.2));
239
+ if (t > 2.9) droppedTag(ctx, t, 2.9, 'the model', NB + 740, 52, { size: 28 });
240
+ if (t < 6.4) {
241
+ const r = H.rng(12), K = 32;
242
+ for (let k = 0; k < K; k++) {
243
+ const kind = r() < 0.5 ? 'cat' : 'dog', ts = 2.6 + 3.2 * Math.pow(k / K, 0.8); if (t < ts) continue;
244
+ const x = NB + 30 + (t - ts) * (430 + k * 14); if (x > NB + 420) continue;
245
+ petTag(ctx, kind, x, lines[k % 3][1] - 4, 64, kind);
246
+ }
247
+ if (t > 2.7) {
248
+ const p = AX(t, 2.75, 5.0), n = p >= 1 ? 1e6 : Math.floor(Math.pow(10, 6 * H.ease.inOut(Math.floor(p * 30) / 30))) - 1;
249
+ const w = sevenSeg(ctx, Math.round(n).toLocaleString('en-US'), NB + 60, 575, 30, 64, '#ffc93a', 200);
250
+ droppedTag(ctx, t, 3.0, 'examples', NB + 60 + w + 70, 600, { size: 28 });
251
+ }
252
+ }
253
+ ktag(ctx, 'cat', OUT.cat[0] + 70, OUT.cat[1] - 24, { size: 34, red: t >= 9.85 && t < 10.6 });
254
+ ktag(ctx, 'dog', OUT.dog[0] + 70, OUT.dog[1] - 24, { size: 34 });
255
+ }
256
+ function sceneLoop(ctx, t) {
257
+ if (t < 6.0) return;
258
+ // the new picture hangs on the input
259
+ petTag(ctx, 'cat', NB + 290, 280, 120, null);
260
+ droppedTag(ctx, t, 6.2, t >= 8.0 ? 'answer: cat' : 'new picture', NB + 350, 440, { size: 28, red: t >= 8.0 });
261
+ tArrow(ctx, NB + 420, 360, NB + 455, 360, 1, '#efe3c8', 70);
262
+ const pass1 = AX(t, 6.9, 7.3), right = t >= 9.8;
263
+ if (!right && pass1 > 0) thread(ctx, wind(pathSegs(IN[1], HID[HD], OUT.dog), pass1), '#ffc93a', 0.95, 3.4);
264
+ if (t >= 7.3 && !right) droppedTag(ctx, t, 7.3, 'guess: dog?', OUT.dog[0] + 70, OUT.dog[1] + 78, { size: 28 });
265
+ if (t >= 7.85 && !right) { // check: red thread wound round a ring of nails on the wrong answer
266
+ const cx = OUT.dog[0] + 70, cy = OUT.dog[1] + 12, ring = [];
267
+ for (let k = 0; k < 14; k++) { for (const sk of [1, 2]) { const a0 = (k / 14) * 6.283, a1 = ((k + sk) / 14) * 6.283; ring.push([[cx + Math.cos(a0) * 82, cy + Math.sin(a0) * 55], [cx + Math.cos(a1) * 82, cy + Math.sin(a1) * 55]]); } }
268
+ thread(ctx, wind(ring, AX(t, 7.85, 8.2)), '#d8322a', 0.9, 1.6);
269
+ for (let k = 0; k < 14; k++) { const a = (k / 14) * 6.283; if (t > 7.85 + k * 0.02) nail(ctx, cx + Math.cos(a) * 82, cy + Math.sin(a) * 55, 2.8, 300 + k); }
270
+ droppedTag(ctx, t, 8.05, 'check: wrong ✗', cx, cy - 120, { size: 28, red: true });
271
+ }
272
+ if (t >= 8.35 && !right) thread(ctx, wind([[OUT.dog, HID[HD]], [HID[HD], IN[1]]], AX(t, 8.35, 8.85)), '#d8322a', 0.92, 2.8);
273
+ if (t >= 8.4 && t < 9.6) droppedTag(ctx, t, 8.4, 'error goes back', NB + 880, 520, { size: 28, red: true });
274
+ if (t >= 8.95 && t < 10.5) droppedTag(ctx, t, 8.95, 'adjust: rewind threads', NB + 740, 52, { size: 28, red: true });
275
+ if (t >= 9.4) thread(ctx, wind(pathSegs(IN[1], HID[HC], OUT.cat), AX(t, 9.4, 9.8)), '#ffc93a', 0.95, 3.4);
276
+ if (right) { droppedTag(ctx, t, 9.85, 'cat ✓', OUT.cat[0] + 70, OUT.cat[1] - 100, { size: 30, red: true }); }
277
+ // loop strip of tags joined by thread arrows
278
+ if (t > 6.3) {
279
+ const words = ['guess', 'check', 'adjust'], act = t < 7.3 ? -1 : t < 7.85 ? 0 : t < 8.35 ? 1 : t < 9.8 ? 2 : Math.floor((t - 9.8) / 0.16) % 3;
280
+ let x = NB + 120;
281
+ words.forEach((w, i) => { const tw = ktag(ctx, w, x, 590, { size: 32, align: 'left', red: i === act, minW: 110 }); x += tw + 16; if (i < 2) { tArrow(ctx, x, 618, x + 54, 618, 1, '#efe3c8', 80 + i * 4); x += 74; } });
282
+ ktag(ctx, '↻ x 1,000,000', x + 70, 590, { size: 28 });
283
+ }
284
+ }
285
+ // ---------- region C: progress ----------
286
+ const NC = 2560;
287
+ function sceneC(ctx, t) {
288
+ droppedTag(ctx, t, 10.6, 'accuracy', NC + 290, 70, { size: 38 });
289
+ const p = H.ease.inOut(AX(t, 10.95, 12.3)), v = Math.round(61 + 36 * Math.floor(p * 18) / 18);
290
+ sevenSeg(ctx, v + '%', NC + 70, 190, 90, 200, '#ffc93a', 400);
291
+ if (t >= 12.3) { const cx = NC + 290, cy = 290, ring = []; for (let k = 0; k < 18; k++) { for (const sk of [1, 2]) { const a0 = (k / 18) * 6.283, a1 = ((k + sk) / 18) * 6.283; ring.push([[cx + Math.cos(a0) * 280, cy + Math.sin(a0) * 165], [cx + Math.cos(a1) * 280, cy + Math.sin(a1) * 165]]); } }
292
+ thread(ctx, wind(ring, AX(t, 12.3, 12.7)), '#d8322a', 0.85, 1.5); for (let k = 0; k < 18; k++) { const a = (k / 18) * 6.283; nail(ctx, cx + Math.cos(a) * 280, cy + Math.sin(a) * 165, 2.8, 700 + k); } }
293
+ // line chart strung nail to nail
294
+ const x0 = NC + 640, y0 = 600, cw = 560, ch = 420, n = 10, pts = [];
295
+ for (let i = 0; i < n; i++) { const acc = 61 + 36 * (1 - Math.exp(-i / 2.6)) / (1 - Math.exp(-(n - 1) / 2.6)); pts.push([x0 + 30 + i * (cw - 60) / (n - 1), y0 - ((acc - 50) / 50) * ch]); }
296
+ thread(ctx, [[[x0, y0], [x0 + cw, y0]], [[x0, y0], [x0, y0 - ch - 20]]], '#c9ccd2', 0.8, 1.2);
297
+ [[x0, y0], [x0 + cw, y0], [x0, y0 - ch - 20]].forEach((q, i) => nail(ctx, q[0], q[1], 3.2, 760 + i));
298
+ const segs = []; for (let i = 0; i < n - 1; i++) for (const o of [-1.5, 0, 1.5]) segs.push([[pts[i][0], pts[i][1] + o], [pts[i + 1][0], pts[i + 1][1] + o]]); // three wraps
299
+ const k = AX(t, 10.95, 12.3); thread(ctx, wind(segs, k), '#ffc93a', 0.9, 1.5);
300
+ pts.forEach((q, i) => { if (k * (n - 1) >= i - 0.01) nail(ctx, q[0], q[1], 3.6, 780 + i); });
301
+ droppedTag(ctx, t, 10.7, 'training rounds →', x0 + cw - 100, y0 + 24, { size: 28 });
302
+ }
303
+ // ---------- region D: takeaway ----------
304
+ const ND = 3840;
305
+ function sceneD(ctx, t) {
306
+ burnText(ctx, 'More examples', ND + 640, 300, '700 110px "Caveat"', AX(t, 13.2, 13.65), t);
307
+ burnText(ctx, '→ better guesses', ND + 660, 440, '700 110px "Caveat"', AX(t, 13.6, 14.05), t);
308
+ // a small cardioid winds on beneath, the same envelope trick as the still
309
+ const N = 72, cx = ND + 640, cy = 590, R = 70, circ = []; for (let i = 0; i < N; i++) { const a = -Math.PI / 2 + (i / N) * 6.283; circ.push([cx + Math.cos(a) * R * 1.12, cy + Math.sin(a) * R * 1.12]); }
310
+ const card = []; for (let i = 0; i < N; i++) card.push([circ[i], circ[(i * 2) % N]]);
311
+ thread(ctx, wind(card, AX(t, 13.0, 14.2)), '#ffc93a', 0.8, 1.05);
312
+ if (t > 12.85) circ.forEach((p, i) => { if (i % 2 === 0) nail(ctx, p[0], p[1], 2.4, 1200 + i); });
313
+ }
314
+ function sceneA(ctx, t) {
315
+ burnText(ctx, 'How does', 600, 300, '700 130px "Caveat"', AX(t, 0.1, 0.8), t);
316
+ burnText(ctx, 'AI learn?', 680, 450, '700 130px "Caveat"', AX(t, 0.75, 1.45), t);
317
+ const N = 40, ring = [], pts = []; for (let i = 0; i < N; i++) { const a = (i / N) * 6.283; pts.push([640 + Math.cos(a) * 470, 360 + Math.sin(a) * 300]); }
318
+ for (let i = 0; i < N; i++) ring.push([pts[i], pts[(i + 13) % N]]);
319
+ thread(ctx, wind(ring, AX(t, 0.3, 2.2)), '#efe3c8', 0.55, 1.1);
320
+ pts.forEach((p, i) => { if (t > 0.05 + i * 0.012) nail(ctx, p[0], p[1], 3, 1000 + i); });
321
+ }
322
+ function explainSA(ctx, W, H_, t) {
323
+ // camera path: (world centre x, centre y, zoom)
324
+ const keys = [[0, 640, 360, 1], [2.15, 640, 360, 1], [2.75, 1920, 360, 1], [6.0, 1920, 360, 1], [6.5, 2010, 340, 1.08], [10.35, 2010, 340, 1.08], [10.95, 3200, 360, 1], [12.85, 3200, 360, 1], [13.35, 4480, 360, 1], [99, 4480, 360, 1]];
325
+ let k = 0; while (k < keys.length - 2 && t >= keys[k + 1][0]) k++;
326
+ const a = keys[k], b = keys[k + 1], u = H.ease.inOut(AX(t, a[0], b[0]));
327
+ const cx = a[1] + (b[1] - a[1]) * u, cy = a[2] + (b[2] - a[2]) * u, z = (a[3] + (b[3] - a[3]) * u) * (1 + 0.012 * Math.sin(t * 0.7));
328
+ ctx.save(); ctx.translate(W / 2, H_ / 2); ctx.scale(z, z); ctx.translate(-cx, -cy);
329
+ const vx0 = cx - W / 2 / z, vx1 = cx + W / 2 / z;
330
+ woodWorld(ctx, vx0, vx1);
331
+ if (vx0 < 1280) sceneA(ctx, t);
332
+ if (vx1 > 1280 && vx0 < 2560) { sceneB(ctx, t); sceneLoop(ctx, t); }
333
+ if (vx1 > 2560 && vx0 < 3840 && t > 10) sceneC(ctx, t);
334
+ if (vx1 > 3840) sceneD(ctx, t);
335
+ ctx.restore();
336
+ if (t > 1.9) { const kk = H.ease.back(AX(t, 1.9, 2.2)); ktag(ctx, 'How does AI learn?', 30, 26 - (1 - kk) * 60, { size: 28, align: 'left', rot: -0.03 }); }
337
+ H.vignette(ctx, W, H_, 0.42, '20,8,2');
338
+ H.grain(ctx, W, H_, 0.035, 12 + Math.floor(t * 12));
339
+ }
340
+
341
+ registerStyle({
342
+ id: 'string-art',
343
+ name: 'Nail & Thread String Art',
344
+ category: 'Generative & Type',
345
+ description: 'Taut cotton threads strung between brass nails on a stained walnut board; every curve is an envelope of straight chords, with thread translucency, sheen and cast shadows, plus pyrography lettering.',
346
+ motion: 'Nails are tapped in around the outline, then thread is wound chord by chord so the glowing envelopes emerge, the filament and base are laced, the rays snap taut, and the title is burned in with a travelling ember while the camera slowly pushes in.',
347
+ fonts: ['700 92px "Caveat"', '400 26px "Caveat"', '700 26px "Caveat"'],
348
+ duration: 7,
349
+ frameT: 6.9,
350
+ readsPixels: true,
351
+ explainerDuration: 15,
352
+ explainerNote: 'The network is literally strung on the board — nails are neurons, a weight is how many wraps of thread a connection has — and learning is re-winding; the camera travels along one long walnut board between scenes, labels are kraft tags nailed on, arrows are threads with nail barbs, counters are nail-and-thread 7-segment digits, and emphasis is a red thread wound round a ring of nails.',
353
+ explainer(ctx, W, H_, t) { explainSA(ctx, W, H_, t); },
354
+ // Sound: a workbench. Nails are tapped into walnut (small metal tink on a wood knock), thread winding is a soft
355
+ // cotton rub with a low muffled pluck each time it snaps round a nail (throttled), kraft tags are nailed on with one
356
+ // tap as they land, pyrography burns with a quiet sizzle and ember crackle, and the camera travel is a smooth low
357
+ // slide along the board. The wrong guess is a slack, out-of-tune twang; the correction and the 97% ring are taut
358
+ // consonant thrums. Timings mirror explainSA / sceneA..D above (AX windows, wind() is linear, droppedTag back-ease).
359
+ explainerSound(k) {
360
+ const D = 15, E = H.ease;
361
+ 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; };
362
+ const land = (t0) => when(t0, t0 + 0.28, E.back, 1); // droppedTag: back ease over 0.28 s
363
+ // hammer tap on a nail: metal tink + wood knock
364
+ const tap = (t, g, p) => { k.noise(t, { dur: 0.02, filter: 'bandpass', f: 2300, q: 5, gain: 0.22 * g, a: 0.001, pan: p }); k.tone(t, { f: 1150, dur: 0.05, gain: 0.05 * g, a: 0.001, pan: p }); k.tone(t, { f: [[0, 210], [0.05, 140]], dur: 0.08, gain: 0.22 * g, a: 0.001, pan: p }); };
365
+ const tagOn = (t0, p, g = 1) => tap(land(t0), 0.75 * g, p);
366
+ // thread snapping round a nail: low muffled pluck
367
+ const pluck = (t, f, g, p) => { k.tone(t, { f, type: 'triangle', dur: 0.28, gain: 0.12 * g, a: 0.002, filter: 'lowpass', f2: [[0, 1400], [0.2, 400]], pan: p }); k.noise(t, { dur: 0.015, filter: 'bandpass', f: 1200, q: 1, gain: 0.06 * g, a: 0.001, pan: p }); };
368
+ // winding over [t0,t1] with N chords (linear): cotton rub + a pluck per chord, at most `rate` per second
369
+ const wind = (t0, t1, N, g, p, f = 196, rate = 8) => {
370
+ const d = t1 - t0;
371
+ k.noise(t0, { dur: d + 0.05, color: 'pink', filter: 'bandpass', f: 900, q: 0.9, gain: 0.07 * g, a: Math.min(0.1, d * 0.3), hold: d - 0.15, pan: p, minor: true });
372
+ let last = -1;
373
+ k.withMinor(() => { for (let i = 0; i < N; i++) { const t = t0 + ((i + 1) / N) * d; if (t - last < 1 / rate) continue; last = t; pluck(t, f * (1 + 0.06 * (i % 3)), 0.7 * g, p + ((i % 5) - 2) * 0.06); } });
374
+ };
375
+ // pyrography: soft sizzle with seeded ember crackle
376
+ const burn = (t0, t1, p) => {
377
+ const d = t1 - t0, r = H.rng(Math.round(t0 * 100));
378
+ k.noise(t0, { dur: d + 0.1, color: 'pink', filter: 'bandpass', f: 2200, q: 0.6, gain: 0.05, a: 0.06, hold: d - 0.1, pan: p, minor: true });
379
+ k.withMinor(() => { for (let t = t0 + 0.03; t < t1; t += 0.05 + r() * 0.07) k.noise(t, { dur: 0.008, filter: 'bandpass', f: 1400 + r() * 900, q: 1, gain: 0.07 + r() * 0.08, a: 0.001, pan: p + (r() - 0.5) * 0.3 }); });
380
+ };
381
+ // camera travels along the board
382
+ const travel = (t0, t1) => { const d = t1 - t0; k.noise(t0, { dur: d, color: 'brown', filter: 'lowpass', f: [[0, 250], [d * 0.5, 520], [d, 220]], gain: 0.16, a: d * 0.45 }); };
383
+ const thrum = (t, notes, g) => notes.forEach((n, i) => k.tone(t + i * 0.035, { f: n, type: 'triangle', dur: 1.1, gain: 0.07 * g, a: 0.003, filter: 'lowpass', f2: [[0, 2200], [0.8, 600]], verb: 0.2, pan: (i - (notes.length - 1) / 2) * 0.2 }));
384
+
385
+ k.bed('room', 0, D, { gain: 0.35 });
386
+
387
+ // ---- A: nails tapped round the ellipse (0.05 + i*0.012), title burned in, ring of thread wound 0.3-2.2
388
+ k.withMinor(() => { for (let i = 0; i < 40; i += 6) { const a = (i / 40) * 6.283; tap(0.05 + i * 0.012, 0.6, Math.cos(a) * 0.6); } });
389
+ burn(0.1, 0.8, -0.15);
390
+ burn(0.75, 1.45, 0.1);
391
+ wind(0.3, 2.2, 40, 0.8, 0, 175, 5);
392
+ tagOn(1.9, -0.7); // header tag (lands 1.9 + 0.3*0.37)
393
+
394
+ // ---- B: travel to the network; it winds 2.6-4.2 (second layer to 4.2), tags, the 7-segment counter
395
+ travel(2.15, 2.75);
396
+ wind(2.6, 4.2, 45, 0.7, 0.2, 220, 4);
397
+ tagOn(2.9, 0.15); tagOn(3.0, -0.45);
398
+ // counter shows floor(p*30)/30 steps of p = AX(2.75, 5.0) through inOut: each step re-strings the digits
399
+ { let prev = 0, last = -1; for (let t = 2.76; t <= 5.0; t += 0.002) { const s = Math.floor(AX(t, 2.75, 5.0) * 30); if (s !== prev && s < 30 && t - last >= 1 / 9) { k.withMinor(() => pluck(t, 330 + s * 4, 0.55, -0.55)); last = t; } prev = s; } }
400
+ tap(5.0, 1.3, -0.5); thrum(5.0, ['G2', 'D3', 'G3'], 0.9); // 1,000,000 strung
401
+
402
+ // ---- loop: push in, tag, guess path winds, red ring of nails, error back, rewind, cat ✓
403
+ travel(6.0, 6.5);
404
+ tagOn(6.2, -0.45);
405
+ wind(6.9, 7.3, 2, 1.1, 0.2, 247);
406
+ tagOn(7.3, 0.6, 0.8);
407
+ k.withMinor(() => { for (let i = 0; i < 14; i += 3) tap(7.85 + i * 0.02, 0.5, 0.55); }); // ring nails go in
408
+ wind(7.85, 8.2, 28, 0.6, 0.55, 165, 9);
409
+ tagOn(8.05, 0.55, 1.1);
410
+ // check: wrong — a slack, out-of-tune twang on the dog thread
411
+ [98, 139].forEach((f, i) => k.tone(8.16, { f: [[0, f], [0.35, f * 0.94]], type: 'sawtooth', dur: 0.5, gain: 0.08, a: 0.003, filter: 'lowpass', f2: [[0, 1300], [0.4, 300]], pan: 0.5 - i * 0.1 }));
412
+ k.play('thud', 8.16, { f: 110, gain: 0.5, pan: 0.5 });
413
+ // red error thread runs back dog -> hidden -> input (8.35-8.85): a rub falling right to left
414
+ k.noise(8.35, { dur: 0.5, color: 'pink', filter: 'bandpass', f: [[0, 1300], [0.5, 600]], q: 1.2, gain: 0.1, a: 0.1, pan: 0.2 });
415
+ tagOn(8.4, 0.35, 0.8);
416
+ tagOn(8.95, 0, 0.8);
417
+ // adjust 8.95-9.45: threads unwound from the dog path, wraps added to the cat path
418
+ k.noise(8.95, { dur: 0.5, color: 'pink', filter: 'bandpass', f: [[0, 700], [0.5, 1100]], q: 0.9, gain: 0.09, a: 0.15, minor: true });
419
+ k.withMinor(() => [0, 1, 2, 3].forEach((i) => pluck(9.0 + i * 0.12, [196, 220, 247, 262][i], 0.8, -0.1)));
420
+ wind(9.4, 9.8, 2, 1.1, 0.1, 262);
421
+ tagOn(9.85, 0.6, 1.1); thrum(land(9.85), ['C3', 'G3', 'E4'], 1.1); // cat ✓
422
+
423
+ // ---- C: travel, tags, chart nails + counter, red ring round 97%
424
+ travel(10.35, 10.95);
425
+ tagOn(10.6, -0.2); tagOn(10.7, 0.6, 0.6);
426
+ k.noise(10.95, { dur: 1.4, color: 'pink', filter: 'bandpass', f: 900, q: 0.9, gain: 0.06, a: 0.1, hold: 1.1, pan: 0.4, minor: true }); // chart thread rubs on
427
+ k.withMinor(() => { for (let i = 1; i < 10; i++) tap(10.95 + (i / 9) * 1.35, 0.45, 0.1 + i * 0.06); }); // a nail at each point
428
+ { let prev = 0, last = -1; for (let t = 10.96; t <= 12.3; t += 0.002) { const s = Math.floor(H.ease.inOut(AX(t, 10.95, 12.3)) * 18); if (s !== prev && t - last >= 1 / 9) { k.withMinor(() => pluck(t, 260 + s * 9, 0.5, -0.45)); last = t; } prev = s; } }
429
+ tap(12.3, 1.5, -0.4);
430
+ k.play('impact', 12.3, { gain: 1.1, pan: -0.3 });
431
+ thrum(12.32, ['C3', 'G3', 'C4', 'E4'], 1.6); // the 97% hit
432
+ wind(12.3, 12.7, 36, 0.8, -0.3, 196, 9); // red thread wound round the ring
433
+
434
+ // ---- D: travel, cardioid winds 13.0-14.2, burn the takeaway, resolve
435
+ travel(12.85, 13.35);
436
+ wind(13.0, 14.2, 72, 0.5, 0, 175, 5);
437
+ burn(13.2, 13.65, -0.1);
438
+ burn(13.6, 14.05, 0.1);
439
+ thrum(14.05, ['F2', 'C3', 'A3', 'C4'], 0.8); thrum(14.3, ['C3', 'G3', 'C4', 'E4'], 1.1);
440
+ },
441
+ draw(ctx, W, H_, t) {
442
+ if (!WOOD) { WOOD = H.layer(W, H_); woodBoard(WOOD.getContext('2d'), W, H_, 0); }
443
+ // slow camera push across the whole shot
444
+ const push = 1 + 0.03 * H.ease.inOut(H.clamp(t / 7));
445
+ ctx.save();
446
+ ctx.translate(560, 340); ctx.scale(push, push); ctx.translate(-560, -340);
447
+ ctx.drawImage(WOOD, -20, -20, W + 40, H_ + 40);
448
+ // nail pop-in helper: tapped in over ~3 frames with a tiny bounce
449
+ const nailAt = (x, y, r, seed, t0) => {
450
+ if (t < t0) return;
451
+ const k = H.clamp((t - t0) / 0.1);
452
+ nail(ctx, x, y, r * (1 + 0.35 * (1 - k) * (1 - k)), seed);
453
+ };
454
+
455
+ const cx = 470, cy = 300, R = 172;
456
+ const N = 120;
457
+ // circle nails, leaving a gap at the bottom where the neck joins
458
+ const gapA = 0.36; // radians each side of straight down
459
+ const circ = [];
460
+ for (let i = 0; i < N; i++) {
461
+ const a = -Math.PI / 2 + (i / N) * TAU; // i=0 at top -> cardioid cusp sits low, lobe fills the glass
462
+ circ.push([cx + Math.cos(a) * R, cy + Math.sin(a) * R, a]);
463
+ }
464
+ const isGap = (p) => { let da = Math.abs(((p[2] - Math.PI / 2) % TAU + TAU) % TAU); da = Math.min(da, TAU - da); return da < gapA; };
465
+
466
+ // progress of winding (animatable); at frameT everything is wound
467
+
468
+ // 1) glass glow: fixed-skip chords -> bright annulus whose inner edge is a circle envelope
469
+ const halo = [], halo2 = [];
470
+ for (let i = 0; i < N; i++) {
471
+ const a = circ[i], b = circ[(i + 34) % N], c = circ[(i + 47) % N];
472
+ if (!isGap(a) && !isGap(b)) halo.push([a, b]);
473
+ if (!isGap(a) && !isGap(c) && i % 2 === 0) halo2.push([a, c]);
474
+ }
475
+ thread(ctx, wind(halo2, ph(t, 2.3, 3.4)), '#e8702e', 0.6, 1.0);
476
+ thread(ctx, wind(halo, ph(t, 1.3, 2.7)), '#ffc93a', 0.82, 1.15);
477
+
478
+ // 2) cardioid (times-two table) in cream: its cusp sits on the filament
479
+ const card = [];
480
+ for (let i = 0; i < N; i += 1) {
481
+ const a = circ[i], c = circ[(i * 2) % N];
482
+ if (!isGap(a) && !isGap(c)) card.push([a, c]);
483
+ }
484
+ thread(ctx, wind(card, ph(t, 2.6, 3.7)), '#f6ead2', 0.5, 0.9);
485
+
486
+ // filament: zig-zag between two short rows of nails, wound many times
487
+ const fil = [], filN = [];
488
+ const fy = cy + 30;
489
+ for (let k = 0; k < 6; k++) { filN.push([cx - 50 + k * 20, fy - 14], [cx - 40 + k * 20, fy + 14]); }
490
+ for (let k = 0; k < filN.length - 1; k++) fil.push([filN[k], filN[k + 1]]);
491
+ for (let k = 0; k < filN.length - 2; k += 2) fil.push([filN[k], filN[k + 2]]);
492
+ // filament supports down to the neck
493
+ const sup = [[filN[0], [cx - 30, cy + R * 0.9]], [filN[filN.length - 1], [cx + 30, cy + R * 0.9]]];
494
+ thread(ctx, wind(sup, ph(t, 3.6, 3.8)), '#c9ccd2', 0.8, 1.0);
495
+ thread(ctx, wind(fil, ph(t, 3.7, 4.2)), '#ff8c2a', 0.95, 1.3);
496
+ thread(ctx, wind(fil, ph(t, 4.0, 4.4)), '#ffe08a', 0.6, 0.8);
497
+
498
+ // 3) neck + screw base: nails in two columns, zig-zag cross-hatched silver thread
499
+ const baseTop = cy + Math.cos(gapA) * R + 4, baseL = cx - Math.sin(gapA) * R, baseR = cx + Math.sin(gapA) * R;
500
+ const neckBot = baseTop + 46;
501
+ const nL = [], nR = [];
502
+ const rows = 9;
503
+ for (let k = 0; k <= rows; k++) {
504
+ const y = baseTop + (k / rows) * 104;
505
+ const narrow = k < 3 ? (k / 3) * 14 : 14 + (k >= rows - 1 ? (k - rows + 2) * 16 : 0);
506
+ nL.push([baseL + narrow, y]); nR.push([baseR - narrow, y]);
507
+ }
508
+ const base = [];
509
+ for (let k = 0; k < rows; k++) { base.push([nL[k], nR[k + 1]]); base.push([nR[k], nL[k + 1]]); base.push([nL[k], nR[k]]); }
510
+ // threads also tie the neck into the bulb rim
511
+ const left0 = circ.find((p) => !isGap(p) && p[0] < cx && p[1] > cy + R * 0.85) || circ[6];
512
+ base.push([nL[0], circ[Math.round(N * gapA / TAU) + 1]]); base.push([nR[0], circ[N - Math.round(N * gapA / TAU) - 1]]);
513
+ thread(ctx, wind(base, ph(t, 3.9, 4.7)), '#d4d7dc', 0.85, 1.05);
514
+ // tip
515
+ const tip = [cx, nL[rows][1] + 22];
516
+ const tipSeg = [[nL[rows], tip], [nR[rows], tip], [nL[rows - 1], tip], [nR[rows - 1], tip]];
517
+ thread(ctx, wind(tipSeg, ph(t, 4.6, 4.8)), '#9aa0a8', 0.75, 1.05);
518
+
519
+ // 4) rays: each ray is several wraps between two nails (thicker, slightly fanned)
520
+ const rays = [];
521
+ const rayNails = [];
522
+ const rayAngles = [-150, -118, -90, -62, -30, 0, 180].map((d) => (d * Math.PI) / 180);
523
+ rayAngles.forEach((a, k) => {
524
+ const r0 = R + 30, r1 = R + 30 + (k === 2 ? 70 : 56);
525
+ const p0 = [cx + Math.cos(a) * r0, cy + Math.sin(a) * r0], p1 = [cx + Math.cos(a) * r1, cy + Math.sin(a) * r1];
526
+ const px = -Math.sin(a) * 4, py = Math.cos(a) * 4;
527
+ const q0 = [p0[0] - px, p0[1] - py], q1 = [p0[0] + px, p0[1] + py], q2 = [p1[0] - px * 1.6, p1[1] - py * 1.6], q3 = [p1[0] + px * 1.6, p1[1] + py * 1.6];
528
+ rayNails.push(q0, q1, q2, q3);
529
+ const len = H.ease.out(ph(t, 4.5 + k * 0.07, 4.75 + k * 0.07));
530
+ if (len > 0) {
531
+ const L = (a0, b0) => [a0, [a0[0] + (b0[0] - a0[0]) * len, a0[1] + (b0[1] - a0[1]) * len]];
532
+ rays.push(L(q0, q2), L(q1, q3), L(q0, q3), L(q1, q2), L(q0, q2), L(q1, q3));
533
+ }
534
+ });
535
+ thread(ctx, rays, '#ffc93a', 0.85, 1.15);
536
+
537
+ // 5) the bulb lights up: warm bloom round the filament + a soft glow off the gold and filament threads
538
+ const glowK = 0.35 * ph(t, 1.3, 2.7) + 0.65 * ph(t, 3.7, 4.4);
539
+ if (glowK > 0) {
540
+ const GS = 440, gx0 = cx - GS / 2, gy0 = cy - GS / 2 + 10, done = t >= 4.4;
541
+ let gb = done ? BURNC.heroGlow : null;
542
+ if (!gb) {
543
+ const gl = H.layer(GS, GS), gc = gl.getContext('2d');
544
+ gc.translate(-gx0, -gy0); gc.lineCap = 'round';
545
+ const strokeSet = (segs, col, w) => { if (!segs.length) return; gc.strokeStyle = col; gc.lineWidth = w; gc.beginPath(); for (const [a, b] of segs) { gc.moveTo(a[0], a[1]); gc.lineTo(b[0], b[1]); } gc.stroke(); };
546
+ strokeSet(wind(halo, ph(t, 1.3, 2.7)), '#ffc93a', 2.2);
547
+ strokeSet(wind(fil, ph(t, 3.7, 4.2)), '#ffb050', 4);
548
+ gb = blurred(gl, 4); if (done) BURNC.heroGlow = gb;
549
+ }
550
+ ctx.save(); ctx.globalCompositeOperation = 'screen';
551
+ const g = ctx.createRadialGradient(cx, fy, 0, cx, fy, 270);
552
+ g.addColorStop(0, `rgba(255,205,120,${0.34 * glowK})`); g.addColorStop(0.4, `rgba(255,170,80,${0.15 * glowK})`); g.addColorStop(1, 'rgba(255,150,60,0)');
553
+ ctx.fillStyle = g; ctx.fillRect(cx - 280, fy - 280, 560, 560);
554
+ ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = 0.35 * Math.min(1, glowK * 1.4);
555
+ ctx.drawImage(gb, gx0 - 8, gy0 - 8); ctx.restore();
556
+ }
557
+
558
+ // nails on top of threads (threads wrap around shafts under the heads)
559
+ let s = 1;
560
+ circ.forEach((p, i) => { if (!isGap(p)) nailAt(p[0], p[1], 3.3, s++, 0.05 + (i / N) * 0.85); });
561
+ nL.forEach((p, k) => nailAt(p[0], p[1], 3.3, s++, 0.9 + k * 0.025));
562
+ nR.forEach((p, k) => nailAt(p[0], p[1], 3.3, s++, 0.92 + k * 0.025));
563
+ nailAt(tip[0], tip[1], 3.3, s++, 1.18);
564
+ filN.forEach((p, k) => nailAt(p[0], p[1], 2.8, s++, 1.0 + k * 0.02));
565
+ nailAt(cx - 30, cy + R * 0.9, 2.8, s++, 1.0); nailAt(cx + 30, cy + R * 0.9, 2.8, s++, 1.02);
566
+ rayNails.forEach((p, k) => nailAt(p[0], p[1], 3.0, s++, 1.05 + k * 0.012));
567
+
568
+ // ---------- pyrography lettering ----------
569
+ const TX = 1000, TY = 318;
570
+ const burn = H.layer(W, H_), bc = burn.getContext('2d');
571
+ bc.textAlign = 'center'; bc.textBaseline = 'alphabetic';
572
+ bc.font = '700 92px "Caveat"';
573
+ bc.fillStyle = '#000';
574
+ bc.fillText('Bright', TX - 20, TY);
575
+ bc.fillText('Ideas', TX + 30, TY + 92);
576
+ // burn-in: the pen travels left->right over each line (line 1 then line 2)
577
+ const b1 = ph(t, 4.9, 5.5), b2 = ph(t, 5.45, 6.0);
578
+ const reveal = H.layer(W, H_), rv = reveal.getContext('2d');
579
+ const x0 = TX - 130, x1 = TX + 82, y0 = TX - 72, y1 = TX + 140;
580
+ if (b1 > 0) rv.fillRect(0, TY - 110, x0 + (x1 - x0) * b1, 132);
581
+ if (b2 > 0) rv.fillRect(0, TY + 22, y0 + (y1 - y0) * b2, 110);
582
+ bc.globalCompositeOperation = 'destination-in'; bc.drawImage(reveal, 0, 0); bc.globalCompositeOperation = 'source-over';
583
+ // toasted rim: the walnut round the burn is singed paler and golden (same treatment as the explainer)
584
+ if (b1 > 0) {
585
+ const rx0 = TX - 300, ry0 = TY - 150, rw = 620, rh = 330, done = b2 >= 1;
586
+ let tb = done ? BURNC.heroToast : null;
587
+ if (!tb) { const tl = H.layer(rw, rh); tl.getContext('2d').drawImage(burn, rx0, ry0, rw, rh, 0, 0, rw, rh); tb = blurred(tl, 12, 'rgb(214,150,80)'); if (done) BURNC.heroToast = tb; }
588
+ ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.globalAlpha = 0.5; ctx.drawImage(tb, rx0 - 24, ry0 - 24); ctx.restore();
589
+ }
590
+ // scorch halo
591
+ ctx.save();
592
+ ctx.filter = 'blur(4px)'; ctx.globalAlpha = 0.55; ctx.globalCompositeOperation = 'multiply';
593
+ ctx.drawImage(burn, 0, 0);
594
+ ctx.filter = 'none';
595
+ ctx.globalAlpha = 1;
596
+ // burnt core: dark umber, slightly uneven
597
+ const core = H.layer(W, H_), cc = core.getContext('2d');
598
+ cc.drawImage(burn, 0, 0);
599
+ cc.globalCompositeOperation = 'source-in';
600
+ const cg = cc.createLinearGradient(TX - 200, 0, TX + 200, 0);
601
+ cg.addColorStop(0, '#1d0d05'); cg.addColorStop(0.5, '#2b1408'); cg.addColorStop(1, '#170903');
602
+ cc.fillStyle = cg; cc.fillRect(0, 0, W, H_);
603
+ // uneven pen pressure: lighter scorched patches inside strokes
604
+ const bn = H.makeNoise(5);
605
+ cc.globalCompositeOperation = 'source-atop';
606
+ for (let y = TY - 90; y < TY + 30 + 92; y += 4) for (let x = TX - 220; x < TX + 220; x += 4) {
607
+ const v = bn.n2(x * 0.03, y * 0.03);
608
+ if (v > 0.05) { cc.fillStyle = `rgba(120,62,24,${Math.min(0.6, v * 1.2)})`; cc.fillRect(x, y, 4, 4); }
609
+ }
610
+ ctx.globalCompositeOperation = 'source-over'; ctx.globalAlpha = 0.92;
611
+ ctx.drawImage(core, 0, 0);
612
+ ctx.restore();
613
+ // ember at the pen tip while burning, with a wisp of smoke
614
+ const ember = (bx, by, k) => {
615
+ if (k <= 0 || k >= 1) return;
616
+ const g = ctx.createRadialGradient(bx, by, 0, bx, by, 26);
617
+ g.addColorStop(0, 'rgba(255,190,90,0.9)'); g.addColorStop(0.3, 'rgba(255,110,30,0.45)'); g.addColorStop(1, 'rgba(255,80,20,0)');
618
+ ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = g; ctx.fillRect(bx - 30, by - 30, 60, 60); ctx.restore();
619
+ const sm = ctx.createRadialGradient(bx + 6, by - 40, 0, bx + 6, by - 40, 36);
620
+ sm.addColorStop(0, 'rgba(220,210,200,0.12)'); sm.addColorStop(1, 'rgba(220,210,200,0)');
621
+ ctx.fillStyle = sm; ctx.fillRect(bx - 40, by - 90, 90, 90);
622
+ };
623
+ ember(x0 + (x1 - x0) * b1, TY - 30 + Math.sin(t * 40) * 14, b1);
624
+ ember(y0 + (y1 - y0) * b2, TY + 62 + Math.sin(t * 40) * 14, b2);
625
+ // small burnt underline flourish + caption
626
+ const fl = ph(t, 5.95, 6.25);
627
+ if (fl > 0) {
628
+ ctx.save();
629
+ ctx.strokeStyle = 'rgba(28,12,4,0.85)'; ctx.lineWidth = 2.4; ctx.lineCap = 'round';
630
+ ctx.beginPath(); ctx.rect(TX - 130, TY + 100, 290 * fl, 40); ctx.clip();
631
+ ctx.beginPath(); ctx.moveTo(TX - 120, TY + 122); ctx.bezierCurveTo(TX - 40, TY + 112, TX + 60, TY + 132, TX + 150, TY + 116); ctx.stroke();
632
+ ctx.restore();
633
+ }
634
+ if (t >= 6.2) {
635
+ ctx.save(); ctx.font = '400 26px "Caveat"'; ctx.fillStyle = 'rgba(28,12,4,0.8)'; ctx.textAlign = 'center';
636
+ ctx.fillText('~ series no. 7 ~', TX + 10, TY + 160); ctx.restore();
637
+ }
638
+ ctx.restore(); // camera
639
+
640
+ // light falloff + fine film grain
641
+ H.vignette(ctx, W, H_, 0.42, '20,8,2');
642
+ H.grain(ctx, W, H_, 0.035, 12 + Math.floor(t * 12));
643
+ },
644
+ });
645
+ }