@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,666 @@
1
+ {
2
+ // Schematic metro diagram (Beck tradition): 0/45/90 lines, rounded corners, ticks, interchange rings.
3
+ const BG = '#F7F5EF', INK = '#1B1F3B', LW = 12, R = 26;
4
+
5
+ const LINES = [
6
+ { id: 'data', name: 'Data Line', color: '#0A6CB8', pts: [[90, 200], [330, 200], [430, 300], [660, 300]] },
7
+ { id: 'train', name: 'Training Line', color: '#DC2A26', pts: [[90, 440], [520, 440], [660, 300], [760, 300], [840, 380], [1100, 380]] },
8
+ { id: 'inf', name: 'Inference Line', color: '#00894A', pts: [[1100, 380], [1100, 480], [1040, 540], [420, 540]] },
9
+ { id: 'fb', name: 'Feedback Loop', color: '#F39419', pts: [[680, 540], [680, 480], [350, 480], [350, 110]] },
10
+ { id: 'safe', name: 'Safety Line', color: '#8B4A9C', pts: [[880, 110], [880, 620]] },
11
+ ];
12
+
13
+ // stations: [name, x, y, line(s), label side, opts]
14
+ // side: n s e w ne nw se sw ; interchange if >1 line
15
+ const ST = [
16
+ ['Web Crawl', 90, 200, ['data'], 'n', { term: true }],
17
+ ['Forums', 180, 200, ['data'], 'n'],
18
+ ['Code Repos', 265, 200, ['data'], 'n'],
19
+ ['Synthetic Data', 350, 220, ['data', 'fb'], 'e2', { dy: -9 }],
20
+ ['Filtering', 470, 300, ['data'], 's'],
21
+ ['Tokenizer', 565, 300, ['data'], 's'],
22
+ ['Pretraining', 660, 300, ['data', 'train'], 'n', { big: true, dy: -2 }],
23
+
24
+ ['Human Labels', 90, 440, ['train'], 's', { term: true }],
25
+ ['Annotation', 220, 440, ['train'], 's'],
26
+ ['Preference Data', 350, 440, ['fb', 'train'], 'nw', { dy: -4 }],
27
+ ['Curriculum', 590, 370, ['train'], 'e'],
28
+ ['Checkpoints', 726, 300, ['train'], 's'],
29
+ ['Fine-Tune', 880, 380, ['train', 'safe'], 'ne', { dy: -5 }],
30
+ ['RLHF', 1005, 380, ['train'], 'n'],
31
+ ['Eval Suite', 1100, 380, ['train', 'inf'], 'e'],
32
+
33
+ ['Deploy', 1100, 438, ['inf'], 'e'],
34
+ ['API Gateway', 990, 540, ['inf'], 'n'],
35
+ ['Guardrails', 880, 540, ['inf', 'safe'], 'se', { dy: 2 }],
36
+ ['Agents', 780, 540, ['inf'], 's'],
37
+ ['Chat App', 680, 540, ['inf', 'fb'], 's'],
38
+ ['Search', 560, 540, ['inf'], 's'],
39
+ ['Edge Devices', 420, 540, ['inf'], 's', { term: true }],
40
+
41
+ ['Simulators', 350, 110, ['fb'], 'e', { term: true }],
42
+ ['User Ratings', 455, 480, ['fb'], 's'],
43
+ ['Logs', 575, 480, ['fb'], 's'],
44
+
45
+ ['Red Team', 880, 110, ['safe'], 'e', { term: true }],
46
+ ['Policy', 880, 220, ['safe'], 'e'],
47
+ ['Bias Audit', 880, 462, ['safe'], 'w'],
48
+ ['Monitoring', 880, 620, ['safe'], 'e', { term: true }],
49
+ ];
50
+
51
+ function roundedPath(ctx, pts, r) {
52
+ ctx.beginPath(); ctx.moveTo(pts[0][0], pts[0][1]);
53
+ for (let i = 1; i < pts.length - 1; i++) ctx.arcTo(pts[i][0], pts[i][1], pts[i + 1][0], pts[i + 1][1], r);
54
+ const l = pts[pts.length - 1]; ctx.lineTo(l[0], l[1]);
55
+ }
56
+ // dense polyline of the rounded path (quadratic corners), with cumulative length
57
+ function densePath(pts, r) {
58
+ const out = [[pts[0][0], pts[0][1]]];
59
+ for (let i = 1; i < pts.length - 1; i++) {
60
+ const [ax, ay] = pts[i - 1], [bx, by] = pts[i], [cx, cy] = pts[i + 1];
61
+ const l1 = Math.hypot(bx - ax, by - ay), l2 = Math.hypot(cx - bx, cy - by);
62
+ const rr = Math.min(r * 0.9, l1 / 2, l2 / 2);
63
+ const p0 = [bx - (bx - ax) / l1 * rr, by - (by - ay) / l1 * rr], p2 = [bx + (cx - bx) / l2 * rr, by + (cy - by) / l2 * rr];
64
+ for (let k = 0; k <= 10; k++) {
65
+ const u = k / 10, a = (1 - u) * (1 - u), b = 2 * u * (1 - u), c = u * u;
66
+ out.push([a * p0[0] + b * bx + c * p2[0], a * p0[1] + b * by + c * p2[1]]);
67
+ }
68
+ }
69
+ out.push([pts[pts.length - 1][0], pts[pts.length - 1][1]]);
70
+ // subdivide straight runs so sampling is even
71
+ const d = [out[0]];
72
+ for (let i = 1; i < out.length; i++) {
73
+ const [ax, ay] = out[i - 1], [bx, by] = out[i], n = Math.max(1, Math.ceil(Math.hypot(bx - ax, by - ay) / 6));
74
+ for (let k = 1; k <= n; k++) d.push([ax + (bx - ax) * k / n, ay + (by - ay) * k / n]);
75
+ }
76
+ const cum = [0];
77
+ for (let i = 1; i < d.length; i++) cum.push(cum[i - 1] + Math.hypot(d[i][0] - d[i - 1][0], d[i][1] - d[i - 1][1]));
78
+ return { p: d, cum, len: cum[cum.length - 1] };
79
+ }
80
+ function pointAt(dp, s) {
81
+ s = H.clamp(s, 0, dp.len);
82
+ let lo = 0, hi = dp.cum.length - 1;
83
+ while (hi - lo > 1) { const m = (lo + hi) >> 1; if (dp.cum[m] < s) lo = m; else hi = m; }
84
+ const f = (s - dp.cum[lo]) / ((dp.cum[hi] - dp.cum[lo]) || 1), a = dp.p[lo], b = dp.p[hi];
85
+ return [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f, Math.atan2(b[1] - a[1], b[0] - a[0])];
86
+ }
87
+ function distOn(dp, x, y) {
88
+ let best = 1e9, bs = 0;
89
+ dp.p.forEach(([px, py], i) => { const d = (px - x) ** 2 + (py - y) ** 2; if (d < best) { best = d; bs = dp.cum[i]; } });
90
+ return bs;
91
+ }
92
+ function plen(pts) { let s = 0; for (let i = 1; i < pts.length; i++) s += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]); return s; }
93
+ // direction of the line segment that passes through (x,y)
94
+ function dirAt(pts, x, y) {
95
+ for (let i = 1; i < pts.length; i++) {
96
+ const [ax, ay] = pts[i - 1], [bx, by] = pts[i];
97
+ const dx = bx - ax, dy = by - ay, L = Math.hypot(dx, dy);
98
+ const u = ((x - ax) * dx + (y - ay) * dy) / (L * L);
99
+ const px = ax + u * dx, py = ay + u * dy;
100
+ if (u >= -0.01 && u <= 1.01 && Math.hypot(px - x, py - y) < 2) return [dx / L, dy / L];
101
+ }
102
+ return [1, 0];
103
+ }
104
+ // ---- print finish: cached paper texture (low-frequency mottle of about +-4% plus sparse fibres) ----
105
+ const TMC = {};
106
+ const paperTex = (W, H_) => {
107
+ const key = 'paper' + W + 'x' + H_; if (TMC[key]) return TMC[key];
108
+ const P = H.layer(W, H_), pc = P.getContext('2d');
109
+ const sw = Math.ceil(W / 4), sh = Math.ceil(H_ / 4), sm = H.layer(sw, sh), sc = sm.getContext('2d');
110
+ const id = sc.createImageData(sw, sh), d = id.data, nz = H.makeNoise(4242);
111
+ for (let y = 0; y < sh; y++) for (let x = 0; x < sw; x++) {
112
+ const v = nz.fbm2(x * 0.045, y * 0.045, 4) * 0.75 + nz.fbm2(x * 0.22 + 40, y * 0.22, 2) * 0.35;
113
+ const i = (y * sw + x) * 4, dk = v > 0; // darker mottle is a warm grey, lighter spots are white
114
+ d[i] = dk ? 104 : 255; d[i + 1] = dk ? 90 : 255; d[i + 2] = dk ? 66 : 250; d[i + 3] = H.clamp(Math.abs(v) * (dk ? 26 : 24), 0, 255);
115
+ }
116
+ sc.putImageData(id, 0, 0);
117
+ pc.imageSmoothingEnabled = true; pc.drawImage(sm, 0, 0, W, H_);
118
+ // fibres: short, gently curved strands, mostly a shade darker, a few lighter
119
+ const r = H.rng(7171); pc.lineCap = 'round';
120
+ for (let k = 0; k < 420; k++) {
121
+ const x = r.range(0, W), y = r.range(0, H_), a = r.range(0, Math.PI * 2), len = r.range(6, 22), bend = r.range(-0.6, 0.6);
122
+ const dark = r() < 0.75; pc.strokeStyle = dark ? `rgba(90,76,52,${r.range(0.07, 0.16)})` : `rgba(255,255,250,${r.range(0.3, 0.5)})`; pc.lineWidth = r.range(0.4, 0.9);
123
+ pc.beginPath(); pc.moveTo(x, y); pc.quadraticCurveTo(x + Math.cos(a + bend) * len * 0.5, y + Math.sin(a + bend) * len * 0.5, x + Math.cos(a) * len, y + Math.sin(a) * len); pc.stroke();
124
+ }
125
+ return (TMC[key] = P);
126
+ };
127
+ const paperFinish = (ctx, W, H_) => ctx.drawImage(paperTex(W, H_), 0, 0, W, H_);
128
+ // ---- amber dot-matrix departure board: a classic 5x7 LED font set onto the board's LED grid; each font dot
129
+ // covers k x k LEDs, every lit LED is a round dot with bloom, unlit LEDs stay faintly visible ----
130
+ const F57 = { 'A':18842895918, 'B':16694887983, 'C':15603893806, 'D':7836583207, 'E':33321092159, 'F':1108837439, 'G':32801457710, 'H':18842895921, 'I':15170932878, 'J':6753100060, 'K':18560947505, 'L':33320633377, 'M':18842572657, 'N':18850958897, 'O':15621211694, 'P':1108854319, 'Q':23946905134, 'R':18561353263, 'S':16660235326, 'T':4433514655, 'U':15621211697, 'V':4648912433, 'W':11464787505, 'X':18834663985, 'Y':4433521201, 'Z':33321787935, '0':15623448110, '1':15170932932, '2':33357578798, '3':15619854623, '4':8891181448, '5':15620127807, '6':15621129292, '7':2216829471, '8':15621113390, '9':6728664622, ',':2287992832, '%':26610901603, '→':143630464, ' ':0 };
131
+ const ledDots = (col, P) => { // one pitch-sized tile (drawn at 4x) holding a soft round dot
132
+ const key = 'dot' + col + P; if (TMC[key]) return TMC[key];
133
+ const T = H.layer(P * 4, P * 4), tc = T.getContext('2d'); tc.scale(4, 4);
134
+ const g = tc.createRadialGradient(P / 2, P / 2, 0, P / 2, P / 2, P * 0.46);
135
+ g.addColorStop(0, col); g.addColorStop(0.78, col); g.addColorStop(1, 'rgba(0,0,0,0)');
136
+ tc.fillStyle = g; tc.fillRect(0, 0, P, P);
137
+ return (TMC[key] = T);
138
+ };
139
+ function ledBoard(ctx, bx, by, bw, bh, a, fn, LEDP = 4) {
140
+ if (a <= 0) return;
141
+ const cols = Math.floor(bw / LEDP), rows = Math.floor(bh / LEDP), cell = new Uint8Array(cols * rows);
142
+ const on = (cx, cy) => { if (cx >= 0 && cy >= 0 && cx < cols && cy < rows) cell[cy * cols + cx] = 1; };
143
+ // writer, in LED cells: text(str, col, topRow, k [, 'center']) and box(c0, r0, c1, r1) outlines
144
+ const led = {
145
+ cols, rows,
146
+ width: (str, k) => (str.length * 6 - 1) * k,
147
+ text(str, c0, r0, k = 1, align) {
148
+ if (align === 'center') c0 = Math.round(c0 - led.width(str, k) / 2);
149
+ [...str].forEach((ch, i) => { const b = F57[ch] || 0, x0 = c0 + i * 6 * k;
150
+ for (let r = 0; r < 7; r++) for (let c = 0; c < 5; c++) if (Math.floor(b / 2 ** (r * 5 + c)) % 2) for (let v = 0; v < k; v++) for (let u = 0; u < k; u++) on(x0 + c * k + u, r0 + r * k + v); });
151
+ },
152
+ box(c0, r0, c1, r1) { for (let x = c0; x <= c1; x++) { on(x, r0); on(x, r1); } for (let y = r0; y <= r1; y++) { on(c0, y); on(c1, y); } },
153
+ };
154
+ fn(led);
155
+ // static parts of each board (housing with unlit LEDs, glass and bezel) and its scratch canvases are cached
156
+ const key = 'board' + bw + 'x' + bh + 'p' + LEDP; let BC = TMC[key];
157
+ if (!BC) {
158
+ const sF = LEDP >= 4 ? 1 : 2; // lit-LED raster: at least 4px per LED so the dots stay round
159
+ BC = TMC[key] = { sF, m: H.layer(cols, rows), lit: H.layer(cols * LEDP * sF, rows * LEDP * sF), BL: H.layer(Math.ceil(cols * LEDP / 4) + 8, Math.ceil(rows * LEDP / 4) + 8), house: H.layer(bw, bh), glass: H.layer(bw, bh) };
160
+ const hc = BC.house.getContext('2d'); hc.fillStyle = '#121317'; hc.fillRect(0, 0, bw, bh);
161
+ hc.scale(0.25, 0.25); hc.fillStyle = hc.createPattern(ledDots('rgba(255,170,40,0.085)', LEDP), 'repeat'); hc.fillRect(0, 0, cols * LEDP * 4, rows * LEDP * 4);
162
+ const gc = BC.glass.getContext('2d');
163
+ const gl = gc.createLinearGradient(0, 0, 0, bh); gl.addColorStop(0, 'rgba(255,255,255,0.06)'); gl.addColorStop(0.35, 'rgba(255,255,255,0)'); gl.addColorStop(1, 'rgba(0,0,0,0.12)');
164
+ gc.fillStyle = gl; gc.fillRect(0, 0, bw, bh);
165
+ gc.strokeStyle = '#2E313C'; gc.lineWidth = 6; gc.strokeRect(3, 3, bw - 6, bh - 6);
166
+ gc.strokeStyle = 'rgba(255,255,255,0.08)'; gc.lineWidth = 1; gc.strokeRect(6.5, 6.5, bw - 13, bh - 13);
167
+ BC.dotPat = BC.lit.getContext('2d').createPattern(ledDots('#fff', LEDP), 'repeat');
168
+ }
169
+ const mc = BC.m.getContext('2d'), id = mc.createImageData(cols, rows), d = id.data;
170
+ for (let i = 0; i < cell.length; i++) { d[i * 4] = 255; d[i * 4 + 1] = 178; d[i * 4 + 2] = 30; d[i * 4 + 3] = cell[i] ? 255 : 0; }
171
+ mc.putImageData(id, 0, 0);
172
+ // 2) blow each cell up to a round LED
173
+ const lit = BC.lit, lc = lit.getContext('2d');
174
+ lc.setTransform(1, 0, 0, 1, 0, 0); lc.globalCompositeOperation = 'source-over'; lc.clearRect(0, 0, lit.width, lit.height);
175
+ lc.imageSmoothingEnabled = false; lc.drawImage(BC.m, 0, 0, lit.width, lit.height);
176
+ lc.globalCompositeOperation = 'destination-in'; lc.fillStyle = BC.dotPat;
177
+ lc.setTransform(BC.sF / 4, 0, 0, BC.sF / 4, 0, 0); lc.fillRect(0, 0, lit.width * 4 / BC.sF, lit.height * 4 / BC.sF);
178
+ // bloom: blurred at quarter resolution (cheap)
179
+ const BL = BC.BL, blc = BL.getContext('2d');
180
+ blc.clearRect(0, 0, BL.width, BL.height); blc.filter = 'blur(1.6px)'; blc.drawImage(lit, 4, 4, cols * LEDP / 4, rows * LEDP / 4); blc.filter = 'none';
181
+ // 3) housing, bloom, lit LEDs, glass + bezel
182
+ const ox = bx, oy = by; // the LED grid starts at the board corner so text origins land on whole cells
183
+ ctx.save(); ctx.globalAlpha = a;
184
+ ctx.drawImage(BC.house, bx, by);
185
+ ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = a;
186
+ ctx.drawImage(BL, ox - 16, oy - 16, BL.width * 4, BL.height * 4);
187
+ ctx.globalAlpha = a; ctx.globalCompositeOperation = 'source-over';
188
+ ctx.drawImage(lit, ox, oy, cols * LEDP, rows * LEDP);
189
+ ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = a * 0.6; // hot LED cores so the sign reads at a glance
190
+ ctx.drawImage(lit, ox, oy, cols * LEDP, rows * LEDP);
191
+ ctx.globalAlpha = a; ctx.globalCompositeOperation = 'source-over';
192
+ ctx.drawImage(BC.glass, bx, by);
193
+ ctx.restore();
194
+ }
195
+
196
+ const SIDE = { n: [0, -1], s: [0, 1], e: [1, 0], w: [-1, 0], ne: [0.7071, -0.7071], nw: [-0.7071, -0.7071], se: [0.7071, 0.7071], sw: [-0.7071, 0.7071], e2: [1, 0] };
197
+
198
+ registerStyle({
199
+ id: 'transit-map',
200
+ name: 'Transit Map',
201
+ category: 'Data & Information',
202
+ description: 'A Beck-style metro diagram of an AI pipeline: fat coloured lines locked to 0/45/90 degrees with rounded bends, line-colour station ticks, black-ringed interchanges, a river and a boxed legend, all in clean sans lettering.',
203
+ motion: 'Title and key settle, then each line draws out from its terminus in turn with stations ticking on and interchanges popping as the line passes; once the network is complete, small trains glide along every route.',
204
+ duration: 7,
205
+ fonts: ['600 14px "Inter"', '800 14px "Inter"', '400 12px "Inter"', '800 30px "Inter"', 'italic 400 15px "Libre Baskerville"', '600 15px "Inter"', '800 15px "Inter"', '800 18px "Inter"', '800 44px "Inter"', '400 46px "VT323"', '800 22px "Inter"', '800 24px "Inter"', '700 20px "Inter"', '800 68px "Inter"', '400 96px "VT323"'],
206
+ frameT: 6.8,
207
+ readsPixels: true,
208
+ draw(ctx, W, H_, t) {
209
+ ctx.fillStyle = BG; ctx.fillRect(0, 0, W, H_);
210
+ paperFinish(ctx, W, H_); // paper mottle and fibres, printed over by the ink
211
+
212
+ const seg = (a, b) => H.clamp((t - a) / (b - a));
213
+ // ---- river: the Data Lake (washes in first) ----
214
+ const river = [[300, 790], [430, 660], [1300, 660]];
215
+ ctx.lineCap = 'butt'; ctx.lineJoin = 'round';
216
+ ctx.globalAlpha = seg(0, 0.6);
217
+ roundedPath(ctx, river, 60); ctx.strokeStyle = '#BFDDEB'; ctx.lineWidth = 34; ctx.stroke();
218
+ ctx.save(); ctx.font = 'italic 400 15px "Libre Baskerville"'; ctx.fillStyle = '#4B8DB0'; ctx.textAlign = 'left';
219
+ let lx = 1030; for (const ch of 'Data Lake') { ctx.fillText(ch, lx, 665); lx += ctx.measureText(ch).width + 2.5; }
220
+ ctx.restore();
221
+ ctx.globalAlpha = 1;
222
+
223
+ // ---- lines draw on in sequence, from their first terminus ----
224
+ const TIMES = { data: [0.5, 1.7], train: [0.95, 2.45], inf: [1.7, 2.7], fb: [2.15, 3.05], safe: [2.55, 3.4] };
225
+ const prog = {}, drawnLen = {}, DP = {};
226
+ LINES.forEach((ln) => {
227
+ DP[ln.id] = densePath(ln.pts, R);
228
+ prog[ln.id] = H.ease.inOut(seg(...TIMES[ln.id]));
229
+ drawnLen[ln.id] = prog[ln.id] * DP[ln.id].len;
230
+ });
231
+ ctx.lineCap = 'butt'; ctx.lineJoin = 'round';
232
+ LINES.forEach((ln) => {
233
+ const dp = DP[ln.id], target = drawnLen[ln.id]; if (target <= 0) return;
234
+ ctx.beginPath(); ctx.moveTo(dp.p[0][0], dp.p[0][1]);
235
+ for (let i = 1; i < dp.p.length && dp.cum[i] <= target; i++) ctx.lineTo(dp.p[i][0], dp.p[i][1]);
236
+ const e = pointAt(dp, target); ctx.lineTo(e[0], e[1]);
237
+ ctx.strokeStyle = ln.color; ctx.globalAlpha = 0.16; ctx.lineWidth = LW + 1.6; ctx.stroke(); // ink spread into the paper
238
+ ctx.globalAlpha = 1; ctx.lineWidth = LW; ctx.stroke();
239
+ });
240
+
241
+ const lineById = Object.fromEntries(LINES.map((l) => [l.id, l]));
242
+ // a station appears when any of its lines has been drawn past it; returns 0..1 pop progress
243
+ const stPop = (s) => {
244
+ let best = 0;
245
+ for (const id of s[3]) {
246
+ const dp = DP[id], d = distOn(dp, s[1], s[2]);
247
+ // how far (in px) the drawing head is beyond this station, mapped to a short pop
248
+ if (drawnLen[id] > 0.5) best = Math.max(best, H.clamp((drawnLen[id] - d + 6) / 40));
249
+ // far terminus: pop over the last moments of the line's draw
250
+ if (d > dp.len - 2) best = Math.max(best, H.clamp((drawnLen[id] - dp.len + 3) / 3));
251
+ }
252
+ return best;
253
+ };
254
+ const pops = ST.map(stPop);
255
+ const shown = (s) => pops[ST.indexOf(s)] > 0;
256
+
257
+ // ---- stations: ticks + terminus bars ----
258
+ ST.forEach((s) => {
259
+ const [name, x, y, ids, side, o = {}] = s;
260
+ if (ids.length > 1 || !shown(s)) return;
261
+ const ln = lineById[ids[0]];
262
+ const [dx, dy] = dirAt(ln.pts, x, y);
263
+ ctx.fillStyle = ln.color; ctx.strokeStyle = ln.color; ctx.lineCap = 'butt';
264
+ const pp = H.ease.out(pops[ST.indexOf(s)]);
265
+ if (o.term) {
266
+ // terminus: perpendicular bar across the line
267
+ ctx.lineWidth = 7; ctx.beginPath();
268
+ ctx.moveTo(x - dy * 15 * pp, y + dx * 15 * pp); ctx.lineTo(x + dy * 15 * pp, y - dx * 15 * pp); ctx.stroke();
269
+ } else {
270
+ // tick toward label side
271
+ let [sx, sy] = SIDE[side];
272
+ const nx = -dy, ny = dx, k = Math.sign(nx * sx + ny * sy) || 1;
273
+ ctx.lineWidth = 6; ctx.beginPath();
274
+ ctx.moveTo(x, y); ctx.lineTo(x + nx * k * (LW / 2 + 7) * pp, y + ny * k * (LW / 2 + 7) * pp); ctx.stroke();
275
+ }
276
+ });
277
+
278
+ // ---- interchanges: white ring, black outline ----
279
+ ST.forEach((s) => {
280
+ const [name, x, y, ids, side, o = {}] = s;
281
+ if (ids.length < 2 || !shown(s)) return;
282
+ const rr = (o.big ? 13 : 10.5) * Math.max(0.01, H.ease.back(pops[ST.indexOf(s)]));
283
+ ctx.fillStyle = '#fff'; ctx.strokeStyle = INK; ctx.lineWidth = 3.6;
284
+ ctx.beginPath(); ctx.arc(x, y, rr, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
285
+ });
286
+
287
+ // ---- labels ----
288
+ ctx.textBaseline = 'middle';
289
+ ST.forEach((s) => {
290
+ const [name, x, y, ids, side, o = {}] = s;
291
+ if (!shown(s)) return;
292
+ const inter = ids.length > 1;
293
+ ctx.font = inter || o.term ? '800 14px "Inter"' : '600 14px "Inter"';
294
+ ctx.fillStyle = INK;
295
+ const [sx, sy] = SIDE[side];
296
+ const off = inter ? (o.big ? 21 : 18) : o.term ? 22 : LW / 2 + 12;
297
+ let tx = x + sx * off, ty = y + sy * off;
298
+ ctx.textAlign = sx > 0.1 ? 'left' : sx < -0.1 ? 'right' : 'center';
299
+ if (side === 'n') ty -= 3; if (side === 's') ty += 3;
300
+ if (side === 'e2') { tx += 2; }
301
+ ty += o.dy || 0;
302
+ ctx.globalAlpha = H.clamp(pops[ST.indexOf(s)] * 1.6 - 0.4);
303
+ ctx.strokeStyle = BG; ctx.lineWidth = 4; ctx.lineJoin = 'round'; ctx.strokeText(name, tx, ty); // paper knockout halo
304
+ ctx.fillText(name, tx, ty);
305
+ ctx.globalAlpha = 1;
306
+ });
307
+
308
+ // ---- trains: once the network is complete, small cars glide along every route ----
309
+ ctx.textBaseline = 'alphabetic';
310
+ const ta = seg(3.5, 4.0);
311
+ if (ta > 0) {
312
+ const tr = H.rng(808);
313
+ LINES.forEach((ln) => {
314
+ const dp = DP[ln.id], nT = dp.len > 700 ? 2 : 1;
315
+ for (let k = 0; k < nT; k++) {
316
+ const speed = tr.range(70, 95), phase = tr.range(0, 1);
317
+ // ping-pong along the line with gentle easing near the termini
318
+ const cyc = (t - 3.5) * speed / dp.len + phase + k * 0.5;
319
+ const f = cyc % 2, u = f < 1 ? f : 2 - f;
320
+ const sPos = H.lerp(14, dp.len - 14, 0.5 - 0.5 * Math.cos(Math.PI * u));
321
+ const [px, py, ang] = pointAt(dp, sPos);
322
+ ctx.save(); ctx.translate(px, py); ctx.rotate(ang); ctx.globalAlpha = ta;
323
+ ctx.fillStyle = '#fff'; ctx.strokeStyle = INK; ctx.lineWidth = 1.6;
324
+ ctx.beginPath(); ctx.roundRect(-9, -3.6, 18, 7.2, 3.6); ctx.fill(); ctx.stroke();
325
+ ctx.restore();
326
+ }
327
+ });
328
+ }
329
+
330
+ // ---- title block (top right) ----
331
+ const tp = H.ease.out(seg(0, 0.6));
332
+ ctx.save(); ctx.globalAlpha = tp; ctx.translate(0, (1 - tp) * -14);
333
+ const tx0 = 990, ty0 = 40;
334
+ ctx.fillStyle = INK; ctx.fillRect(tx0, ty0, 250, 108);
335
+ // mark: three lines meeting at an interchange ring
336
+ const mx = tx0 + 46, my = ty0 + 50;
337
+ ctx.lineCap = 'butt'; ctx.lineWidth = 8;
338
+ [['#0A6CB8', [mx - 34, my], [mx, my]], ['#DC2A26', [mx - 26, my + 30], [mx, my]], ['#00894A', [mx, my], [mx + 0, my + 38]], ['#F39419', [mx, my - 34], [mx, my]]]
339
+ .forEach(([c, a1, b1]) => { ctx.strokeStyle = c; ctx.beginPath(); ctx.moveTo(...a1); ctx.lineTo(...b1); ctx.stroke(); });
340
+ ctx.fillStyle = '#fff'; ctx.strokeStyle = '#fff'; ctx.lineWidth = 3.5;
341
+ ctx.beginPath(); ctx.arc(mx, my, 10, 0, 7); ctx.fill();
342
+ ctx.strokeStyle = INK; ctx.beginPath(); ctx.arc(mx, my, 7, 0, 7); ctx.stroke();
343
+ ctx.fillStyle = '#fff'; ctx.textAlign = 'left';
344
+ ctx.font = '800 30px "Inter"'; ctx.fillText('AI Pipeline', tx0 + 84, ty0 + 52);
345
+ ctx.font = '400 12px "Inter"'; ctx.fillStyle = '#C9CCE0';
346
+ ctx.fillText('NETWORK MAP · 2026', tx0 + 84, ty0 + 76);
347
+ ctx.restore();
348
+
349
+ // ---- legend (bottom left) ----
350
+ const gx = 40, gy = 512, gw = 270, gh = 168;
351
+ ctx.save(); ctx.globalAlpha = seg(0.2, 0.6);
352
+ ctx.textAlign = 'left'; ctx.textBaseline = 'alphabetic';
353
+ ctx.fillStyle = '#fff'; ctx.fillRect(gx, gy, gw, gh);
354
+ ctx.strokeStyle = INK; ctx.lineWidth = 1.5; ctx.strokeRect(gx + 0.5, gy + 0.5, gw, gh);
355
+ ctx.font = '800 14px "Inter"'; ctx.fillStyle = INK; ctx.fillText('Key to lines', gx + 16, gy + 26);
356
+ LINES.forEach((ln, i) => {
357
+ const yy = gy + 48 + i * 22;
358
+ ctx.fillStyle = ln.color; ctx.fillRect(gx + 16, yy - 5, 44 * H.ease.out(seg(0.35 + i * 0.07, 0.65 + i * 0.07)), 10);
359
+ ctx.fillStyle = INK; ctx.font = '600 14px "Inter"'; ctx.textBaseline = 'middle';
360
+ ctx.fillText(ln.name, gx + 72, yy + 1);
361
+ });
362
+ // symbol key
363
+ ctx.fillStyle = '#fff'; ctx.strokeStyle = INK; ctx.lineWidth = 2.6;
364
+ ctx.beginPath(); ctx.arc(gx + 214, gy + 52, 7, 0, 7); ctx.fill(); ctx.stroke();
365
+ ctx.font = '400 12px "Inter"'; ctx.fillStyle = INK; ctx.textAlign = 'center';
366
+ ctx.fillText('Interchange', gx + 214, gy + 72);
367
+ ctx.fillStyle = '#6A6D80'; ctx.fillRect(gx + 196, gy + 100, 36, 8); ctx.fillRect(gx + 211, gy + 92, 6, 9);
368
+ ctx.fillStyle = INK; ctx.fillText('Station', gx + 214, gy + 118);
369
+ ctx.textAlign = 'left'; ctx.textBaseline = 'alphabetic';
370
+ ctx.restore();
371
+
372
+ // footnote
373
+ ctx.font = '400 12px "Inter"'; ctx.fillStyle = '#6A6D80'; ctx.textAlign = 'right';
374
+ ctx.fillText('Diagram not to scale · illustrative network', W - 40, 700);
375
+
376
+ H.grain(ctx, W, H_, 0.018, 3 + Math.floor(t * 12));
377
+ },
378
+ // Sound: a quiet station. Platform chimes (soft sine bells, kept low) open and resolve the journey, lines lay
379
+ // down with a smooth low track hum, stations click onto the map as the line reaches them, the train runs on an
380
+ // electric motor swell with bogie clacks over rail joints, and the amber board ticks its relay digits and
381
+ // clatters as it flips to the takeaway. Geometry and times mirror explainer() below.
382
+ explainerSound(k) {
383
+ const D = 15;
384
+ 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; };
385
+ const seg = (t, a, b) => H.clamp((t - a) / (b - a));
386
+ const io = H.ease.inOut;
387
+ // the explainer's network (same points as explainer())
388
+ const dpD = densePath([[80, 380], [900, 380]], 30);
389
+ const dpL = densePath([[900, 380], [900, 300], [1000, 200], [1400, 200], [1500, 300], [1500, 460], [1400, 560], [1000, 560], [900, 460], [900, 381]], 40);
390
+ const dpA = densePath([[1500, 380], [2620, 380]], 30);
391
+ const sGuess = distOn(dpL, 1200, 200), sCheck = distOn(dpL, 1500, 380), sAdj = distOn(dpL, 1200, 560);
392
+
393
+ // platform chime: soft sine bell with a quiet octave, nothing above ~1.6 kHz
394
+ const chime = (t, notes, g = 1, gap = 0.32, p = 0) => notes.forEach((n, i) => {
395
+ const f = k.hz(n);
396
+ k.tone(t + i * gap, { f, dur: 1.4, gain: 0.2 * g, a: 0.006, verb: 0.35, pan: p });
397
+ k.tone(t + i * gap, { f: f * 2, dur: 0.45, gain: 0.03 * g, a: 0.004, pan: p });
398
+ });
399
+ // a station marker clicks onto the map
400
+ const station = (t, g = 1, p = 0) => k.withMinor(() => {
401
+ k.noise(t, { dur: 0.02, filter: 'bandpass', f: 1500, q: 1.5, gain: 0.22 * g, a: 0.001, pan: p });
402
+ k.tone(t, { f: [[0, 520], [0.06, 300]], dur: 0.08, gain: 0.12 * g, pan: p });
403
+ });
404
+ // line laying: a smooth low hum whose brightness follows the draw speed
405
+ const lay = (t0, t1, p0, p1) => k.noise(t0, { dur: t1 - t0, color: 'brown', filter: 'bandpass', f: [[0, 180], [(t1 - t0) * 0.5, 520], [t1 - t0, 180]], q: 1.2, gain: 0.2, a: (t1 - t0) * 0.4, pan: (p0 + p1) / 2, minor: true });
406
+ // train run from s0 to s1 (path units) with ease inOut over [t0,t1]: motor swell + bogie clacks every 90 px
407
+ const run = (t0, t1, s0, s1, p = 0, g = 1) => {
408
+ const d = t1 - t0;
409
+ k.tone(t0, { f: [[0, 70], [d * 0.5, 140], [d, 70]], type: 'sawtooth', dur: d + 0.05, gain: 0.07 * g, a: d * 0.35, filter: 'lowpass', f2: 520, pan: p });
410
+ k.noise(t0, { dur: d + 0.05, color: 'brown', filter: 'lowpass', f: 400, gain: 0.18 * g, a: d * 0.3, pan: p });
411
+ for (let s = 90; s < Math.abs(s1 - s0); s += 90) {
412
+ const tc = when(t0, t1, io, s / Math.abs(s1 - s0));
413
+ k.withMinor(() => [0, 0.045].forEach((o) => { k.noise(tc + o, { dur: 0.025, filter: 'bandpass', f: 900, q: 1.4, gain: 0.22 * g, a: 0.001, pan: p }); k.tone(tc + o, { f: 95, dur: 0.05, gain: 0.12 * g, pan: p }); }));
414
+ }
415
+ k.noise(t1 - 0.12, { dur: 0.28, filter: 'bandpass', f: 2400, q: 0.8, gain: 0.05 * g, a: 0.1, pan: p, minor: true }); // brakes ease off
416
+ };
417
+ // dot-matrix board relay tick
418
+ const relay = (t, g = 1, p = 0) => k.withMinor(() => { k.noise(t, { dur: 0.014, filter: 'bandpass', f: 2000, q: 2.2, gain: 0.2 * g, a: 0.001, pan: p }); k.tone(t, { f: 180, type: 'square', dur: 0.018, gain: 0.025 * g, filter: 'lowpass', f2: 600, pan: p }); });
419
+ const counter = (t0, t1, steps, p) => { let last = -1, lastT = -1; for (let t = t0; t <= t1 + 1e-6; t += 1 / 120) { const v = Math.floor(io(seg(t, t0, t1)) * steps); if (v !== last && t - lastT >= 1 / 12) { relay(t, 0.8 + 0.3 * v / steps, p); last = v; lastT = t; } } };
420
+ // camera move: a soft low air shift, no swoosh
421
+ const glide = (t0, t1, g = 1) => k.noise(t0, { dur: t1 - t0 + 0.1, color: 'pink', filter: 'bandpass', f: [[0, 240], [(t1 - t0) * 0.5, 480], [t1 - t0 + 0.1, 220]], q: 0.7, gain: 0.08 * g, a: (t1 - t0) * 0.45 });
422
+
423
+ k.bed('room', 0, D, { gain: 0.45 });
424
+
425
+ // ---- title block + opening chime; title docks 1.4 -> 2.2
426
+ chime(0.05, ['E5', 'C5'], 0.7, 0.36);
427
+ k.noise(1.4, { dur: 0.8, filter: 'bandpass', f: [[0, 400], [0.8, 700]], q: 0.7, gain: 0.06, a: 0.3, pan: -0.4, minor: true });
428
+
429
+ // ---- Data Line lays 0.9 -> 2.5, its stations click on as it reaches them
430
+ lay(0.9, 2.5, -0.6, 0.4);
431
+ station(0.95, 0.9, -0.85); // terminus bar
432
+ [230, 450, 670].forEach((x) => station(when(0.9, 2.5, io, (x - 86) / dpD.len), 1, (x - 490) / 640 * 1.2));
433
+ k.play('pop', when(2.1, 2.5, (u) => u, 0.15), { f: 330, gain: 0.7, pan: 0.6 }); // MODEL interchange ring opens
434
+ relay(2.3, 1.2, -0.1); k.tone(2.3, { f: 100, dur: 0.3, gain: 0.04, a: 0.05 }); // board lights
435
+ // examples ride the line into the model: a faint rolling rumble under the count
436
+ k.noise(2.4, { dur: 3.9, color: 'brown', filter: 'lowpass', f: 260, gain: 0.1, a: 0.5, hold: 2.9, pan: 0.1 });
437
+ counter(2.5, 5.8, 30, -0.1);
438
+ k.play('tock', 5.8, { f: 420, gain: 0.45, pan: -0.1 }); // 1,000,000 settles on the board
439
+
440
+ // ---- camera to the Training Loop; the red line lays 6.1 -> 7.0 with its stations
441
+ glide(6.0, 6.7);
442
+ lay(6.1, 7.0, -0.3, 0.3);
443
+ [[sGuess, 0], [sCheck, 0.55], [sAdj, 0]].forEach(([s, p]) => station(when(6.1, 7.0, io, (s - 6) / dpL.len), 1.1, p));
444
+
445
+ // ---- the loop journey (train positions from explainer's trainS)
446
+ run(6.9, 7.4, 0, sGuess, -0.2);
447
+ k.play('pop', 7.4, { f: 300, gain: 0.5, minor: true }); // "dog?" board in the loop
448
+ // wrong guess: a low minor platform tone, the red X
449
+ k.tone(7.45, { f: 'D4', dur: 0.5, gain: 0.2, a: 0.006, verb: 0.25 });
450
+ k.tone(7.68, { f: 'B3', dur: 0.8, gain: 0.2, a: 0.006, verb: 0.25 });
451
+ k.tone(7.45, { f: 'D3', type: 'triangle', dur: 1.0, gain: 0.08, a: 0.01, filter: 'lowpass', f2: 500 });
452
+ run(8.0, 8.5, sGuess, sCheck, 0.4);
453
+ k.play('pop', 8.5, { f: 360, gain: 0.55, pan: 0.4 }); // "Answer: cat"
454
+ chime(8.52, ['A4'], 0.6, 0, 0.4);
455
+ run(8.9, 9.4, sCheck, sAdj, 0.2, 1.1); // error signal heads back
456
+ k.play('tock', 9.2, { f: 360, gain: 0.35, pan: -0.1, minor: true }); // weights panel
457
+ [9.48, 9.58, 9.68, 9.78].forEach((t, i) => k.play('tock', t, { f: 600 + i * 30, gain: 0.3, pan: -0.15, minor: true })); // slider knobs notch along
458
+ run(9.8, 10.2, sAdj, dpL.len + sGuess, -0.3);
459
+ chime(10.22, ['C5', 'E5', 'G5'], 0.65, 0.12); // "cat": correct, the bright platform chime
460
+
461
+ // ---- out along the Accuracy Line
462
+ glide(10.5, 11.2, 1.2);
463
+ lay(10.7, 11.6, -0.3, 0.7);
464
+ [1760, 2040, 2320].forEach((x, i) => station(when(10.7, 11.6, io, (x - 1506) / dpA.len), 1, -0.2 + i * 0.25));
465
+ k.play('pop', 11.6, { f: 300, gain: 0.6, pan: 0.7 }); // 97% terminus ring
466
+ { // fast train on the accuracy line from 10.9 at 1020 px/s, held behind the line head
467
+ const d = 0.85;
468
+ k.tone(10.9, { f: [[0, 90], [d * 0.6, 170], [d, 110]], type: 'sawtooth', dur: d, gain: 0.06, a: 0.2, filter: 'lowpass', f2: 600, pan: 0.2 });
469
+ for (let s = 120; s < dpA.len; s += 120) { const tc = Math.max(10.9 + s / 1020, when(10.7, 11.6, io, s / dpA.len)); k.noise(tc, { dur: 0.022, filter: 'bandpass', f: 900, q: 1.4, gain: 0.18, a: 0.001, pan: -0.4 + s / dpA.len, minor: true }); }
470
+ }
471
+ relay(11.0, 1.2, -0.1); // accuracy board lights
472
+ counter(11.1, 12.6, 36, -0.1);
473
+ // 97% held, board outlines it: the big hit
474
+ k.play('impact', 12.6, { gain: 0.75, pan: -0.1 });
475
+ chime(12.6, ['G4', 'D5'], 1.4, 0.0);
476
+
477
+ // ---- camera pulls out to the whole network; the takeaway board flips
478
+ glide(12.9, 13.6, 1.3);
479
+ relay(13.3, 1.2);
480
+ for (let t = 13.5, i = 0; t < 14.1; t += 1 / 22, i++) k.withMinor(() => k.noise(t, { dur: 0.012, filter: 'bandpass', f: 1600 + (i % 4) * 200, q: 1.6, gain: 0.12 + 0.05 * ((i * 3) % 4) / 3, a: 0.001, pan: -0.6 + (t - 13.5) / 0.6 * 1.2 }));
481
+ chime(14.12, ['C5', 'E5', 'G5', 'C6'], 0.9, 0.18); // takeaway set: the resolving platform chime
482
+ },
483
+ explainerDuration: 15,
484
+ explainerNote: 'The lesson is a journey on the network: the camera rides along a Data Line into the Model interchange, round a Training Loop line (Guess, Check, Adjust stations) and out along an Accuracy Line, while an amber dot-matrix departure board counts examples and accuracy and finally flips to the takeaway.',
485
+ explainer(ctx, W, H_, t) {
486
+ const S = (a, b) => H.clamp((t - a) / (b - a));
487
+ const BLUEL = '#0A6CB8', REDL = '#DC2A26', GRN = '#00894A';
488
+ const LWX = 16; // thicker lines for video
489
+ // ---- network geometry (world coordinates) ----
490
+ const DATA = [[80, 380], [900, 380]];
491
+ const LOOP = [[900, 380], [900, 300], [1000, 200], [1400, 200], [1500, 300], [1500, 460], [1400, 560], [1000, 560], [900, 460], [900, 381]];
492
+ const ACC = [[1500, 380], [2620, 380]];
493
+ const dpD = densePath(DATA, 30), dpL = densePath(LOOP, 40), dpA = densePath(ACC, 30);
494
+ const sGuess = distOn(dpL, 1200, 200), sCheck = distOn(dpL, 1500, 380), sAdj = distOn(dpL, 1200, 560);
495
+
496
+ // ---- camera: [time, scale, world x, world y] -> world point lands at screen (640, 360) ----
497
+ const CK = [[0, 1.35, 490, 330], [6.0, 1.35, 490, 330], [6.7, 1.5, 1200, 380], [10.5, 1.5, 1200, 380], [11.2, 1.0, 2060, 440], [12.9, 1.0, 2060, 440], [13.6, 0.47, 1350, 330], [15, 0.47, 1350, 330]];
498
+ let cs = 0.47, cx = 1350, cyw = 330;
499
+ for (let i = 0; i < CK.length - 1; i++) if (t >= CK[i][0] && t <= CK[i + 1][0]) { const u = H.ease.inOut((t - CK[i][0]) / (CK[i + 1][0] - CK[i][0])); cs = H.lerp(CK[i][1], CK[i + 1][1], u); cx = H.lerp(CK[i][2], CK[i + 1][2], u); cyw = H.lerp(CK[i][3], CK[i + 1][3], u); break; }
500
+ ctx.fillStyle = BG; ctx.fillRect(0, 0, W, H_);
501
+ paperFinish(ctx, W, H_); // paper mottle and fibres, printed over by the ink
502
+ ctx.save(); ctx.translate(W / 2, H_ / 2); ctx.scale(cs, cs); ctx.translate(-cx, -cyw);
503
+
504
+ ctx.lineCap = 'butt'; ctx.lineJoin = 'round';
505
+ roundedPath(ctx, [[-400, 690], [700, 690], [800, 640], [2000, 640], [2100, 700], [3200, 700]], 60);
506
+ ctx.strokeStyle = '#BFDDEB'; ctx.lineWidth = 40; ctx.stroke();
507
+
508
+ const drawLine = (dp, len, col) => {
509
+ if (len <= 0) return;
510
+ ctx.beginPath(); ctx.moveTo(dp.p[0][0], dp.p[0][1]);
511
+ for (let i = 1; i < dp.p.length && dp.cum[i] <= len; i++) ctx.lineTo(dp.p[i][0], dp.p[i][1]);
512
+ const e = pointAt(dp, len); ctx.lineTo(e[0], e[1]);
513
+ ctx.strokeStyle = col; ctx.globalAlpha = 0.16; ctx.lineWidth = LWX + 1.8; ctx.stroke(); // ink spread
514
+ ctx.globalAlpha = 1; ctx.lineWidth = LWX; ctx.stroke();
515
+ };
516
+ const lD = dpD.len * H.ease.inOut(S(0.9, 2.5)), lL = dpL.len * H.ease.inOut(S(6.1, 7.0)), lA = dpA.len * H.ease.inOut(S(10.7, 11.6));
517
+ drawLine(dpD, lD, BLUEL); drawLine(dpL, lL, REDL); drawLine(dpA, lA, GRN);
518
+ const chevrons = (dp, len, phase) => {
519
+ ctx.strokeStyle = 'rgba(255,255,255,0.9)'; ctx.lineWidth = 2.6; ctx.lineCap = 'round';
520
+ for (let s = 40 + (phase % 60); s < len - 20; s += 60) {
521
+ const [x, y, a] = pointAt(dp, s);
522
+ ctx.beginPath(); ctx.moveTo(x - Math.cos(a - 0.8) * 6, y - Math.sin(a - 0.8) * 6); ctx.lineTo(x, y); ctx.lineTo(x - Math.cos(a + 0.8) * 6, y - Math.sin(a + 0.8) * 6); ctx.stroke();
523
+ }
524
+ };
525
+ chevrons(dpD, lD, t * 30); chevrons(dpL, lL, t * 30); chevrons(dpA, lA, t * 30);
526
+
527
+ const tick = (x, y, nx, ny, col) => { ctx.strokeStyle = col; ctx.lineWidth = 7; ctx.lineCap = 'butt'; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + nx * (LWX / 2 + 9), y + ny * (LWX / 2 + 9)); ctx.stroke(); };
528
+ // labels sized so they read >= 28 px on screen in their beat
529
+ // in the final pull-back (camera scale < 1) labels counter-scale so they keep a minimum on-screen size
530
+ const minPx = (size) => (size >= 34 ? 22 : size >= 24 ? 20 : 17);
531
+ const label = (txt, x, y, align, bold, a = 1, size = 22) => { if (a <= 0) return; const fs = Math.max(size, minPx(size) / cs); ctx.globalAlpha = a; ctx.font = `${bold ? 800 : 600} ${fs}px "Inter"`; ctx.fillStyle = INK; ctx.textAlign = align; ctx.textBaseline = 'middle';
532
+ ctx.strokeStyle = BG; ctx.lineWidth = fs * 0.24; ctx.lineJoin = 'round'; ctx.strokeText(txt, x, y); ctx.fillText(txt, x, y); ctx.globalAlpha = 1; }; // paper knockout halo
533
+ const ring = (x, y, r, a = 1) => { if (a <= 0) return; const rr = r * H.ease.back(H.clamp(a)); ctx.fillStyle = '#fff'; ctx.strokeStyle = INK; ctx.lineWidth = 4.4; ctx.beginPath(); ctx.arc(x, y, Math.max(0.1, rr), 0, 7); ctx.fill(); ctx.stroke(); };
534
+ const badge = (kind, x, y, a) => { if (a <= 0) return; const k = 1.6; ctx.save(); ctx.globalAlpha = a; ctx.translate(x, y); ctx.scale(k, k); ctx.fillStyle = INK; ctx.beginPath(); ctx.roundRect(-17, -17, 34, 34, 6); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.fillStyle = '#fff'; ctx.lineWidth = 2; ctx.lineJoin = 'round';
535
+ if (kind === 'cat') { ctx.beginPath(); ctx.moveTo(-9, -2); ctx.lineTo(-8, -11); ctx.lineTo(-3, -6); ctx.lineTo(3, -6); ctx.lineTo(8, -11); ctx.lineTo(9, -2); ctx.quadraticCurveTo(9, 9, 0, 9); ctx.quadraticCurveTo(-9, 9, -9, -2); ctx.stroke(); }
536
+ else { ctx.beginPath(); ctx.arc(0, 1, 8, 0, 7); ctx.stroke(); ctx.beginPath(); ctx.ellipse(-9, -1, 3, 7, 0.3, 0, 7); ctx.ellipse(9, -1, 3, 7, -0.3, 0, 7); ctx.fill(); }
537
+ ctx.restore(); };
538
+ [[230, 'Cat Photos', 'cat'], [450, 'Dog Photos', 'dog'], [670, 'Labels', null]].forEach(([x, name, kind]) => {
539
+ const a = H.clamp((lD - (x - 80) + 6) / 30); if (a <= 0) return;
540
+ tick(x, 380, 0, -1, BLUEL); label(name, x, 340, 'center', true, a);
541
+ if (kind) badge(kind, x, 280, a);
542
+ });
543
+ if (lD > 0) { ctx.strokeStyle = BLUEL; ctx.lineWidth = 9; ctx.beginPath(); ctx.moveTo(80, 360); ctx.lineTo(80, 400); ctx.stroke(); label('Training Data', 70, 424, 'left', true, S(0.9, 1.2)); }
544
+ if (t > 2.3 && t < 6.6) {
545
+ for (let k = 0; k < 12; k++) {
546
+ const s = ((t - 2.3) * 170 + k * 68) % dpD.len; if (s > lD) continue;
547
+ const [x, y] = pointAt(dpD, s);
548
+ ctx.fillStyle = '#fff'; ctx.strokeStyle = INK; ctx.lineWidth = 2; ctx.beginPath(); ctx.roundRect(x - 13, y - 5, 26, 10, 5); ctx.fill(); ctx.stroke();
549
+ }
550
+ }
551
+ ring(900, 380, 26, S(2.1, 2.5));
552
+ label('MODEL', 866, 420, 'right', true, S(2.3, 2.7), 24);
553
+ const lp = (s) => H.clamp((lL - s + 6) / 30);
554
+ ring(1200, 200, 16, lp(sGuess)); label('Guess', 1200, 160, 'center', true, lp(sGuess), 24);
555
+ ring(1500, 380, 16, lp(sCheck)); label('Check', 1518, 344, 'left', true, lp(sCheck), 24);
556
+ ring(1200, 560, 16, lp(sAdj)); label('Adjust', 1200, 600, 'center', true, lp(sAdj), 24);
557
+
558
+ // ---- the loop journey ----
559
+ const trainS = (() => {
560
+ if (t < 6.9) return -1;
561
+ if (t < 7.4) return sGuess * H.ease.inOut(S(6.9, 7.4));
562
+ if (t < 8.0) return sGuess;
563
+ if (t < 8.5) return H.lerp(sGuess, sCheck, H.ease.inOut(S(8.0, 8.5)));
564
+ if (t < 8.9) return sCheck;
565
+ if (t < 9.4) return H.lerp(sCheck, sAdj, H.ease.inOut(S(8.9, 9.4)));
566
+ if (t < 9.8) return sAdj;
567
+ if (t < 10.2) return H.lerp(sAdj, dpL.len + sGuess, H.ease.inOut(S(9.8, 10.2)));
568
+ return dpL.len + sGuess;
569
+ })();
570
+ const box = (x, y, txt, col, a, size = 22) => { if (a <= 0) return 0; ctx.globalAlpha = a; ctx.font = `800 ${size}px "Inter"`; const w = ctx.measureText(txt).width + 30; const h = size * 1.9; ctx.fillStyle = '#fff'; ctx.strokeStyle = col; ctx.lineWidth = 3.5; ctx.beginPath(); ctx.roundRect(x - w / 2, y - h / 2, w, h, 9); ctx.fill(); ctx.stroke(); ctx.fillStyle = col; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(txt, x, y + 1); ctx.globalAlpha = 1; return w; };
571
+ // guess bubbles sit inside the loop, just under the Guess station
572
+ const g1 = S(7.4, 7.6) * (1 - S(9.8, 10.0));
573
+ if (g1 > 0) {
574
+ const w = box(1180, 262, 'dog?', INK, g1, 24);
575
+ ctx.globalAlpha = g1; ctx.strokeStyle = REDL; ctx.lineWidth = 5; ctx.lineCap = 'round';
576
+ const xx = 1180 + w / 2 + 12; ctx.beginPath(); ctx.moveTo(xx, 250); ctx.lineTo(xx + 22, 272); ctx.moveTo(xx + 22, 250); ctx.lineTo(xx, 272); ctx.stroke(); ctx.globalAlpha = 1;
577
+ for (let k = 0; k < 2; k++) { const ph = ((t - 7.5) / 0.6 + k * 0.5); if (ph < 0 || t > 8.4) continue; const f = ph % 1; ctx.strokeStyle = H.rgba(REDL, 0.7 * (1 - f)); ctx.lineWidth = 4; ctx.beginPath(); ctx.arc(1200, 200, 20 + f * 46, 0, 7); ctx.stroke(); }
578
+ }
579
+ const ck = S(8.5, 8.7) * (1 - S(9.8, 10.0));
580
+ if (ck > 0) box(1340, 380, 'Answer: cat', REDL, ck, 22);
581
+ const g2 = S(10.2, 10.35);
582
+ if (g2 > 0) {
583
+ const w = box(1180, 262, 'cat', GRN, g2 * (1 - S(10.9, 11.1)), 24);
584
+ ctx.globalAlpha = g2 * (1 - S(10.9, 11.1)); ctx.strokeStyle = GRN; ctx.lineWidth = 5; ctx.lineCap = 'round'; ctx.lineJoin = 'round';
585
+ const xx = 1180 + w / 2 + 12; ctx.beginPath(); ctx.moveTo(xx, 262); ctx.lineTo(xx + 9, 272); ctx.lineTo(xx + 28, 248); ctx.stroke(); ctx.globalAlpha = 1;
586
+ }
587
+ const adjA = S(9.2, 9.4) * (1 - S(10.4, 10.7));
588
+ if (adjA > 0) {
589
+ ctx.globalAlpha = adjA;
590
+ ctx.fillStyle = INK; ctx.fillRect(1010, 400, 200, 116);
591
+ ctx.fillStyle = '#C9CCE0'; ctx.font = '700 20px "Inter"'; ctx.textAlign = 'left'; ctx.textBaseline = 'alphabetic'; ctx.fillText('WEIGHTS', 1024, 426);
592
+ const mv = H.ease.inOut(S(9.4, 9.8));
593
+ [[0.2, 0.75], [0.7, 0.3], [0.4, 0.85]].forEach(([a0, a1], i) => {
594
+ const y = 448 + i * 24; ctx.fillStyle = '#555a78'; ctx.fillRect(1024, y, 170, 5);
595
+ ctx.fillStyle = '#F39419'; ctx.fillRect(1024 + 160 * H.lerp(a0, a1, mv), y - 6, 10, 17);
596
+ });
597
+ ctx.globalAlpha = 1;
598
+ }
599
+ if (trainS >= 0) {
600
+ const red = t >= 8.9 && t < 9.8;
601
+ const [x, y, a] = pointAt(dpL, trainS % dpL.len);
602
+ ctx.save(); ctx.translate(x, y); ctx.rotate(a);
603
+ ctx.fillStyle = red ? INK : '#fff'; ctx.strokeStyle = INK; ctx.lineWidth = 2.6;
604
+ ctx.beginPath(); ctx.roundRect(-22, -8, 44, 16, 8); ctx.fill(); ctx.stroke();
605
+ ctx.restore();
606
+ if (red) { ctx.save(); ctx.font = '800 22px "Inter"'; ctx.fillStyle = REDL; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText('error signal', x + 30, y - 22); ctx.restore(); }
607
+ if (t >= 6.9 && t < 7.6) { ctx.save(); ctx.globalAlpha = 1 - S(7.4, 7.6); ctx.font = '700 22px "Inter"'; ctx.fillStyle = INK; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText('a new cat photo', x + 30, y + 4); ctx.restore(); }
608
+ }
609
+ const accS = [[1760, '61%'], [2040, '80%'], [2320, '90%'], [2620, '97%']];
610
+ accS.forEach(([x, name], i) => {
611
+ const a = i === 3 ? H.clamp((lA - dpA.len + 3) / 3) : H.clamp((lA - (x - 1500) + 6) / 30); if (a <= 0) return;
612
+ if (i < 3) { tick(x, 380, 0, -1, GRN); label(name, x, 336, 'center', true, a, 30); }
613
+ else { ring(x, 380, 24, a); label(name, x, 326, 'center', true, a, 34); }
614
+ });
615
+ label('Accuracy Line', 1540, 424, 'left', true, S(10.9, 11.3), 24);
616
+ const pulse = S(12.0, 12.8);
617
+ if (pulse > 0 && pulse < 1) for (let k = 0; k < 2; k++) { const f = (pulse * 2 + k * 0.5) % 1; ctx.strokeStyle = H.rgba(GRN, 0.7 * (1 - f)); ctx.lineWidth = 5; ctx.beginPath(); ctx.arc(2620, 380, 28 + f * 56, 0, 7); ctx.stroke(); }
618
+ if (t > 10.9) {
619
+ const s = Math.min(dpA.len, (t - 10.9) * 1020);
620
+ const [x, y] = pointAt(dpA, Math.min(s, lA)); ctx.fillStyle = '#fff'; ctx.strokeStyle = INK; ctx.lineWidth = 2.6; ctx.beginPath(); ctx.roundRect(x - 22, y - 8, 44, 16, 8); ctx.fill(); ctx.stroke();
621
+ }
622
+ ctx.restore();
623
+
624
+ // ---- title block: big, then docked to the corner ----
625
+ {
626
+ const d = H.ease.inOut(S(1.4, 2.2));
627
+ const bw = H.lerp(860, 352, d), bh = H.lerp(200, 76, d), bx = H.lerp(210, 24, d), by = H.lerp(240, 20, d);
628
+ ctx.globalAlpha = S(0, 0.3);
629
+ ctx.fillStyle = INK; ctx.fillRect(bx, by, bw, bh);
630
+ const mxp = bx + bh * 0.42, myp = by + bh / 2, k = bh / 108;
631
+ ctx.lineCap = 'butt'; ctx.lineWidth = 8 * k;
632
+ [[BLUEL, [mxp - 34 * k, myp], [mxp, myp]], [REDL, [mxp - 26 * k, myp + 30 * k], [mxp, myp]], [GRN, [mxp, myp], [mxp, myp + 38 * k]], ['#F39419', [mxp, myp - 34 * k], [mxp, myp]]]
633
+ .forEach(([c, a1, b1]) => { ctx.strokeStyle = c; ctx.beginPath(); ctx.moveTo(...a1); ctx.lineTo(...b1); ctx.stroke(); });
634
+ ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(mxp, myp, 10 * k, 0, 7); ctx.fill();
635
+ ctx.strokeStyle = INK; ctx.lineWidth = 3.5 * k; ctx.beginPath(); ctx.arc(mxp, myp, 7 * k, 0, 7); ctx.stroke();
636
+ ctx.fillStyle = '#fff'; ctx.textAlign = 'left'; ctx.textBaseline = 'alphabetic';
637
+ ctx.font = `800 ${H.lerp(68, 28, d)}px "Inter"`; ctx.fillText('How does AI learn?', bx + bh * 0.85, by + bh * 0.58);
638
+ ctx.font = `400 ${H.lerp(22, 12, d)}px "Inter"`; ctx.fillStyle = '#C9CCE0'; ctx.fillText('A JOURNEY IN THREE LINES', bx + bh * 0.85, by + bh * 0.82);
639
+ ctx.globalAlpha = 1;
640
+ }
641
+ // ---- departure board (amber dot-matrix): hero counter under the line ----
642
+ const scr = (str, seed) => { const r = H.rng(seed); return str.replace(/[0-9A-Z]/g, () => '0123456789ABCDEFGHJKLMNPRSTUVWXYZ'[Math.floor(r() * 33)]); };
643
+ const board = (bx, by, bw, bh, a, fn, P) => ledBoard(ctx, bx, by, bw, bh, a, fn, P);
644
+ const dataB = S(2.3, 2.6) * (1 - S(6.0, 6.3));
645
+ board(380, 470, 520, 150, dataB, (led) => { // 4px LED pitch: 130 x 37 LEDs
646
+ led.text('EXAMPLES LOADED', 6, 5, 1);
647
+ const n = Math.round(1e6 * H.ease.inOut(S(2.5, 5.8)) / 1000) * 1000;
648
+ led.text(n.toLocaleString('en-US'), 6, 17, 2);
649
+ });
650
+ const accB = S(11.0, 11.3) * (1 - S(13.2, 13.4));
651
+ board(390, 395, 500, 160, accB, (led) => { // 125 x 40 LEDs; the number fills the board at 3x3 LEDs per dot
652
+ const p = H.ease.inOut(S(11.1, 12.6)), val = Math.round(61 + 36 * p) + '%';
653
+ led.text('ACCURACY', 6, 3, 1);
654
+ led.text(val, 6, 13, 3);
655
+ if (t >= 12.6) led.box(3, 11, 6 + led.width(val, 3) + 2, 35);
656
+ });
657
+ const tkB = S(13.3, 13.6);
658
+ board(130, 540, 1020, 130, tkB, (led) => { // finer 2.5px pitch sign: 408 x 52 LEDs
659
+ const msg = 'MORE EXAMPLES → BETTER GUESSES';
660
+ const flip = S(13.5, 14.1), nOk = Math.floor(msg.length * flip);
661
+ led.text(msg.slice(0, nOk) + scr(msg.slice(nOk), Math.floor(t * 20)), 204, 19, 2, 'center');
662
+ }, 2.5);
663
+ H.grain(ctx, W, H_, 0.018, 3 + Math.floor(t * 12));
664
+ },
665
+ });
666
+ }