@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,697 @@
1
+ {
2
+ // ---------- Two-colour linocut (reduction red + black key) on rice paper ----------
3
+ const PAPER = [240, 232, 212];
4
+ const RED = [196, 58, 38];
5
+ const INKK = [26, 23, 22];
6
+ const BX0 = 250, BY0 = 30, BX1 = 1030, BY1 = 578; // block
7
+ const CX = 640, CY = 232, R = 126; // bulb
8
+
9
+ // tapered stroke along a parametric path P(s) -> [x,y]
10
+ const tapered = (x, P, w, prof, N = 14) => {
11
+ const L = [], Rr = [];
12
+ for (let i = 0; i <= N; i++) {
13
+ const s = i / N, a = P(Math.max(0, s - 0.01)), b = P(Math.min(1, s + 0.01)), p = P(s);
14
+ let dx = b[0] - a[0], dy = b[1] - a[1]; const l = Math.hypot(dx, dy) || 1; dx /= l; dy /= l;
15
+ const ww = (w * prof(s)) / 2;
16
+ L.push([p[0] - dy * ww, p[1] + dx * ww]); Rr.push([p[0] + dy * ww, p[1] - dx * ww]);
17
+ }
18
+ x.beginPath(); x.moveTo(L[0][0], L[0][1]);
19
+ for (let i = 1; i < L.length; i++) x.lineTo(L[i][0], L[i][1]);
20
+ for (let i = Rr.length - 1; i >= 0; i--) x.lineTo(Rr[i][0], Rr[i][1]);
21
+ x.closePath(); x.fill();
22
+ };
23
+ const lens = (s) => Math.pow(Math.sin(Math.PI * s), 0.6);
24
+ const tail = (s) => Math.min(1, s * 7) * Math.pow(1 - s, 0.9);
25
+ const quad = (x0, y0, cx, cy, x1, y1) => (s) => [(1 - s) * (1 - s) * x0 + 2 * (1 - s) * s * cx + s * s * x1, (1 - s) * (1 - s) * y0 + 2 * (1 - s) * s * cy + s * s * y1];
26
+ const line = (x0, y0, x1, y1, bend) => { const nx = -(y1 - y0), ny = x1 - x0; return quad(x0, y0, (x0 + x1) / 2 + nx * bend, (y0 + y1) / 2 + ny * bend, x1, y1); };
27
+
28
+ const bulbPath = (x) => {
29
+ x.beginPath();
30
+ x.arc(CX, CY, R, Math.PI * 0.72, Math.PI * 0.28 + Math.PI * 2, false);
31
+ x.bezierCurveTo(CX + 78, CY + 128, CX + 54, CY + 140, CX + 52, CY + 168);
32
+ x.lineTo(CX - 52, CY + 168);
33
+ x.bezierCurveTo(CX - 54, CY + 140, CX - 78, CY + 128, CX + R * Math.cos(Math.PI * 0.72), CY + R * Math.sin(Math.PI * 0.72));
34
+ x.closePath();
35
+ };
36
+ const hillF = (xx) => 446 + Math.pow((xx - 610) / 230, 2) * 34;
37
+ const hillB = (xx) => 418 + Math.pow((xx - 930) / 140, 2) * 26;
38
+ const leaf = (x, x0, y0, x1, y1, bend) => {
39
+ const mx = (x0 + x1) / 2, my = (y0 + y1) / 2, nx = -(y1 - y0), ny = x1 - x0;
40
+ x.beginPath(); x.moveTo(x0, y0);
41
+ x.quadraticCurveTo(mx + nx * bend, my + ny * bend, x1, y1);
42
+ x.quadraticCurveTo(mx - nx * bend * 0.45, my - ny * bend * 0.45, x0, y0); x.closePath();
43
+ };
44
+
45
+ const lowField = (W, H_, seed, gw, gh, f) => {
46
+ const nz = H.makeNoise(seed), s = H.layer(gw, gh), sx = s.getContext('2d'), im = sx.createImageData(gw, gh);
47
+ for (let y = 0; y < gh; y++) for (let x = 0; x < gw; x++) {
48
+ const i = (y * gw + x) * 4;
49
+ im.data[i] = 128 + 127 * H.clamp(nz.fbm2(x * f, y * f, 3) * 1.7, -1, 1);
50
+ im.data[i + 1] = 128 + 127 * H.clamp(nz.fbm2(x * f * 2.3 + 17, y * f * 0.6, 2) * 1.7, -1, 1);
51
+ im.data[i + 2] = 128 + 127 * H.clamp(nz.n2(x * f * 3 + 5, y * f * 3 + 9) * 1.4, -1, 1);
52
+ im.data[i + 3] = 255;
53
+ }
54
+ sx.putImageData(im, 0, 0);
55
+ const b = H.layer(W, H_), bx = b.getContext('2d'); bx.imageSmoothingQuality = 'high'; bx.drawImage(s, 0, 0, W, H_);
56
+ return bx.getImageData(0, 0, W, H_).data;
57
+ };
58
+
59
+ // ================= EXPLAINER: a run of proofs, each pulled from a newly carved block =================
60
+ const LE = H.ease, lcl = H.clamp, lseg = (t, a, b) => lcl((t - a) / (b - a));
61
+ const KX0 = 64, KY0 = 34, KX1 = 1216, KY1 = 586; // block area
62
+ const PULL = [0, 2.25, 5.85, 10.35, 12.95], PEEL = 0.8; // a new proof is pulled at each time
63
+ let LX_CACHE = null;
64
+ const lhash = (x, y, s) => { let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(s, 1442695041)) | 0; h = Math.imul(h ^ (h >>> 13), 1274126177); h ^= h >>> 16; return (h >>> 0) / 4294967296; };
65
+
66
+ // carving vocabulary
67
+ const carveText = (k, s, font, x, y, align = 'center', ls = 4, jitSeed = 1) => {
68
+ const r = H.rng(jitSeed); k.font = font; k.textBaseline = 'middle'; k.letterSpacing = ls + 'px';
69
+ const tw = k.measureText(s).width; let cx = align === 'center' ? x - tw / 2 : align === 'right' ? x - tw : x;
70
+ k.textAlign = 'left';
71
+ for (const ch of s) { const cw = k.measureText(ch).width; k.save(); k.translate(cx, y + r.range(-1.5, 1.5)); k.rotate(r.range(-0.03, 0.03)); k.fillText(ch, 0, 0); k.restore(); cx += cw; }
72
+ k.letterSpacing = '0px';
73
+ };
74
+ const gougeArrow = (k, P, u, w) => { // tapered carved stroke along P with a carved head, drawn up to u
75
+ if (u <= 0) return;
76
+ const Q = (s) => P(s * u);
77
+ tapered(k, Q, w, (s) => Math.min(1, s * 8) * (0.7 + 0.3 * s), 22);
78
+ const e = P(u), b = P(Math.max(0, u - 0.03)), a = Math.atan2(e[1] - b[1], e[0] - b[0]), hs = w * 2.4;
79
+ k.beginPath(); k.moveTo(e[0] + Math.cos(a) * hs, e[1] + Math.sin(a) * hs); k.lineTo(e[0] + Math.cos(a + 2.4) * hs, e[1] + Math.sin(a + 2.4) * hs); k.lineTo(e[0] + Math.cos(a - 2.4) * hs, e[1] + Math.sin(a - 2.4) * hs); k.closePath(); k.fill();
80
+ };
81
+ const raysAround = (k, cx, cy, r0, r1, n, seed, w = 16) => { const r = H.rng(seed); for (let i = 0; i < n; i++) { const a = (i / n) * Math.PI * 2 + r.range(-0.03, 0.03); const long = i % 2 === 0; tapered(k, line(cx + Math.cos(a) * r0, cy + Math.sin(a) * r0, cx + Math.cos(a) * (long ? r1 : r0 + (r1 - r0) * 0.5), cy + Math.sin(a) * (long ? r1 : r0 + (r1 - r0) * 0.5), r.range(-0.02, 0.02)), long ? w : w * 0.55, lens); } };
82
+ const chips = (k, n, seed, avoid) => { const r = H.rng(seed); for (let i = 0; i < n; i++) { const x0 = r.range(KX0 + 24, KX1 - 24), y0 = r.range(KY0 + 24, KY1 - 24); if (avoid && avoid(x0, y0)) continue; const a = r.range(-0.5, 0.5), l = r.range(5, 12); tapered(k, line(x0, y0, x0 + Math.cos(a) * l, y0 + Math.sin(a) * l, 0.15), r.range(2, 4), lens, 4); } };
83
+ // hand-cut gouge chatter for open black fields, placed where a cutter actually leaves it: echo nicks radiating
84
+ // just outside a carved shape (ring) and short clearing cuts along the inside of the block border (edge)
85
+ const nick = (k, x, y, a, l, w, r) => tapered(k, line(x, y, x + Math.cos(a) * l, y + Math.sin(a) * l, r.range(-0.06, 0.06)), w, lens, 6);
86
+ const chatterRing = (k, cx, cy, r0, r1, n, seed, avoid) => {
87
+ const r = H.rng(seed);
88
+ for (let i = 0; i < n; i++) {
89
+ const a = r() * Math.PI * 2, rr = r0 + r() * r() * (r1 - r0), x = cx + Math.cos(a) * rr, y = cy + Math.sin(a) * rr, l = r.range(12, 30) * (1 - (rr - r0) / (r1 - r0) * 0.5);
90
+ if (avoid && (avoid(x, y) || avoid(x + Math.cos(a) * l, y + Math.sin(a) * l))) continue;
91
+ nick(k, x, y, a + r.range(-0.08, 0.08), l, r.range(3, 6), r);
92
+ if (r.chance(0.45)) nick(k, x + Math.cos(a + 1.57) * 9, y + Math.sin(a + 1.57) * 9, a + r.range(-0.1, 0.1), l * r.range(0.5, 0.85), r.range(2.5, 4.5), r);
93
+ }
94
+ };
95
+ const chatterEdge = (k, n, seed, avoid) => {
96
+ const r = H.rng(seed), x0 = KX0 + 20, y0 = KY0 + 20, x1 = KX1 - 20, y1 = KY1 - 20;
97
+ for (let i = 0; i < n; i++) {
98
+ const side = Math.floor(r() * 4), u = r();
99
+ const [x, y, a] = side === 0 ? [H.lerp(x0, x1, u), y0, Math.PI / 2] : side === 1 ? [H.lerp(x0, x1, u), y1, -Math.PI / 2] : side === 2 ? [x0, H.lerp(y0, y1, u), 0] : [x1, H.lerp(y0, y1, u), Math.PI];
100
+ const l = r.range(10, 26), aa = a + r.range(-0.35, 0.35);
101
+ if (avoid && (avoid(x, y) || avoid(x + Math.cos(aa) * l, y + Math.sin(aa) * l))) continue;
102
+ nick(k, x, y, aa, l, r.range(3, 5.5), r);
103
+ }
104
+ };
105
+ // carved animal tile: paper square with the animal left standing in black lines, red label strip
106
+ const tile = (k, rd, cx, cy, s, kind, label) => {
107
+ const h = s / 2;
108
+ k.globalCompositeOperation = 'destination-out'; k.fillRect(cx - h, cy - h, s, s * 1.25); k.globalCompositeOperation = 'source-over';
109
+ k.fillStyle = '#000'; k.strokeStyle = '#000'; k.lineWidth = Math.max(3, s * 0.05); k.lineCap = 'round'; k.lineJoin = 'round';
110
+ k.beginPath(); k.arc(cx, cy, s * 0.28, 0, 7); k.stroke();
111
+ if (kind === 'cat') { k.beginPath(); k.moveTo(cx - s * 0.24, cy - s * 0.12); k.lineTo(cx - s * 0.22, cy - s * 0.4); k.lineTo(cx - s * 0.05, cy - s * 0.26); k.moveTo(cx + s * 0.24, cy - s * 0.12); k.lineTo(cx + s * 0.22, cy - s * 0.4); k.lineTo(cx + s * 0.05, cy - s * 0.26); k.stroke(); k.beginPath(); for (const sg of [-1, 1]) { k.moveTo(cx + sg * s * 0.08, cy + s * 0.08); k.lineTo(cx + sg * s * 0.4, cy + s * 0.05); k.moveTo(cx + sg * s * 0.08, cy + s * 0.12); k.lineTo(cx + sg * s * 0.38, cy + s * 0.16); } k.stroke(); }
112
+ else { for (const sg of [-1, 1]) { k.beginPath(); k.ellipse(cx + sg * s * 0.28, cy, s * 0.08, s * 0.18, sg * 0.4, 0, 7); k.fill(); } k.beginPath(); k.ellipse(cx, cy + s * 0.1, s * 0.06, s * 0.045, 0, 0, 7); k.fill(); }
113
+ k.beginPath(); k.arc(cx - s * 0.1, cy - s * 0.04, s * 0.035, 0, 7); k.arc(cx + s * 0.1, cy - s * 0.04, s * 0.035, 0, 7); k.fill();
114
+ // label strip: the black is cleared, so it prints as clean vermilion with the letters cut from the red block
115
+ rd.fillRect(cx - h, cy + h, s, s * 0.25);
116
+ if (label) { rd.globalCompositeOperation = 'destination-out'; carveText(rd, label, `400 ${Math.round(s * (label === '?' ? 0.21 : 0.17))}px "Archivo Black"`, cx, cy + h + s * 0.125, 'center', 2, s | 0); rd.globalCompositeOperation = 'source-over'; }
117
+ };
118
+ // the model: a carved head-sized ring with a neuron web inside, plus three dials
119
+ const brain = (k, rd, cx, cy, r, dials, glow) => {
120
+ k.globalCompositeOperation = 'destination-out';
121
+ k.lineWidth = 9; k.beginPath(); k.arc(cx, cy, r, 0, 7); k.stroke();
122
+ const nodes = [[-0.5, -0.35], [0.1, -0.55], [0.55, -0.2], [-0.15, 0.0], [0.4, 0.3], [-0.55, 0.25], [0.05, 0.5]];
123
+ k.lineWidth = 5; k.beginPath();
124
+ [[0, 1], [1, 2], [0, 3], [1, 3], [2, 4], [3, 4], [3, 5], [5, 6], [4, 6], [2, 3]].forEach(([a, b]) => { k.moveTo(cx + nodes[a][0] * r, cy + nodes[a][1] * r); k.lineTo(cx + nodes[b][0] * r, cy + nodes[b][1] * r); });
125
+ k.stroke();
126
+ nodes.forEach(([a, b]) => { k.beginPath(); k.arc(cx + a * r, cy + b * r, r * 0.08, 0, 7); k.fill(); });
127
+ k.globalCompositeOperation = 'source-over';
128
+ rd.beginPath(); rd.arc(cx, cy, r - 6, 0, 7); rd.fill();
129
+ if (dials) dials.forEach((a, i) => {
130
+ const dx = cx + (i - 1) * r * 0.62, dy = cy + r + 52;
131
+ k.globalCompositeOperation = 'destination-out'; k.beginPath(); k.arc(dx, dy, 30, 0, 7); k.fill(); k.globalCompositeOperation = 'source-over';
132
+ k.lineWidth = 8; k.lineCap = 'round'; k.beginPath(); k.moveTo(dx, dy); k.lineTo(dx + Math.cos(a) * 24, dy + Math.sin(a) * 24); k.stroke();
133
+ k.lineWidth = 3; k.beginPath(); k.arc(dx, dy, 26, 0, 7); k.stroke();
134
+ });
135
+ if (glow) { k.globalCompositeOperation = 'destination-out'; raysAround(k, cx, cy, r + 14, r + 70, 30, 9, 8); k.globalCompositeOperation = 'source-over'; }
136
+ };
137
+
138
+ // draw scene si's two blocks (black key into k, red into rd) at time t
139
+ const scene = (si, k, rd, t) => {
140
+ const st = Math.floor(t * 12) / 12; // stop-motion: each re-pull on twos
141
+ k.fillStyle = '#000'; rd.fillStyle = '#000'; k.strokeStyle = '#000';
142
+ // block
143
+ k.fillRect(KX0, KY0, KX1 - KX0, KY1 - KY0);
144
+ k.globalCompositeOperation = 'destination-out'; k.lineWidth = 4; k.strokeRect(KX0 + 14, KY0 + 14, KX1 - KX0 - 28, KY1 - KY0 - 28); k.globalCompositeOperation = 'source-over';
145
+ const out = () => { k.globalCompositeOperation = 'destination-out'; k.fillStyle = '#000'; }, inn = () => { k.globalCompositeOperation = 'source-over'; k.fillStyle = '#000'; };
146
+ // reduction band: black cleared inside a rim (kept off the carved border), red block fills it; band(y0[, y1])
147
+ const band = (y0, y1 = KY1 - 18) => {
148
+ k.fillRect(KX0 + 18, y0, KX1 - KX0 - 36, y1 - y0);
149
+ out(); k.fillRect(KX0 + 24, y0 + 6, KX1 - KX0 - 48, y1 - y0 - 12); inn();
150
+ rd.fillRect(KX0 + 24, y0 + 6, KX1 - KX0 - 48, y1 - y0 - 12);
151
+ };
152
+ // letters cut from the red block: paper-coloured type on the vermilion band
153
+ const redText = (str, font, x, y, ls, seed) => { rd.globalCompositeOperation = 'destination-out'; carveText(rd, str, font, x, y, 'center', ls, seed); rd.globalCompositeOperation = 'source-over'; rd.fillStyle = '#000'; };
154
+ if (si === 0) {
155
+ rd.beginPath(); rd.arc(640, 270, 230, 0, 7); rd.fill();
156
+ out(); raysAround(k, 640, 270, 175, 640, 44, 3, 18); chips(k, 120, 5, (x, y) => Math.hypot(x - 640, y - 270) < 300 || y > 470);
157
+ k.beginPath(); k.arc(640, 270, 160, 0, 7); k.fill(); inn();
158
+ k.font = '400 270px "Archivo Black"'; k.textAlign = 'center'; k.textBaseline = 'middle'; k.fillText('?', 646, 290);
159
+ band(480);
160
+ redText('HOW DOES AI LEARN?', '400 58px "Archivo Black"', 640, 532, 8, 11);
161
+ }
162
+ if (si === 1) {
163
+ brain(k, rd, 960, 250, 150, null, false);
164
+ out(); carveText(k, 'MODEL', '400 32px "Archivo Black"', 960, 440, 'center', 8, 4); inn();
165
+ out(); gougeArrow(k, quad(150, 360, 470, 120, 790, 250), LE.out(lseg(st, 2.9, 3.4)), 12); inn();
166
+ // tiles ride the arrow into the model, re-cut each frame
167
+ for (let j = 0; j < 9; j++) {
168
+ const t0 = 3.0 + j * 0.34, u = lseg(st, t0, t0 + 1.3); if (u <= 0 || u >= 1) continue;
169
+ const q = quad(150, 330, 470, 90, 820, 230)(u), kind = j % 3 === 1 ? 'dog' : 'cat';
170
+ tile(k, rd, q[0], q[1] - 40, 150 * (1 - u * 0.45), kind, kind.toUpperCase());
171
+ }
172
+ chips(k, 60, 6, (x, y) => y > 440 || Math.hypot(x - 960, y - 260) < 230);
173
+ out(); chatterRing(k, 960, 250, 168, 232, 70, 61, (x, y) => y > 405 || x > KX1 - 30 || (x < 840 && y < 320)); chatterEdge(k, 34, 62, (x, y) => y > 460 || (x < 360 && y < 120)); inn();
174
+ // counter band
175
+ band(476);
176
+ const n = Math.round(1e6 * Math.pow(lseg(st, 3.1, 5.4), 2.3));
177
+ redText(n.toLocaleString('en-US') + ' EXAMPLES', '400 64px "Archivo Black"', 640, 530, 4, 13);
178
+ }
179
+ if (si === 2) {
180
+ const right = st >= 9.05;
181
+ tile(k, rd, 200, 230, 190, 'cat', '?');
182
+ const adj = LE.inOut(lseg(st, 8.3, 8.9));
183
+ brain(k, rd, 600, 230, 140, [-2.2 + adj * 1.9, -0.6 - adj * 1.4, 0.8 + adj * 2.3], right);
184
+ out();
185
+ gougeArrow(k, line(310, 230, 440, 230, -0.15), LE.out(lseg(st, 6.45, 6.8)), 12);
186
+ if (st > 6.6) carveText(k, 'GUESS', '400 30px "Archivo Black"', 375, 140, 'center', 6, 21);
187
+ gougeArrow(k, line(760, 230, 860, 200, -0.1), LE.out(lseg(st, 6.85, 7.1)), 12);
188
+ // answer card
189
+ if (st > 7.0) { k.fillRect(880, 120, 280, 170); }
190
+ inn();
191
+ if (st > 7.0) {
192
+ k.font = '400 80px "Archivo Black"'; k.textAlign = 'center'; k.textBaseline = 'middle'; k.fillText(right ? 'cat' : 'dog?', 1020, 205);
193
+ if (!right && st > 7.45) { const u = LE.out(lseg(st, 7.45, 7.6)); rd.save(); rd.translate(1130, 135); rd.scale(u, u); rd.lineWidth = 22; rd.lineCap = 'round'; rd.strokeStyle = '#000'; rd.beginPath(); rd.moveTo(-36, -36); rd.lineTo(36, 36); rd.moveTo(36, -36); rd.lineTo(-36, 36); rd.stroke(); rd.restore(); }
194
+ if (right) { const u = LE.back(lseg(st, 9.05, 9.3)); rd.save(); rd.translate(1130, 135); rd.scale(u, u); rd.lineWidth = 22; rd.lineCap = 'round'; rd.lineJoin = 'round'; rd.strokeStyle = '#000'; rd.beginPath(); rd.moveTo(-34, 0); rd.lineTo(-10, 26); rd.lineTo(36, -30); rd.stroke(); rd.restore(); }
195
+ }
196
+ out();
197
+ if (st > 7.45) carveText(k, 'CHECK', '400 30px "Archivo Black"', 1020, 330, 'center', 6, 22);
198
+ // error travels back: carved return channel, printed red
199
+ const ra = LE.inOut(lseg(st, 7.9, 8.4));
200
+ const back = quad(1020, 380, 900, 520, 720, 440);
201
+ gougeArrow(k, back, ra, 22);
202
+ if (st > 8.1) carveText(k, 'ADJUST', '400 30px "Archivo Black"', 930, 545, 'center', 6, 23);
203
+ chips(k, 70, 7, (x, y) => (x > 860 && y < 360) || (x < 330 && y < 400) || Math.hypot(x - 600, y - 280) < 260 || y > 380);
204
+ chatterRing(k, 600, 230, 158, 215, 60, 71, (x, y) => y > 395 || Math.abs(y - 230) < 34 || (y < 165 && x < 470) || x > 860);
205
+ chatterEdge(k, 40, 72, (x, y) => (x > 860 && y < 300) || (x < 310 && y < 380) || (x > 820 && x < 1040 && y > 515) || (x > 280 && x < 470 && y < 165));
206
+ inn();
207
+ if (ra > 0) { rd.save(); rd.lineWidth = 30; rd.lineCap = 'round'; rd.beginPath(); for (let i = 0; i <= 20 * ra; i++) { const q = back(i / 20); i ? rd.lineTo(q[0], q[1]) : rd.moveTo(q[0], q[1]); } rd.stroke(); rd.restore(); }
208
+ // keep the loop alive: the knob turns again on each cycle
209
+ }
210
+ if (si === 3) {
211
+ const X0 = 150, Y0 = 470, CW = 560, CHh = 360;
212
+ const prog = LE.inOut(lseg(st, 10.9, 12.3));
213
+ const acc = (u) => 61 + 36 * (1 - Math.exp(-u * 3.2)) / (1 - Math.exp(-3.2));
214
+ const yv = (v) => Y0 - ((v - 50) / 50) * CHh;
215
+ // red fill under the curve (red block), hatch carved over it in the black block
216
+ out(); k.beginPath(); k.moveTo(X0, Y0); for (let i = 0; i <= 60 * prog; i++) { const u = i / 60; k.lineTo(X0 + u * CW, yv(acc(u))); } k.lineTo(X0 + prog * CW, Y0); k.closePath(); k.fill(); inn();
217
+ rd.beginPath(); rd.moveTo(X0, Y0); for (let i = 0; i <= 60 * prog; i++) { const u = i / 60; rd.lineTo(X0 + u * CW, yv(acc(u))); } rd.lineTo(X0 + prog * CW, Y0); rd.closePath(); rd.fill();
218
+ k.save(); k.beginPath(); k.moveTo(X0, Y0); for (let i = 0; i <= 60 * prog; i++) { const u = i / 60; k.lineTo(X0 + u * CW, yv(acc(u))); } k.lineTo(X0 + prog * CW, Y0); k.closePath(); k.clip();
219
+ for (let j = 0; j < 30; j++) { k.fillRect(X0, yv(acc(1)) + j * 13, CW, 4.5 + j * 0.12); }
220
+ k.restore();
221
+ out();
222
+ k.lineWidth = 6; k.beginPath(); k.moveTo(X0, Y0 - CHh - 10); k.lineTo(X0, Y0); k.lineTo(X0 + CW + 20, Y0); k.stroke();
223
+ if (prog > 0) tapered(k, (s) => { const u = s * prog; return [X0 + u * CW, yv(acc(u)) - 4]; }, 12, (s) => Math.min(1, s * 10), 40);
224
+ carveText(k, 'TRAINING ROUNDS', '400 26px "Archivo Black"', X0 + CW / 2, Y0 + 32, 'center', 4, 31);
225
+ carveText(k, 'ACCURACY', '400 34px "Archivo Black"', 950, 130, 'center', 10, 32);
226
+ const v = Math.round(61 + 36 * prog);
227
+ carveText(k, v + '%', '400 190px "Archivo Black"', 950, 290, 'center', -4, 33);
228
+ if (st > 12.3) raysAround(k, 950, 290, 210, 330, 26, 11, 9);
229
+ chatterEdge(k, 46, 81, (x, y) => (x > 140 && x < 740 && y > 470 && y < 530) || (x > 700 && y < 140) || x > 720);
230
+ { const uh = 22 * LE.out(lseg(st, 12.3, 12.5)); if (uh > 0) k.fillRect(800, 390, 300, uh); } // clear the black so the underline prints vermilion
231
+ inn();
232
+ rd.fillRect(800, 390, 300, 22 * LE.out(lseg(st, 12.3, 12.5)));
233
+ }
234
+ if (si === 4) {
235
+ out(); raysAround(k, 640, 300, 60, 760, 48, 41, 18); inn();
236
+ k.fillRect(KX0 + 18, 140, KX1 - KX0 - 36, 120);
237
+ rd.fillRect(560, 250, 160, 110);
238
+ band(350, 470);
239
+ out();
240
+ k.lineWidth = 4; k.strokeRect(KX0 + 40, 154, KX1 - KX0 - 80, 92);
241
+ carveText(k, 'MORE EXAMPLES', '400 78px "Archivo Black"', 640, 202, 'center', 4, 41);
242
+ gougeArrow(k, line(640, 262, 640, 330, 0), LE.out(lseg(st, 13.5, 13.8)), 18);
243
+ inn();
244
+ redText('BETTER GUESSES', '400 78px "Archivo Black"', 640, 412, 4, 42);
245
+ }
246
+ };
247
+
248
+ const linoExplainer = (ctx, W, H_, t) => {
249
+ if (!LX_CACHE) {
250
+ const paper = H.layer(W, H_), px = paper.getContext('2d'), r = H.rng(12);
251
+ px.fillStyle = `rgb(${PAPER})`; px.fillRect(0, 0, W, H_); px.lineCap = 'round';
252
+ for (let i = 0; i < 520; i++) {
253
+ const x0 = r.range(-40, W + 40), y0 = r.range(-40, H_ + 40), a = r.range(0, Math.PI * 2), l = r.range(30, 170);
254
+ px.strokeStyle = r.chance(0.65) ? `rgba(255,252,240,${r.range(0.25, 0.6)})` : `rgba(150,128,90,${r.range(0.08, 0.2)})`; px.lineWidth = r.range(0.5, 1.6);
255
+ px.beginPath(); px.moveTo(x0, y0); px.bezierCurveTo(x0 + Math.cos(a) * l * 0.3 + r.range(-15, 15), y0 + Math.sin(a) * l * 0.3 + r.range(-15, 15), x0 + Math.cos(a) * l * 0.7 + r.range(-15, 15), y0 + Math.sin(a) * l * 0.7 + r.range(-15, 15), x0 + Math.cos(a) * l, y0 + Math.sin(a) * l); px.stroke();
256
+ }
257
+ LX_CACHE = { paper: px.getImageData(0, 0, W, H_).data, f1: lowField(W, H_, 31, 160, 90, 0.09), f2: lowField(W, H_, 57, 426, 240, 0.35) };
258
+ }
259
+ const { paper, f1, f2 } = LX_CACHE;
260
+ let cur = 0; for (let i = 0; i < PULL.length; i++) if (t >= PULL[i]) cur = i;
261
+ const peeling = t < PULL[cur] + PEEL, prev = cur - 1;
262
+ const front = peeling ? LE.inOut(lseg(t, PULL[cur], PULL[cur] + PEEL)) * 1900 - 150 : 1e9;
263
+ const masks = (si) => {
264
+ if (si < 0) return null;
265
+ const kc = H.layer(W, H_), k = kc.getContext('2d'), rc = H.layer(W, H_), rd = rc.getContext('2d');
266
+ scene(si, k, rd, t);
267
+ rd.globalCompositeOperation = 'destination-out'; rd.fillRect(0, 0, W, KY0 + 16); rd.fillRect(0, KY1 - 16, W, H_); rd.fillRect(0, 0, KX0 + 16, H_); rd.fillRect(KX1 - 16, 0, W, H_);
268
+ const blur = (c, dx, dy, b) => { const o = H.layer(W, H_), ox = o.getContext('2d'); ox.filter = `blur(${b}px)`; ox.drawImage(c, dx, dy); return ox.getImageData(0, 0, W, H_).data; };
269
+ return { km: blur(kc, 0, 0, 1.1), rm: blur(rc, 3, -2, 1.3) };
270
+ };
271
+ const A = masks(cur), B = peeling ? masks(prev) : null;
272
+ // pencil: running title in the margin, proof numbers, and correction circles for emphasis
273
+ const pc = H.layer(W, H_), p = pc.getContext('2d'); p.fillStyle = '#000'; p.strokeStyle = '#000';
274
+ const write = (t0, dur, x0, w, fn) => { const u = lseg(t, t0, t0 + dur); if (u <= 0) return; p.save(); p.beginPath(); p.rect(x0 - 6, 0, (w + 12) * u, H_); p.clip(); fn(); p.restore(); };
275
+ write(1.2, 0.8, KX0 + 6, 330, () => { p.font = '400 34px "Caveat"'; p.textAlign = 'left'; p.fillText('How does AI learn?', KX0 + 6, 632); });
276
+ p.font = '400 24px "Kalam"'; p.textAlign = 'right';
277
+ write(PULL[cur] + 0.5, 0.25, KX1 - 120, 120, () => { p.fillText(`proof ${cur + 1}/5`, KX1 - 6, 630); });
278
+ const circ = (cx, cy, rx, ry, u) => { if (u <= 0) return; p.lineWidth = 5; p.beginPath(); for (let i = 0; i <= 50 * u; i++) { const a = -2.4 + (i / 50) * 7.2; const px = cx + Math.cos(a) * rx * (1 + 0.05 * Math.sin(i)), py = cy + Math.sin(a) * ry; i ? p.lineTo(px, py) : p.moveTo(px, py); } p.stroke(); };
279
+ if (cur === 2 && t < 9.05) { circ(1020, 205, 175, 95, lseg(t, 7.55, 7.95)); if (t > 7.9) { p.font = '400 30px "Caveat"'; p.textAlign = 'left'; p.fillText("wrong — it's a cat", 640, 632); } }
280
+ if (cur === 3) { circ(950, 290, 230, 140, lseg(t, 12.35, 12.75)); }
281
+ if (cur === 1) write(5.2, 0.45, 640, 230, () => { p.font = '400 30px "Caveat"'; p.textAlign = 'left'; p.fillText('labelled examples', 640, 632); });
282
+ if (cur === 4) write(14.0, 0.7, 760, 300, () => { p.font = '400 36px "Caveat"'; p.textAlign = 'left'; p.fillText('M. Okonkwo', 860, 634); });
283
+ const pm = p.getImageData(0, 0, W, H_).data;
284
+
285
+ const img = ctx.createImageData(W, H_), d = img.data;
286
+ const step = Math.floor(t * 12);
287
+ for (let y = 0; y < H_; y++) for (let x = 0; x < W; x++) {
288
+ const i = (y * W + x) * 4;
289
+ let cr = paper[i], cg = paper[i + 1], cb = paper[i + 2];
290
+ const tooth = (f2[i] - 128) / 128, broad = (f1[i] - 128) / 128, streak = (f1[i + 1] - 128) / 128, fine = (f2[i + 2] - 128) / 128;
291
+ const hh = lhash(x, y, 1);
292
+ const pg = 1 + (hh - 0.5) * 0.05 + tooth * 0.015 + streak * 0.018 + broad * 0.012;
293
+ cr *= pg; cg *= pg; cb *= pg;
294
+ const u = x * 0.85 + y * 0.55;
295
+ const M = u < front ? A : B;
296
+ let inkC = 0;
297
+ if (peeling && x > KX0 - 12 && x < KX1 + 12 && y > KY0 - 12 && y < KY1 + 12) { const q = (u - front - 14) / 22; const sh = 1 - 0.14 * Math.exp(-q * q); cr *= sh; cg *= sh; cb *= sh; }
298
+ if (M) {
299
+ const ra = M.rm[i + 3] / 255;
300
+ if (ra > 0.02) {
301
+ let cov = lcl((ra - (0.5 + fine * 0.22)) * 4 + 0.5);
302
+ let dens = 0.8 + broad * 0.14 + (lhash(x, y, 2 + step) - 0.5) * 0.18;
303
+ if (tooth + (lhash(x >> 1, y >> 1, 3) - 0.5) * 0.9 > 0.62) dens *= 0.25;
304
+ cov *= lcl(dens);
305
+ cr = cr * (1 - cov) + (cr * RED[0] / 255) * cov; cg = cg * (1 - cov) + (cg * RED[1] / 255) * cov; cb = cb * (1 - cov) + (cb * RED[2] / 255) * cov;
306
+ inkC = cov * 0.55;
307
+ }
308
+ const ka = M.km[i + 3] / 255;
309
+ if (ka > 0.02) {
310
+ let cov = lcl((ka - (0.5 + fine * 0.25 + (hh - 0.5) * 0.12)) * 4.5 + 0.5);
311
+ let dens = 0.95 + broad * 0.06 + streak * 0.04 + (lhash(x, y, 4 + step) - 0.5) * 0.08;
312
+ const salt = tooth * 0.75 + (lhash(x >> 1, y, 5) - 0.5) * 0.7 + Math.max(0, broad) * 0.35;
313
+ if (salt > 0.7) dens *= 0.15; else if (salt > 0.55) dens *= 0.7;
314
+ cov *= lcl(dens);
315
+ cr = cr * (1 - cov) + INKK[0] * cov; cg = cg * (1 - cov) + INKK[1] * cov; cb = cb * (1 - cov) + INKK[2] * cov;
316
+ if (cov > inkC) inkC = cov;
317
+ }
318
+ }
319
+ const pa = pm[i + 3] / 255;
320
+ if (pa > 0.02) { // graphite: warm grey on paper; over ink only a faint silvery sheen
321
+ const g = pa * (0.45 + 0.45 * hh) * (tooth > 0.2 ? 0.6 : 1) * (1 - 0.68 * inkC);
322
+ const gr = 78 + 52 * inkC, gg = 72 + 54 * inkC, gb = 66 + 56 * inkC;
323
+ cr = cr * (1 - g) + gr * g; cg = cg * (1 - g) + gg * g; cb = cb * (1 - g) + gb * g; }
324
+ d[i] = cr; d[i + 1] = cg; d[i + 2] = cb; d[i + 3] = 255;
325
+ }
326
+ ctx.putImageData(img, 0, 0);
327
+ H.vignette(ctx, W, H_, 0.12, '90,70,40');
328
+ };
329
+
330
+ registerStyle({
331
+ id: 'linocut',
332
+ name: 'Linocut Relief Print',
333
+ category: 'Print',
334
+ description: 'A hand-carved two-colour relief print: tapering white gouge strokes cut from dense black, parallel carving for tone, a red reduction layer, salty uneven ink on cream rice paper, signed and numbered in pencil.',
335
+ motion: 'The proof is pulled twice — the red sheet peels back diagonally, then the black key — then the edition number, title and signature are pencilled in while a raking glint slides across the wet ink and the camera eases in.',
336
+ fonts: ['400 44px "Archivo Black"', '400 24px "Kalam"', '400 36px "Caveat"'],
337
+ duration: 5.5,
338
+ frameT: 5.3,
339
+ readsPixels: true,
340
+ explainerDuration: 15,
341
+ explainerNote: 'Each beat is a newly carved block pulled as a fresh proof that peels back over the last one; arrows are gouged channels (the error path printed red), counters are carved numerals re-cut on twos, and emphasis is the printmaker\'s own pencil circle and margin note.',
342
+ explainer(ctx, W, H_, t) { linoExplainer(ctx, W, H_, t); },
343
+ // Sound: a printmaker's bench. Every scene change is a proof peeled off a freshly inked block (tacky crackle
344
+ // under the sheet lifting), gouged arrows and channels are the scrape of a V-gouge through lino, carved
345
+ // numerals tick like small chisel nicks, the pencil scratches out notes and the signature, and the hits are
346
+ // the wooden thump of the press. Timings mirror PULL / PEEL / scene() / linoExplainer above. scene() runs on
347
+ // twos (st = floor(t*12)/12), so on-screen events are snapped to the next 1/12 s step with q()/qq().
348
+ // Voiceover mode: room bed drops; tile nicks, counter nicks, pencil and tack crackle are minor.
349
+ explainerSound(k) {
350
+ const D = 15;
351
+ const q = (t) => Math.ceil(t * 12 - 1e-6) / 12; // first step with st >= t
352
+ const qq = (t) => Math.floor(t * 12 + 1e-6) / 12 + 1 / 12; // first step with st > t
353
+ const pan = (px) => H.clamp((px / 1280) * 2 - 1, -1, 1) * 0.6;
354
+ 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; };
355
+ const rnd = H.rng(808);
356
+ // V-gouge scraping through lino: dense crunchy grains + a low body, a tiny chip flick at the end
357
+ const gouge = (t, d, g, p, minor) => {
358
+ const run = () => {
359
+ for (let x = 0; x < d; x += 0.016 + rnd() * 0.012) k.noise(t + x, { dur: 0.03, filter: 'bandpass', f: 1000 + rnd() * 700, q: 1.3, gain: 0.2 * g * (0.6 + rnd() * 0.5), a: 0.004, pan: p });
360
+ k.noise(t, { dur: d, color: 'brown', filter: 'lowpass', f: 420, gain: 0.22 * g, a: Math.min(0.05, d * 0.2), hold: d * 0.6, pan: p });
361
+ k.noise(t + d, { dur: 0.025, filter: 'bandpass', f: 1900, q: 1.5, gain: 0.18 * g, a: 0.001, pan: p }); // chip flicks off
362
+ };
363
+ if (minor) k.withMinor(run); else run();
364
+ };
365
+ // a carved letter / numeral nick: short dry knock, low
366
+ const nick = (t, g, p) => k.withMinor(() => { k.noise(t, { dur: 0.03, filter: 'bandpass', f: 1300 + rnd() * 300, q: 1.6, gain: 0.3 * g, a: 0.001, pan: p }); k.tone(t, { f: 190, dur: 0.04, gain: 0.08 * g, pan: p }); });
367
+ // pulling a proof: the press lets go, the sheet peels back off tacky ink over PEEL seconds
368
+ const peel = (t0, first) => {
369
+ if (!first) k.play('press', t0, { gain: 0.3 });
370
+ k.play('paper', t0 + 0.14, { dur: PEEL - 0.16, gain: 0.7 }); // sheet lifts as the peel gathers speed
371
+ k.noise(t0, { dur: PEEL, color: 'pink', filter: 'bandpass', f: [[0, 400], [PEEL * 0.6, 900], [PEEL, 500]], q: 0.8, gain: 0.12, a: PEEL * 0.4 });
372
+ k.withMinor(() => { for (let x = 0.03; x < PEEL; x += 0.008 + rnd() * 0.02) { const env = Math.sin(Math.PI * x / PEEL); k.noise(t0 + x, { dur: 0.006, filter: 'bandpass', f: 1500 + rnd() * 900, q: 1, gain: 0.22 * env * (0.4 + rnd() * 0.6), a: 0.001, pan: (rnd() - 0.5) * 0.6 }); } });
373
+ };
374
+ const pencil = (t, d, g, p) => k.play('stroke', t, { material: 'pencil', dur: d, gain: g, pan: p, minor: true });
375
+
376
+ k.bed('room', 0, D, { gain: 0.22 });
377
+
378
+ // ---- proof 1: HOOK
379
+ k.play('press', 0.0, { gain: 0.4 });
380
+ peel(PULL[0], true);
381
+ pencil(PULL[0] + 0.5, 0.25, 0.6, 0.6); // "proof 1/5"
382
+ pencil(1.2, 0.8, 0.7, -0.5); // running title pencilled in the margin
383
+
384
+ // ---- proof 2: DATA
385
+ peel(PULL[1]);
386
+ pencil(PULL[1] + 0.5, 0.25, 0.6, 0.6);
387
+ gouge(qq(2.9), 0.5, 1, pan(470)); // channel gouged toward the model
388
+ for (let j = 0; j < 9; j++) nick(qq(3.0 + j * 0.34), 0.8, pan(150)); // each tile cut in
389
+ // counter n = 1e6 * lseg(st, 3.1, 5.4)^2.3, re-cut on twos: nicks speeding up, <= ~11/s
390
+ for (let t = q(3.17); t < 5.36; ) { const s = lseg(t, 3.1, 5.4); nick(t, 0.6 + 0.3 * s, pan(640)); t = q(t + 0.19 - 0.1 * s); }
391
+ k.play('press', q(5.4), { gain: 0.75, pan: pan(640) }); // 1,000,000 EXAMPLES
392
+ k.noise(q(5.4), { dur: 0.05, filter: 'bandpass', f: 1200, q: 1, gain: 0.3, a: 0.001, pan: pan(640) });
393
+ pencil(5.2, 0.45, 0.6, 0.1); // "labelled examples"
394
+
395
+ // ---- proof 3: GUESS / CHECK / ADJUST
396
+ peel(PULL[2]);
397
+ pencil(PULL[2] + 0.5, 0.25, 0.6, 0.6);
398
+ gouge(qq(6.45), 0.35, 0.9, pan(375));
399
+ nick(qq(6.6), 0.9, pan(375)); // GUESS
400
+ gouge(qq(6.85), 0.25, 0.7, pan(810));
401
+ k.play('tock', qq(7.0), { f: 360, gain: 0.5, pan: pan(1020) }); // answer card "dog?"
402
+ // wrong: the red X prints, then a low muted two-note
403
+ k.play('stamp', qq(7.45), { gain: 0.8, pan: pan(1130) });
404
+ k.play('error', qq(7.45) + 0.12, { f: 'E3', gain: 0.75, pan: pan(1100) });
405
+ pencil(7.55, 0.4, 0.75, pan(1020)); // pencil circle round the wrong answer
406
+ pencil(7.95, 0.3, 0.5, 0); // margin note
407
+ gouge(qq(7.9), 0.5, 1.2, pan(870)); // wide return channel, printed red
408
+ nick(qq(8.1), 0.8, pan(930)); // ADJUST
409
+ for (let i = 1; i <= 5; i++) k.play('tock', q(when(8.3, 8.9, LE.inOut, i / 5)), { f: 520 + i * 25, gain: 0.35, pan: pan(600), minor: true }); // dials turn
410
+ // correction: the tick prints and the model's rays are cut out
411
+ k.play('tock', q(9.05), { f: 300, gain: 0.5, pan: pan(1130) });
412
+ k.play('success', when(9.05, 9.3, LE.back, 1), { f: 'C5', gain: 0.45, pan: pan(1000) });
413
+
414
+ // ---- proof 4: ACCURACY
415
+ peel(PULL[3]);
416
+ pencil(PULL[3] + 0.5, 0.25, 0.6, 0.6);
417
+ gouge(q(10.9), 1.4, 0.45, pan(430), true); // the curve is carved along as it climbs
418
+ for (let t = q(10.92), last = 61, lastT = -1; t < 12.32; t += 1 / 12) { const v = Math.round(61 + 36 * LE.inOut(lseg(t, 10.9, 12.3))); if (v !== last && t - lastT >= 0.1) { nick(t, 0.9, pan(950)); last = v; lastT = t; } }
419
+ // 97%: the rays cut and the red bar prints. Biggest hit: the press comes down hard
420
+ k.play('impact', qq(12.3), { gain: 1.2, pan: pan(950) });
421
+ k.play('press', qq(12.3), { gain: 0.9, pan: pan(950) });
422
+ pencil(12.35, 0.4, 0.8, pan(950)); // pencil circle
423
+
424
+ // ---- proof 5: TAKEAWAY
425
+ peel(PULL[4]);
426
+ pencil(PULL[4] + 0.5, 0.25, 0.6, 0.6);
427
+ gouge(qq(13.5), 0.3, 0.8, 0); // arrow gouged down
428
+ ['C4', 'G4', 'C5', 'E5'].forEach((f, i) => k.play('ding', 13.8 + i * 0.06, { f, gain: 0.36 - i * 0.05, pan: (i - 1.5) * 0.2 }));
429
+ pencil(14.0, 0.7, 1.2, 0.35); // signed M. Okonkwo
430
+ },
431
+ draw(ctx, W, H_, t) {
432
+ const rng = H.rng(12);
433
+ const spin = (t - 5.3) * 0.01;
434
+
435
+ // ================= BLACK KEY BLOCK =================
436
+ const kc = H.layer(W, H_), k = kc.getContext('2d');
437
+ const nzE = H.makeNoise(4);
438
+ // block with slightly wavy, rounded edges
439
+ k.fillStyle = '#000'; k.beginPath();
440
+ const edge = (u, v) => nzE.n2(u * 0.03, v * 0.03) * 2.2;
441
+ for (let x = BX0; x <= BX1; x += 10) k.lineTo(x, BY0 + edge(x, 0));
442
+ for (let y = BY0; y <= BY1; y += 10) k.lineTo(BX1 + edge(0, y), y);
443
+ for (let x = BX1; x >= BX0; x -= 10) k.lineTo(x, BY1 + edge(x, 9));
444
+ for (let y = BY1; y >= BY0; y -= 10) k.lineTo(BX0 + edge(9, y), y);
445
+ k.closePath(); k.fill();
446
+
447
+ const carve = () => { k.globalCompositeOperation = 'destination-out'; };
448
+ const ink = () => { k.globalCompositeOperation = 'source-over'; };
449
+ k.fillStyle = '#000';
450
+
451
+ // inner border line carved
452
+ carve(); k.strokeStyle = '#000'; k.lineWidth = 4;
453
+ k.beginPath(); k.rect(BX0 + 15, BY0 + 15, BX1 - BX0 - 30, BY1 - BY0 - 30); k.stroke();
454
+
455
+ // sky rays: alternating long lens gouges and short flicks
456
+ k.save(); k.beginPath(); k.rect(BX0 + 19, BY0 + 19, BX1 - BX0 - 38, BY1 - BY0 - 38); k.clip();
457
+ const NR = 46;
458
+ for (let i = 0; i < NR; i++) {
459
+ const a = (i / NR) * Math.PI * 2 + spin + rng.range(-0.02, 0.02);
460
+ const long = i % 2 === 0;
461
+ const r0 = long ? rng.range(168, 180) : rng.range(205, 230);
462
+ const r1 = long ? rng.range(560, 640) : rng.range(300, 380);
463
+ const w = long ? rng.range(15, 21) : rng.range(8, 12);
464
+ const bend = rng.range(-0.02, 0.02);
465
+ tapered(k, line(CX + Math.cos(a) * r0, CY + Math.sin(a) * r0, CX + Math.cos(a) * r1, CY + Math.sin(a) * r1, bend), w, long ? (s) => Math.pow(Math.sin(Math.PI * Math.min(1, s * 1.15)), 0.5) * (1 - s * 0.35) : lens);
466
+ if (long && rng.chance(0.4)) { const rr = rng.range(300, 460), aa = a + rng.range(-0.04, 0.04); tapered(k, line(CX + Math.cos(aa) * rr, CY + Math.sin(aa) * rr, CX + Math.cos(aa) * (rr + 26), CY + Math.sin(aa) * (rr + 26), 0.05), 3, lens, 5); }
467
+ }
468
+ // halo: ring of short gouge dashes hugging the bulb
469
+ for (let i = 0; i < 60; i++) {
470
+ const a = (i / 60) * Math.PI * 2 + spin * 2, r0 = 142 + (i % 2) * 6;
471
+ tapered(k, line(CX + Math.cos(a) * r0, CY + Math.sin(a) * r0, CX + Math.cos(a + 0.05) * (r0 + 14), CY + Math.sin(a + 0.05) * (r0 + 14), 0.1), 6, lens, 6);
472
+ }
473
+ // scattered carving chips in the far sky
474
+ for (let i = 0; i < 140; i++) {
475
+ const x0 = rng.range(BX0 + 24, BX1 - 24), y0 = rng.range(BY0 + 24, 420);
476
+ if (Math.hypot(x0 - CX, y0 - CY) < 300) continue;
477
+ const a = rng.range(-0.5, 0.5) + (rng.chance(0.5) ? 0 : Math.PI / 2), l = rng.range(5, 13);
478
+ tapered(k, line(x0, y0, x0 + Math.cos(a) * l, y0 + Math.sin(a) * l, 0.15), rng.range(2, 4), lens, 4);
479
+ }
480
+ // carved stars
481
+ for (const [sx, sy, ss] of [[330, 92, 14], [960, 110, 12], [905, 330, 9], [372, 300, 10], [470, 70, 7], [1000, 230, 8]]) {
482
+ for (let j = 0; j < 4; j++) { const a = j * Math.PI / 4; tapered(k, line(sx - Math.cos(a) * ss, sy - Math.sin(a) * ss, sx + Math.cos(a) * ss, sy + Math.sin(a) * ss, 0), j % 2 ? 3 : 5, lens, 6); }
483
+ }
484
+ k.restore();
485
+
486
+ // bulb glass carved out
487
+ bulbPath(k); k.fill();
488
+ ink();
489
+ // contour hatching (left standing) on the shadow side of the glass
490
+ k.save(); bulbPath(k); k.clip();
491
+ for (let j = 0; j < 7; j++) {
492
+ const rr = R - 6 - j * 11, a0 = -0.95 + j * 0.06, a1 = 1.3 - j * 0.05;
493
+ tapered(k, (s) => { const a = a0 + (a1 - a0) * s; return [CX + 6 + Math.cos(a) * rr, CY + Math.sin(a) * rr]; }, 9 - j * 1.1, lens, 20);
494
+ }
495
+ // a few on the lower-left
496
+ for (let j = 0; j < 3; j++) {
497
+ const rr = R - 8 - j * 12;
498
+ tapered(k, (s) => { const a = 2.0 + s * 0.9; return [CX + Math.cos(a) * rr, CY + Math.sin(a) * rr]; }, 5 - j, lens, 12);
499
+ }
500
+ k.restore();
501
+ // glass outline left standing as a strong black rim (slightly uneven)
502
+ k.lineWidth = 7; k.strokeStyle = '#000'; bulbPath(k); k.stroke();
503
+ // filament + supports
504
+ k.lineWidth = 5; k.lineJoin = 'round'; k.lineCap = 'round';
505
+ k.beginPath(); k.moveTo(CX - 22, CY + 168); k.lineTo(CX - 26, CY + 60); k.lineTo(CX - 34, CY + 10);
506
+ k.moveTo(CX + 22, CY + 168); k.lineTo(CX + 26, CY + 60); k.lineTo(CX + 34, CY + 10); k.stroke();
507
+ k.lineWidth = 4.5; k.beginPath(); k.moveTo(CX - 34, CY + 10);
508
+ for (let i = 1; i <= 8; i++) k.lineTo(CX - 34 + i * 8.5, CY + 10 + (i % 2 ? -16 : 0));
509
+ k.stroke();
510
+
511
+ // screw base: black, with carved thread gouges + highlight
512
+ k.beginPath(); k.moveTo(CX - 54, CY + 166); k.lineTo(CX + 54, CY + 166); k.lineTo(CX + 50, CY + 222); k.lineTo(CX - 50, CY + 222); k.closePath(); k.fill();
513
+ carve();
514
+ for (let j = 0; j < 4; j++) {
515
+ const y = CY + 178 + j * 12;
516
+ tapered(k, quad(CX - 50, y + 2, CX, y - 6, CX + 48, y + 4), 6, tail, 12);
517
+ }
518
+ tapered(k, line(CX - 38, CY + 172, CX - 36, CY + 218, 0), 7, lens, 8);
519
+ ink();
520
+
521
+ // ground: two hills with parallel contour carving
522
+ const groundPath = (f, y1) => { k.beginPath(); k.moveTo(BX0 + 19, y1); for (let x = BX0 + 19; x <= BX1 - 19; x += 8) k.lineTo(x, Math.min(f(x), y1)); k.lineTo(BX1 - 19, y1); k.closePath(); };
523
+ groundPath(hillB, 520); k.fill();
524
+ groundPath(hillF, 520); k.fill();
525
+ carve();
526
+ // back hill lines (denser carving = lighter)
527
+ k.save(); groundPath(hillB, 520); k.clip();
528
+ for (let j = 0; j < 9; j++) {
529
+ const off = 12 + j * 9;
530
+ tapered(k, (s) => { const x = 700 + s * 340; return [x, hillB(x) + off + Math.sin(x * 0.03 + j) * 1.5]; }, 5.5 - j * 0.25, tail, 24);
531
+ }
532
+ k.restore();
533
+ // front hill: sparser lines, starting from the light (left)
534
+ k.save(); groundPath(hillF, 520); k.clip();
535
+ for (let j = 0; j < 6; j++) {
536
+ const off = 14 + j * 13, xs = BX0 + 10 + j * 22, xe = 860 - j * 40;
537
+ tapered(k, (s) => { const x = xs + s * (xe - xs); return [x, hillF(x) + off + Math.sin(x * 0.025 + j * 2) * 2]; }, 7 - j * 0.6, tail, 26);
538
+ }
539
+ // grass flicks along the crest
540
+ for (let i = 0; i < 40; i++) {
541
+ const x0 = BX0 + 30 + i * 18 + rng.range(-5, 5), y0 = hillF(x0) + 5;
542
+ if (Math.abs(x0 - CX) < 70) continue;
543
+ tapered(k, line(x0, y0 + 8, x0 + rng.range(-4, 6), y0 - 1, 0.1), 3.5, tail, 5);
544
+ }
545
+ k.restore();
546
+ // re-cut the bulb base contact into the hill + sprouting leaves
547
+ ink();
548
+ const leaves = [[CX - 26, 452, CX - 190, 368, -0.3], [CX + 26, 452, CX + 186, 380, 0.28]];
549
+ carve(); for (const [a, b, c, d, e] of leaves) { leaf(k, a, b, c, d, e); k.fill(); }
550
+ ink();
551
+ for (const [a, b, c, d, e] of leaves) {
552
+ k.save(); leaf(k, a, b, c, d, e); k.clip();
553
+ const mid = quad(a, b, (a + c) / 2 - (d - b) * e * 0.25, (b + d) / 2 + (c - a) * e * 0.25, c, d);
554
+ tapered(k, mid, 6, tail, 16);
555
+ for (let j = 1; j <= 5; j++) {
556
+ const s0 = j / 6.5, [px, py] = mid(s0), [qx, qy] = mid(s0 + 0.14);
557
+ const nx = -(d - b), ny = c - a, nl = Math.hypot(nx, ny);
558
+ for (const sg of [1, -1]) tapered(k, line(px, py, qx + (nx / nl) * 34 * sg, qy + (ny / nl) * 34 * sg, 0.08 * sg), 3.2, tail, 6);
559
+ }
560
+ k.restore();
561
+ k.lineWidth = 4; leaf(k, a, b, c, d, e); k.stroke();
562
+ }
563
+ k.lineWidth = 6; k.beginPath(); k.moveTo(CX, CY + 222); k.lineTo(CX, 460); k.stroke();
564
+
565
+ // title band, letters carved out
566
+ k.fillRect(BX0 + 19, 512, BX1 - BX0 - 38, BY1 - 19 - 512);
567
+ carve();
568
+ k.font = '400 40px "Archivo Black"'; k.letterSpacing = '9px'; k.textAlign = 'center'; k.textBaseline = 'middle';
569
+ const word = 'LET IDEAS GROW';
570
+ // hand-cut letters: each glyph slightly rotated / shifted
571
+ let tw = k.measureText(word).width, cx0 = CX - tw / 2;
572
+ for (const ch of word) {
573
+ const cw = k.measureText(ch).width;
574
+ k.save(); k.translate(cx0 + cw / 2, 543 + rng.range(-1.5, 1.5)); k.rotate(rng.range(-0.025, 0.025)); k.fillText(ch, 0, 0); k.restore();
575
+ cx0 += cw;
576
+ }
577
+ k.letterSpacing = '0px';
578
+ tapered(k, line(BX0 + 40, 543, BX0 + 120, 543, 0), 6, lens); tapered(k, line(BX1 - 120, 543, BX1 - 40, 543, 0), 6, lens);
579
+ ink();
580
+
581
+ // ================= RED REDUCTION BLOCK =================
582
+ const rc = H.layer(W, H_), r = rc.getContext('2d');
583
+ r.fillStyle = '#000'; r.beginPath();
584
+ for (let i = 0; i <= 90; i++) { const a = (i / 90) * Math.PI * 2, rr = 268 + nzE.n2(Math.cos(a) * 2, Math.sin(a) * 2) * 14; r.lineTo(CX + Math.cos(a) * rr, CY + Math.sin(a) * rr); }
585
+ r.closePath(); r.fill();
586
+ r.fillRect(BX0 + 19, 512, BX1 - BX0 - 38, BY1 - 19 - 512);
587
+ // white-hot centre carved from the red
588
+ r.globalCompositeOperation = 'destination-out';
589
+ r.beginPath(); r.ellipse(CX - 6, CY - 4, 66, 74, 0, 0, Math.PI * 2); r.fill();
590
+ for (let i = 0; i < 18; i++) { const a = (i / 18) * Math.PI * 2 - 0.1; tapered(r, line(CX + Math.cos(a) * 60, CY + Math.sin(a) * 66, CX + Math.cos(a) * 98, CY + Math.sin(a) * 104, 0), 9, tail, 6); }
591
+ r.globalCompositeOperation = 'source-over';
592
+ // ground hills get no red
593
+ r.globalCompositeOperation = 'destination-out';
594
+ r.beginPath(); r.moveTo(BX0, 512); for (let x = BX0; x <= BX1; x += 8) r.lineTo(x, Math.min(hillF(x), hillB(x), 512) + 3); r.lineTo(BX1, 512); r.closePath(); r.fill();
595
+ r.fillRect(0, 0, W, BY0 + 15); r.fillRect(0, 0, BX0 + 15, H_); r.fillRect(BX1 - 15, 0, W, H_);
596
+ r.globalCompositeOperation = 'source-over';
597
+
598
+ // ================= PENCIL =================
599
+ const pc = H.layer(W, H_), p = pc.getContext('2d');
600
+ p.fillStyle = '#000'; p.textBaseline = 'alphabetic';
601
+ // written left-to-right, one inscription after another
602
+ const write = (t0, dur, x0, x1, fn) => { const u = H.clamp((t - t0) / dur); if (u <= 0) return; p.save(); p.beginPath(); p.rect(x0 - 4, 590, (x1 - x0 + 8) * u, 70); p.clip(); fn(); p.restore(); };
603
+ p.font = '400 24px "Kalam"';
604
+ write(3.0, 0.35, BX0 + 8, BX0 + 60, () => { p.font = '400 24px "Kalam"'; p.textAlign = 'left'; p.fillText('3/20', BX0 + 8, 630); });
605
+ write(3.45, 0.55, CX - 62, CX + 62, () => { p.font = '400 24px "Kalam"'; p.textAlign = 'center'; p.fillText('Bright Idea', CX, 630); });
606
+ write(4.05, 0.75, BX1 - 230, BX1 - 4, () => {
607
+ p.font = '400 36px "Caveat"'; p.textAlign = 'right'; p.fillText('M. Okonkwo 2026', BX1 - 6, 632);
608
+ p.lineWidth = 1.4; p.beginPath(); p.moveTo(BX1 - 196, 640); p.quadraticCurveTo(BX1 - 120, 646, BX1 - 20, 636); p.stroke();
609
+ });
610
+
611
+ // ================= PRINT (per-pixel) =================
612
+ // soften masks a hair so ink edges can be roughened by threshold noise
613
+ const blurMask = (c) => { const b = H.layer(W, H_), bx = b.getContext('2d'); bx.filter = 'blur(1.1px)'; bx.drawImage(c, 0, 0); return bx.getImageData(0, 0, W, H_).data; };
614
+ const km = blurMask(kc);
615
+ const rcs = H.layer(W, H_), rcx = rcs.getContext('2d'); rcx.filter = 'blur(1.3px)'; rcx.drawImage(rc, 3, -2); // red slightly out of register
616
+ const rm = rcx.getImageData(0, 0, W, H_).data;
617
+ const pm = p.getImageData(0, 0, W, H_).data;
618
+ const f1 = lowField(W, H_, 31, 160, 90, 0.09); // broad ink density
619
+ const f2 = lowField(W, H_, 57, 426, 240, 0.35); // salt / paper tooth
620
+
621
+ // paper base + rice-paper fibres drawn first
622
+ ctx.fillStyle = `rgb(${PAPER})`; ctx.fillRect(0, 0, W, H_);
623
+ ctx.lineCap = 'round';
624
+ for (let i = 0; i < 520; i++) {
625
+ const x0 = rng.range(-40, W + 40), y0 = rng.range(-40, H_ + 40), a = rng.range(0, Math.PI * 2), l = rng.range(30, 170);
626
+ ctx.strokeStyle = rng.chance(0.65) ? `rgba(255,252,240,${rng.range(0.25, 0.6)})` : `rgba(150,128,90,${rng.range(0.08, 0.2)})`;
627
+ ctx.lineWidth = rng.range(0.5, 1.6);
628
+ ctx.beginPath(); ctx.moveTo(x0, y0);
629
+ ctx.bezierCurveTo(x0 + Math.cos(a) * l * 0.3 + rng.range(-15, 15), y0 + Math.sin(a) * l * 0.3 + rng.range(-15, 15), x0 + Math.cos(a) * l * 0.7 + rng.range(-15, 15), y0 + Math.sin(a) * l * 0.7 + rng.range(-15, 15), x0 + Math.cos(a) * l, y0 + Math.sin(a) * l);
630
+ ctx.stroke();
631
+ }
632
+ // faint plate-mark impression around the block
633
+ if (t > 1.1) {
634
+ ctx.strokeStyle = 'rgba(120,100,70,0.10)'; ctx.lineWidth = 2; ctx.strokeRect(BX0 - 3, BY0 - 3, BX1 - BX0 + 6, BY1 - BY0 + 6);
635
+ ctx.strokeStyle = 'rgba(255,255,255,0.35)'; ctx.lineWidth = 1.5; ctx.strokeRect(BX0 - 5, BY0 - 5, BX1 - BX0 + 10, BY1 - BY0 + 10);
636
+ }
637
+
638
+ const img = ctx.getImageData(0, 0, W, H_), d = img.data;
639
+ const hash = (x, y, s) => { let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(s, 1442695041)) | 0; h = Math.imul(h ^ (h >>> 13), 1274126177); h ^= h >>> 16; return (h >>> 0) / 4294967296; };
640
+ // proof pulls: each sheet peels back along a diagonal front
641
+ const pullR = H.ease.inOut(H.clamp((t - 0.2) / 1.0)) * 1700 - 120;
642
+ const pullK = H.ease.inOut(H.clamp((t - 1.35) / 1.3)) * 1700 - 120;
643
+ const gpos = H.lerp(-300, 1900, H.clamp((t - 2.9) / 2.4));
644
+ for (let y = 0; y < H_; y++) for (let x = 0; x < W; x++) {
645
+ const i = (y * W + x) * 4;
646
+ let cr = d[i], cg = d[i + 1], cb = d[i + 2];
647
+ const tooth = (f2[i] - 128) / 128, broad = (f1[i] - 128) / 128, streak = (f1[i + 1] - 128) / 128, fine = (f2[i + 2] - 128) / 128;
648
+ const hh = hash(x, y, 1);
649
+ // paper grain
650
+ const pg = 1 + (hh - 0.5) * 0.05 + tooth * 0.015 + streak * 0.018 + broad * 0.012;
651
+ cr *= pg; cg *= pg; cb *= pg;
652
+ // red
653
+ const u = x * 0.85 + y * 0.55;
654
+ // shadow of the lifting sheet just ahead of the peel front
655
+ const front = pullK > -100 && pullK < 1560 ? pullK : (pullR > -100 && pullR < 1560 ? pullR : -1e9);
656
+ if (u > front - 10 && u < front + 60 && x > BX0 - 12 && x < BX1 + 12 && y > BY0 - 12 && y < BY1 + 12) { const q = (u - front - 14) / 22; const sh = 1 - 0.13 * Math.exp(-q * q); cr *= sh; cg *= sh; cb *= sh; }
657
+ const ra = u < pullR ? rm[i + 3] / 255 : 0;
658
+ if (ra > 0.02) {
659
+ const thr = 0.5 + fine * 0.22;
660
+ let cov = H.clamp((ra - thr) * 4 + 0.5);
661
+ let dens = 0.8 + broad * 0.14 + (hash(x, y, 2) - 0.5) * 0.18;
662
+ if (tooth + (hash(x >> 1, y >> 1, 3) - 0.5) * 0.9 > 0.62) dens *= 0.25;
663
+ cov *= H.clamp(dens);
664
+ cr = cr * (1 - cov) + (cr * RED[0] / 255) * cov; cg = cg * (1 - cov) + (cg * RED[1] / 255) * cov; cb = cb * (1 - cov) + (cb * RED[2] / 255) * cov;
665
+ }
666
+ // black key
667
+ const ka = u < pullK ? km[i + 3] / 255 : 0;
668
+ if (ka > 0.02) {
669
+ const thr = 0.5 + fine * 0.25 + (hh - 0.5) * 0.12;
670
+ let cov = H.clamp((ka - thr) * 4.5 + 0.5);
671
+ let dens = 0.95 + broad * 0.06 + streak * 0.04 + (hash(x, y, 4) - 0.5) * 0.08;
672
+ // salt: paper tooth showing through dense black
673
+ const salt = tooth * 0.75 + (hash(x >> 1, y, 5) - 0.5) * 0.7 + Math.max(0, broad) * 0.35;
674
+ if (salt > 0.7) dens *= 0.15; else if (salt > 0.55) dens *= 0.7;
675
+ cov *= H.clamp(dens);
676
+ const gd = (x * 0.8 + y * 0.6 - gpos) / 70, gl = Math.exp(-gd * gd) * 26 * (0.6 + 0.4 * tooth);
677
+ cr = cr * (1 - cov) + (INKK[0] + gl) * cov; cg = cg * (1 - cov) + (INKK[1] + gl) * cov; cb = cb * (1 - cov) + (INKK[2] + gl * 1.05) * cov;
678
+ }
679
+ // pencil graphite (grainy, sits on the tooth)
680
+ const pa = pm[i + 3] / 255;
681
+ if (pa > 0.02) {
682
+ const g = pa * (0.25 + 0.5 * hh) * (tooth > 0.2 ? 0.55 : 1);
683
+ cr = cr * (1 - g) + 78 * g; cg = cg * (1 - g) + 78 * g; cb = cb * (1 - g) + 84 * g;
684
+ }
685
+ d[i] = cr; d[i + 1] = cg; d[i + 2] = cb;
686
+ }
687
+ ctx.putImageData(img, 0, 0);
688
+ H.vignette(ctx, W, H_, 0.12, '90,70,40');
689
+ const z = 1 + 0.035 * H.ease.inOut(H.clamp((t - 2.6) / 2.9));
690
+ if (z > 1.0001) {
691
+ const cp = H.layer(W, H_); cp.getContext('2d').drawImage(ctx.canvas, 0, 0, W, H_);
692
+ const fx = 640, fy = 330;
693
+ ctx.drawImage(cp, fx - fx / z, fy - fy / z, W / z, H_ / z, 0, 0, W, H_);
694
+ }
695
+ },
696
+ });
697
+ }