@autoaiai/aicut 0.1.7 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/MANUAL.md +94 -84
  2. package/package.json +1 -1
  3. package/product.json +2 -1
  4. package/skills/aicut/SKILL.md +4 -4
  5. package/skills/aicut/references/commands.md +15 -15
  6. package/skills/aicut/references/film-format.md +1 -1
  7. package/skills/aicut/references/recipes.md +4 -4
  8. package/skills/aicut/references/troubleshooting.md +2 -2
  9. package/skills/aicut-motion/SKILL.md +75 -9
  10. package/skills/aicut-motion/THIRD_PARTY_NOTICES.md +14 -0
  11. package/skills/aicut-motion/assets/atlas/lib/harness.js +116 -0
  12. package/skills/aicut-motion/assets/atlas/lib/sound.js +274 -0
  13. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/code.js +937 -0
  14. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/poster.jpg +0 -0
  15. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/style.json +13 -0
  16. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/code.js +909 -0
  17. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/poster.jpg +0 -0
  18. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/style.json +13 -0
  19. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/code.js +881 -0
  20. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/poster.jpg +0 -0
  21. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/style.json +13 -0
  22. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/code.js +897 -0
  23. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/poster.jpg +0 -0
  24. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/style.json +13 -0
  25. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/code.js +814 -0
  26. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/poster.jpg +0 -0
  27. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/style.json +13 -0
  28. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/code.js +990 -0
  29. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/poster.jpg +0 -0
  30. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/style.json +13 -0
  31. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/code.js +994 -0
  32. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/poster.jpg +0 -0
  33. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/style.json +13 -0
  34. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/code.js +792 -0
  35. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/poster.jpg +0 -0
  36. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/style.json +13 -0
  37. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/code.js +840 -0
  38. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/poster.jpg +0 -0
  39. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/style.json +13 -0
  40. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/code.js +1126 -0
  41. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/poster.jpg +0 -0
  42. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/style.json +13 -0
  43. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/code.js +854 -0
  44. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/poster.jpg +0 -0
  45. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/style.json +13 -0
  46. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/code.js +1124 -0
  47. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/poster.jpg +0 -0
  48. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/style.json +13 -0
  49. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/code.js +728 -0
  50. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/poster.jpg +0 -0
  51. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/style.json +13 -0
  52. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/code.js +664 -0
  53. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/poster.jpg +0 -0
  54. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/style.json +13 -0
  55. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/code.js +991 -0
  56. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/poster.jpg +0 -0
  57. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/style.json +13 -0
  58. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/code.js +801 -0
  59. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/poster.jpg +0 -0
  60. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/style.json +13 -0
  61. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/code.js +692 -0
  62. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/poster.jpg +0 -0
  63. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/style.json +13 -0
  64. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/code.js +756 -0
  65. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/poster.jpg +0 -0
  66. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/style.json +13 -0
  67. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/code.js +922 -0
  68. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/poster.jpg +0 -0
  69. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/style.json +13 -0
  70. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/code.js +852 -0
  71. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/poster.jpg +0 -0
  72. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/style.json +13 -0
  73. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/code.js +724 -0
  74. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/poster.jpg +0 -0
  75. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/style.json +13 -0
  76. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/code.js +793 -0
  77. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/poster.jpg +0 -0
  78. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/style.json +13 -0
  79. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/code.js +846 -0
  80. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/poster.jpg +0 -0
  81. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/style.json +13 -0
  82. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/code.js +1008 -0
  83. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/poster.jpg +0 -0
  84. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/style.json +13 -0
  85. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/code.js +680 -0
  86. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/poster.jpg +0 -0
  87. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/style.json +13 -0
  88. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/code.js +633 -0
  89. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/poster.jpg +0 -0
  90. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/style.json +13 -0
  91. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/code.js +757 -0
  92. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/poster.jpg +0 -0
  93. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/style.json +13 -0
  94. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/code.js +828 -0
  95. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/poster.jpg +0 -0
  96. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/style.json +13 -0
  97. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/code.js +697 -0
  98. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/poster.jpg +0 -0
  99. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/style.json +13 -0
  100. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/code.js +687 -0
  101. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/poster.jpg +0 -0
  102. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/style.json +13 -0
  103. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/code.js +727 -0
  104. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/poster.jpg +0 -0
  105. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/style.json +13 -0
  106. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/code.js +903 -0
  107. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/poster.jpg +0 -0
  108. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/style.json +13 -0
  109. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/code.js +662 -0
  110. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/poster.jpg +0 -0
  111. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/style.json +13 -0
  112. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/code.js +951 -0
  113. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/poster.jpg +0 -0
  114. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/style.json +13 -0
  115. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/code.js +759 -0
  116. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/poster.jpg +0 -0
  117. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/style.json +13 -0
  118. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/code.js +738 -0
  119. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/poster.jpg +0 -0
  120. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/style.json +13 -0
  121. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/code.js +793 -0
  122. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/poster.jpg +0 -0
  123. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/style.json +13 -0
  124. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/code.js +673 -0
  125. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/poster.jpg +0 -0
  126. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/style.json +13 -0
  127. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/code.js +907 -0
  128. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/poster.jpg +0 -0
  129. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/style.json +13 -0
  130. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/code.js +833 -0
  131. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/poster.jpg +0 -0
  132. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/style.json +13 -0
  133. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/code.js +845 -0
  134. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/poster.jpg +0 -0
  135. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/style.json +13 -0
  136. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/code.js +496 -0
  137. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/poster.jpg +0 -0
  138. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/style.json +13 -0
  139. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/code.js +635 -0
  140. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/poster.jpg +0 -0
  141. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/style.json +13 -0
  142. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/code.js +799 -0
  143. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/poster.jpg +0 -0
  144. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/style.json +13 -0
  145. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/code.js +547 -0
  146. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/poster.jpg +0 -0
  147. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/style.json +13 -0
  148. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/code.js +592 -0
  149. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/poster.jpg +0 -0
  150. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/style.json +13 -0
  151. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/code.js +768 -0
  152. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/poster.jpg +0 -0
  153. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/style.json +13 -0
  154. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/code.js +666 -0
  155. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/poster.jpg +0 -0
  156. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/style.json +13 -0
  157. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/code.js +559 -0
  158. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/poster.jpg +0 -0
  159. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/style.json +13 -0
  160. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/code.js +943 -0
  161. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/poster.jpg +0 -0
  162. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/style.json +13 -0
  163. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/code.js +606 -0
  164. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/poster.jpg +0 -0
  165. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/style.json +13 -0
  166. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/code.js +730 -0
  167. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/poster.jpg +0 -0
  168. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/style.json +13 -0
  169. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/code.js +782 -0
  170. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/poster.jpg +0 -0
  171. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/style.json +13 -0
  172. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/code.js +504 -0
  173. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/poster.jpg +0 -0
  174. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/style.json +13 -0
  175. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/code.js +517 -0
  176. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/poster.jpg +0 -0
  177. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/style.json +13 -0
  178. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/code.js +713 -0
  179. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/poster.jpg +0 -0
  180. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/style.json +13 -0
  181. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/code.js +717 -0
  182. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/poster.jpg +0 -0
  183. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/style.json +13 -0
  184. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/code.js +540 -0
  185. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/poster.jpg +0 -0
  186. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/style.json +13 -0
  187. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/code.js +524 -0
  188. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/poster.jpg +0 -0
  189. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/style.json +13 -0
  190. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/code.js +934 -0
  191. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/poster.jpg +0 -0
  192. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/style.json +13 -0
  193. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/code.js +690 -0
  194. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/poster.jpg +0 -0
  195. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/style.json +13 -0
  196. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/code.js +612 -0
  197. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/poster.jpg +0 -0
  198. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/style.json +13 -0
  199. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/code.js +666 -0
  200. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/poster.jpg +0 -0
  201. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/style.json +13 -0
  202. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/code.js +651 -0
  203. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/poster.jpg +0 -0
  204. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/style.json +13 -0
  205. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/code.js +656 -0
  206. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/poster.jpg +0 -0
  207. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/style.json +13 -0
  208. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/code.js +672 -0
  209. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/poster.jpg +0 -0
  210. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/style.json +13 -0
  211. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/code.js +548 -0
  212. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/poster.jpg +0 -0
  213. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/style.json +13 -0
  214. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/code.js +989 -0
  215. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/poster.jpg +0 -0
  216. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/style.json +13 -0
  217. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/code.js +440 -0
  218. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/poster.jpg +0 -0
  219. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/style.json +13 -0
  220. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/code.js +895 -0
  221. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/poster.jpg +0 -0
  222. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/style.json +13 -0
  223. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/code.js +563 -0
  224. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/poster.jpg +0 -0
  225. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/style.json +13 -0
  226. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/code.js +627 -0
  227. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/poster.jpg +0 -0
  228. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/style.json +13 -0
  229. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/code.js +1096 -0
  230. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/poster.jpg +0 -0
  231. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/style.json +13 -0
  232. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/code.js +595 -0
  233. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/poster.jpg +0 -0
  234. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/style.json +13 -0
  235. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/code.js +665 -0
  236. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/poster.jpg +0 -0
  237. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/style.json +13 -0
  238. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/code.js +645 -0
  239. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/poster.jpg +0 -0
  240. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/style.json +13 -0
  241. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/code.js +436 -0
  242. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/poster.jpg +0 -0
  243. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/style.json +13 -0
  244. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/code.js +1237 -0
  245. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/poster.jpg +0 -0
  246. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/style.json +13 -0
  247. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/code.js +622 -0
  248. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/poster.jpg +0 -0
  249. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/style.json +13 -0
  250. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/code.js +524 -0
  251. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/poster.jpg +0 -0
  252. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/style.json +13 -0
  253. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/code.js +689 -0
  254. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/poster.jpg +0 -0
  255. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/style.json +13 -0
  256. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/code.js +574 -0
  257. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/poster.jpg +0 -0
  258. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/style.json +13 -0
  259. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/code.js +469 -0
  260. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/poster.jpg +0 -0
  261. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/style.json +13 -0
  262. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/code.js +531 -0
  263. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/poster.jpg +0 -0
  264. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/style.json +13 -0
  265. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/code.js +675 -0
  266. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/poster.jpg +0 -0
  267. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/style.json +13 -0
  268. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/code.js +609 -0
  269. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/poster.jpg +0 -0
  270. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/style.json +13 -0
  271. package/skills/aicut-motion/references/design-systems/README.md +1 -1
  272. package/skills/aicut-motion/references/motion-delivery.md +55 -0
  273. package/skills/aicut-motion/references/pages.md +1 -1
  274. package/skills/aicut-motion/references/style-index.json +690 -0
  275. package/skills/aicut-motion/references/style-research.md +73 -0
  276. package/skills/aicut-motion/references/style-selection.md +71 -0
  277. package/skills/aicut-motion/scripts/atlas-viewer.html +195 -0
  278. package/skills/aicut-motion/scripts/atlas.mjs +109 -0
  279. package/studio/client.mjs +3 -3
  280. package/studio/server/fetch-github.mjs +14 -3
  281. package/studio/server/server.mjs +71 -6
  282. package/studio/server/skill-summaries.mjs +60 -0
  283. package/studio/server/skills.mjs +121 -7
  284. package/studio/server/styles-store.mjs +235 -0
  285. package/studio/server/template-posters.mjs +2 -2
  286. package/studio/server/templates-page.mjs +93 -93
  287. package/studio/server/templates.mjs +32 -7
  288. package/studio/ui/dist/assets/ExportCenter-DasulhAv.js +1 -0
  289. package/studio/ui/dist/assets/SettingsModal-BZw0Hz2_.js +2 -0
  290. package/studio/ui/dist/assets/apple-touch-icon-ClxQ7j-O.png +0 -0
  291. package/studio/ui/dist/assets/es-C8XPZARI.js +1 -0
  292. package/studio/ui/dist/assets/favicon-Blwx8Trs.png +0 -0
  293. package/studio/ui/dist/assets/index-BjJVmBzB.js +1623 -0
  294. package/studio/ui/dist/assets/index-C3BfKZFO.css +1 -0
  295. package/studio/ui/dist/assets/ja-BeNkdaEj.js +1 -0
  296. package/studio/ui/dist/assets/ko-DcTVhdzK.js +1 -0
  297. package/studio/ui/dist/assets/zh-CN-Cg51ctbL.js +1 -0
  298. package/studio/ui/dist/index.html +4 -3
  299. package/studio/ui/dist/assets/ExportCenter-F-2vF-lQ.js +0 -1
  300. package/studio/ui/dist/assets/SettingsModal-BgVYguNu.js +0 -2
  301. package/studio/ui/dist/assets/es-BRUzvuoM.js +0 -1
  302. package/studio/ui/dist/assets/index-Bhg-9dA3.css +0 -1
  303. package/studio/ui/dist/assets/index-CWur1Iw9.js +0 -1625
  304. package/studio/ui/dist/assets/ja-Cth-uDcW.js +0 -1
  305. package/studio/ui/dist/assets/ko-upvw591D.js +0 -1
  306. package/studio/ui/dist/assets/zh-CN-BCfJFazB.js +0 -1
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": 25,
3
+ "name": "Cathedral Glass",
4
+ "nameZh": "教堂彩绘玻璃",
5
+ "family": "Painterly",
6
+ "familyZh": "绘画质感",
7
+ "description": "A leaded Gothic window: hand-cut pieces of streaky, seedy antique glass held in thick dark came, painted grisaille details, and light blazing through into a dim stone interior with dusty rays.",
8
+ "motionNotes": "Motion: Daylight sweeps slowly across the window so pieces brighten in turn, the rays drift with floating dust, and the chip in the saint's hands pulses.",
9
+ "explainerNotes": "Explainer: Told as a cycle of three leaded windows along a nave, the camera walking from window to window; arrows are strips of glass in lead came with glass arrowheads, labels and the counters are grisaille painted on pale banner glass, and emphasis is ruby glass rings and light flaring through the pieces.",
10
+ "code": "code.js",
11
+ "registeredAs": "stained-glass",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }
@@ -0,0 +1,633 @@
1
+ {
2
+ // Prestige-newspaper data graphic (FT / NYT / Economist register).
3
+ const BG = '#FFF1E5', INK = '#33302E', MUTED = '#66605C', GRID = 'rgba(51,48,46,0.16)';
4
+ const HI = '#990F3D', GREY = '#A79E95';
5
+
6
+ // Illustrative annual data, $bn
7
+ const YEARS = [2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026];
8
+ const SERIES = [
9
+ { name: 'Artificial intelligence', hi: true, v: [11, 16, 24, 37, 50, 63, 58, 92, 104, 176, 262, 348, 405] },
10
+ { name: 'Cloud infrastructure', v: [58, 66, 77, 88, 101, 116, 138, 152, 163, 171, 182, 191, 198] },
11
+ { name: 'Cybersecurity', v: [28, 32, 36, 41, 47, 52, 60, 73, 82, 86, 92, 98, 104] },
12
+ { name: 'Robotics', v: [19, 21, 24, 28, 31, 33, 30, 38, 44, 47, 52, 58, 63] },
13
+ { name: 'AR/VR headsets', v: [4, 7, 11, 14, 17, 19, 24, 36, 31, 25, 22, 21, 22] },
14
+ ];
15
+
16
+ // quarterly interpolation with a little seeded texture so lines feel like real data
17
+ function quarterly(v, seed, jitter) {
18
+ const r = H.rng(seed), out = [];
19
+ for (let i = 0; i < v.length - 1; i++) {
20
+ const p0 = v[Math.max(0, i - 1)], p1 = v[i], p2 = v[i + 1], p3 = v[Math.min(v.length - 1, i + 2)];
21
+ for (let q = 0; q < 4; q++) {
22
+ const f = q / 4, f2 = f * f, f3 = f2 * f;
23
+ // Catmull-Rom between annual points
24
+ const base = 0.5 * (2 * p1 + (-p0 + p2) * f + (2 * p0 - 5 * p1 + 4 * p2 - p3) * f2 + (-p0 + 3 * p1 - 3 * p2 + p3) * f3);
25
+ out.push(base + (i + q > 0 ? r.gauss() * jitter * Math.sqrt(Math.max(1, base)) : 0));
26
+ }
27
+ }
28
+ out.push(v[v.length - 1]);
29
+ return out;
30
+ }
31
+
32
+ function smallCaps(ctx, str, x, y, size, color, track, align) {
33
+ // faux small caps: capitals at full size, lower-case set as smaller capitals
34
+ ctx.fillStyle = color;
35
+ const big = `600 ${size}px "Inter"`, sm = `600 ${Math.round(size * 0.78)}px "Inter"`;
36
+ const parts = [];
37
+ for (const ch of str) {
38
+ const up = ch === ch.toUpperCase() && ch !== ch.toLowerCase();
39
+ parts.push({ c: ch.toUpperCase(), f: up || /[0-9©]/.test(ch) ? big : sm });
40
+ }
41
+ let w = 0;
42
+ for (const p of parts) { ctx.font = p.f; w += ctx.measureText(p.c).width + track; }
43
+ let cx = align === 'right' ? x - w : x;
44
+ ctx.textAlign = 'left';
45
+ for (const p of parts) { ctx.font = p.f; ctx.fillText(p.c, cx, y); cx += ctx.measureText(p.c).width + track; }
46
+ }
47
+
48
+ // hand-drawn leader: wobbly quadratic with a small open arrowhead
49
+ function leader(ctx, x0, y0, cx, cy, x1, y1, seed, color, prog = 1, lw = 1.25) {
50
+ if (prog <= 0) return;
51
+ const r = H.rng(seed);
52
+ ctx.save();
53
+ ctx.strokeStyle = color; ctx.lineWidth = lw; ctx.lineCap = 'round'; ctx.lineJoin = 'round';
54
+ ctx.beginPath();
55
+ let px = 0, py = 0;
56
+ const nI = Math.max(1, Math.round(28 * prog));
57
+ for (let i = 0; i <= nI; i++) {
58
+ const u = i / 28, a = (1 - u) * (1 - u), b = 2 * (1 - u) * u, c = u * u;
59
+ const wob = Math.sin(u * Math.PI) * 0.8;
60
+ px = a * x0 + b * cx + c * x1 + r.gauss() * 0.25 * wob;
61
+ py = a * y0 + b * cy + c * y1 + r.gauss() * 0.25 * wob;
62
+ i ? ctx.lineTo(px, py) : ctx.moveTo(px, py);
63
+ }
64
+ ctx.stroke();
65
+ if (prog < 0.97) { ctx.restore(); return; }
66
+ // arrowhead from tangent at end
67
+ const tx = x1 - cx, ty = y1 - cy, L = Math.hypot(tx, ty) || 1, ux = tx / L, uy = ty / L;
68
+ const hl = 8;
69
+ ctx.beginPath();
70
+ ctx.moveTo(x1 - ux * hl - uy * hl * 0.55, y1 - uy * hl + ux * hl * 0.55);
71
+ ctx.lineTo(x1 + r.gauss() * 0.2, y1);
72
+ ctx.lineTo(x1 - ux * hl + uy * hl * 0.5, y1 - uy * hl - ux * hl * 0.5);
73
+ ctx.stroke();
74
+ ctx.restore();
75
+ }
76
+
77
+ // handwritten write-on: reveal text left to right
78
+ function writeOn(ctx, str, x, y, p) {
79
+ if (p <= 0) return;
80
+ const w = ctx.measureText(str).width;
81
+ ctx.save(); ctx.beginPath(); ctx.rect(x - 4, y - 30, (w + 8) * H.clamp(p), 42); ctx.clip();
82
+ ctx.fillText(str, x, y); ctx.restore();
83
+ }
84
+ const seg = (t, a, b) => H.clamp((t - a) / (b - a));
85
+
86
+ // ---- explainer helpers: line icons in the graphic's hairline style ----
87
+ function catIcon(ctx, x, y, s, col) {
88
+ ctx.save(); ctx.translate(x, y); ctx.scale(s, s); ctx.strokeStyle = col; ctx.fillStyle = col; ctx.lineWidth = 1.6 / s; ctx.lineJoin = 'round';
89
+ ctx.beginPath(); ctx.moveTo(-12, -4); ctx.lineTo(-11, -17); ctx.lineTo(-4, -10); ctx.quadraticCurveTo(0, -11.5, 4, -10); ctx.lineTo(11, -17); ctx.lineTo(12, -4);
90
+ ctx.quadraticCurveTo(13, 11, 0, 12); ctx.quadraticCurveTo(-13, 11, -12, -4); ctx.closePath(); ctx.stroke();
91
+ ctx.beginPath(); ctx.arc(-5, -1, 1.6, 0, 7); ctx.arc(5, -1, 1.6, 0, 7); ctx.fill();
92
+ ctx.beginPath(); ctx.moveTo(-1.5, 4); ctx.lineTo(1.5, 4); ctx.lineTo(0, 5.8); ctx.closePath(); ctx.fill();
93
+ ctx.beginPath(); ctx.moveTo(-3, 6); ctx.lineTo(-15, 4); ctx.moveTo(-3, 7.5); ctx.lineTo(-14, 9); ctx.moveTo(3, 6); ctx.lineTo(15, 4); ctx.moveTo(3, 7.5); ctx.lineTo(14, 9); ctx.stroke();
94
+ ctx.restore();
95
+ }
96
+ function dogIcon(ctx, x, y, s, col) {
97
+ ctx.save(); ctx.translate(x, y); ctx.scale(s, s); ctx.strokeStyle = col; ctx.fillStyle = col; ctx.lineWidth = 1.6 / s; ctx.lineJoin = 'round';
98
+ ctx.beginPath(); ctx.moveTo(-9, -10); ctx.quadraticCurveTo(0, -15, 9, -10); ctx.quadraticCurveTo(12, 0, 8, 7); ctx.quadraticCurveTo(0, 14, -8, 7); ctx.quadraticCurveTo(-12, 0, -9, -10); ctx.stroke();
99
+ ctx.beginPath(); ctx.ellipse(-12, -2, 4, 9, 0.35, 0, 7); ctx.ellipse(12, -2, 4, 9, -0.35, 0, 7); ctx.fill();
100
+ ctx.beginPath(); ctx.arc(-4, -3, 1.6, 0, 7); ctx.arc(4, -3, 1.6, 0, 7); ctx.fill();
101
+ ctx.beginPath(); ctx.ellipse(0, 5, 3.2, 2.3, 0, 0, 7); ctx.fill();
102
+ ctx.restore();
103
+ }
104
+ // hand-drawn emphasis ring (wobbly, overshooting like a marker circle)
105
+ function handRing(ctx, x, y, rx, ry, p, seed, col, lw = 1.8) {
106
+ if (p <= 0) return;
107
+ const r = H.rng(seed);
108
+ ctx.save(); ctx.strokeStyle = col; ctx.lineWidth = lw; ctx.lineCap = 'round'; ctx.beginPath();
109
+ const n = Math.max(2, Math.floor(64 * p)), a0 = -2.4;
110
+ for (let k = 0; k <= n; k++) { const a = a0 + k / 64 * Math.PI * 2.25, w = 1 + 0.06 * Math.sin(k * 0.35) + k * 0.0012; const px = x + Math.cos(a) * rx * w, py = y + Math.sin(a) * ry * w; k ? ctx.lineTo(px, py) : ctx.moveTo(px, py); }
111
+ ctx.stroke(); ctx.restore();
112
+ }
113
+ // a small network drawn as an editorial diagram: nodes + hairline weights
114
+ function miniNet(ctx, x, y, w, h, weights, hot, nr = 7) {
115
+ const cols = [3, 4, 2];
116
+ const P = cols.map((n, c) => Array.from({ length: n }, (_, i) => [x + c * w / 2, y + (i + 0.5) * h / n]));
117
+ let k = 0;
118
+ for (let c = 0; c < 2; c++) for (const a of P[c]) for (const b of P[c + 1]) {
119
+ const wt = weights[k++ % weights.length];
120
+ // hairline-but-dark like the hero's grid: readable at video size even for near-zero weights
121
+ ctx.strokeStyle = wt > 0 ? H.rgba(HI, Math.min(1, 0.5 + 0.7 * Math.abs(wt))) : H.rgba('#857D75', Math.min(1, 0.42 + 0.7 * Math.abs(wt)));
122
+ ctx.lineWidth = 1.4 + 2.4 * Math.abs(wt);
123
+ ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
124
+ }
125
+ for (const col of P) for (const [px, py] of col) {
126
+ ctx.fillStyle = BG; ctx.beginPath(); ctx.arc(px, py, nr, 0, 7); ctx.fill();
127
+ ctx.strokeStyle = hot ? HI : INK; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(px, py, nr, 0, 7); ctx.stroke();
128
+ }
129
+ return P;
130
+ }
131
+
132
+ registerStyle({
133
+ id: 'editorial-chart',
134
+ name: 'Editorial Chart',
135
+ category: 'Data & Information',
136
+ description: 'A prestige-newspaper data graphic on FT salmon: serif headline and dek, hairline grid, one claret highlight series against quiet greys, direct labels and hand-annotated callouts, small-caps source line.',
137
+ motion: 'Headline sets, gridlines rule across, grey context lines settle, then the claret line draws left to right with its wash filling behind it and handwritten notes inking in as the line reaches them; a slow push and a soft pulse on the end point hold the shot.',
138
+ duration: 6,
139
+ fonts: ['700 34px "Libre Baskerville"', '400 17px "Libre Baskerville"', 'italic 400 15px "Libre Baskerville"',
140
+ '600 13px "Inter"', '400 13px "Inter"', '800 13px "Inter"', '700 22px "Caveat"', '700 92px "Libre Baskerville"', '700 46px "Libre Baskerville"', '600 28px "Inter"', '400 26px "Inter"'],
141
+ frameT: 5.9,
142
+ readsPixels: true,
143
+ draw(ctx, W, H_, t) {
144
+ // ---- paper ----
145
+ ctx.fillStyle = BG; ctx.fillRect(0, 0, W, H_);
146
+ // slow push-in across the shot
147
+ ctx.save();
148
+ const push = 1 + 0.018 * H.ease.inOut(seg(t, 1.5, 6));
149
+ ctx.translate(W / 2, H_ * 0.55); ctx.scale(push, push); ctx.translate(-W / 2, -H_ * 0.55);
150
+
151
+ const L = 84, R = 1080, T = 214, B = 596;
152
+ const yMax = 450;
153
+ const X = (yr) => L + (yr - 2014) / 12 * (R - L);
154
+ const Y = (v) => B - v / yMax * (B - T);
155
+ const reveal = H.ease.inOut(seg(t, 2.0, 4.0));
156
+ const greyIn = H.ease.out(seg(t, 1.1, 2.2));
157
+
158
+ // ---- masthead rule + kicker ----
159
+ const ruleP = H.ease.out(seg(t, 0, 0.5));
160
+ ctx.fillStyle = INK; ctx.fillRect(L, 40, 1116 * ruleP, 3);
161
+ ctx.fillStyle = HI; ctx.fillRect(L, 40, 46 * H.clamp(ruleP * 4), 7);
162
+ ctx.globalAlpha = seg(t, 0.2, 0.5);
163
+ smallCaps(ctx, 'Artificial Intelligence', L, 76, 13, HI, 1.6);
164
+ ctx.globalAlpha = 1;
165
+
166
+ // ---- headline & dek: set line by line with a short rise ----
167
+ ctx.textAlign = 'left'; ctx.textBaseline = 'alphabetic';
168
+ const setIn = (str, font, col, y, a, b) => {
169
+ const p = H.ease.out(seg(t, a, b)); if (p <= 0) return;
170
+ ctx.globalAlpha = p; ctx.fillStyle = col; ctx.font = font; ctx.fillText(str, L, y + (1 - p) * 10); ctx.globalAlpha = 1;
171
+ };
172
+ setIn('AI spending leaves the rest behind', '700 34px "Libre Baskerville"', INK, 120, 0.15, 0.75);
173
+ setIn('Global corporate investment by technology category, $bn', '400 17px "Libre Baskerville"', MUTED, 152, 0.35, 0.9);
174
+ setIn('Annual, 2014–2026. Figures are illustrative', '400 13px "Inter"', MUTED, 177, 0.5, 1.0);
175
+
176
+ // ---- forecast band ----
177
+ const fx0 = X(2025), fx1 = X(2026);
178
+ const fb = seg(t, 1.2, 1.7);
179
+ ctx.fillStyle = `rgba(51,48,46,${0.055 * fb})`; ctx.fillRect(fx0, T - 6, fx1 - fx0, B - T + 6);
180
+ ctx.globalAlpha = fb;
181
+ ctx.font = 'italic 400 13px "Libre Baskerville"'; ctx.fillStyle = MUTED; ctx.textAlign = 'center';
182
+ ctx.fillText('Forecast', (fx0 + fx1) / 2, T + 10);
183
+ ctx.globalAlpha = 1;
184
+
185
+ // ---- gridlines rule across, bottom first; labels sit on the line (NYT style) ----
186
+ ctx.textAlign = 'left'; ctx.font = '400 13px "Inter"';
187
+ for (let v = 0; v <= 400; v += 100) {
188
+ const k = v / 100, gp = H.ease.inOut(seg(t, 0.5 + k * 0.09, 1.05 + k * 0.09));
189
+ if (gp <= 0) continue;
190
+ const y = Y(v) + 0.5;
191
+ ctx.fillStyle = v === 0 ? INK : GRID;
192
+ ctx.fillRect(L, Math.round(y) - (v === 0 ? 0.5 : 0), (R - L + 24) * gp, v === 0 ? 1.4 : 1);
193
+ if (v) { ctx.globalAlpha = seg(gp, 0.3, 1); ctx.fillStyle = MUTED; ctx.fillText(v === 400 ? '$400bn' : String(v), L, y - 6); ctx.globalAlpha = 1; }
194
+ }
195
+ // x ticks
196
+ ctx.textAlign = 'center';
197
+ for (let yr = 2014; yr <= 2026; yr += 1) {
198
+ const a = seg(t, 0.75 + (yr - 2014) * 0.035, 1.05 + (yr - 2014) * 0.035); if (a <= 0) continue;
199
+ ctx.globalAlpha = a;
200
+ const x = Math.round(X(yr)) + 0.5;
201
+ ctx.fillStyle = INK; ctx.fillRect(x - 0.5, B, 1, yr % 2 === 0 ? 6 : 3);
202
+ if (yr % 2 === 0) { ctx.fillStyle = MUTED; ctx.fillText(String(yr), x, B + 24); }
203
+ ctx.globalAlpha = 1;
204
+ }
205
+
206
+ // ---- grey context lines with direct labels ----
207
+ const pts = SERIES.map((s, i) => {
208
+ const q = quarterly(s.v, 100 + i, s.hi ? 0.18 : 0.12);
209
+ return q.map((v, k) => [X(2014 + k / 4), Y(v)]);
210
+ });
211
+ ctx.lineJoin = 'round'; ctx.lineCap = 'round';
212
+ SERIES.forEach((s, i) => {
213
+ if (s.hi) return;
214
+ const p = pts[i];
215
+ const gp = H.ease.inOut(seg(t, 1.0 + i * 0.1, 1.9 + i * 0.1)); if (gp <= 0) return;
216
+ ctx.save(); ctx.beginPath(); ctx.rect(0, 0, L + (R - L) * gp + 2, H_); ctx.clip();
217
+ ctx.strokeStyle = GREY; ctx.lineWidth = 1.8;
218
+ ctx.beginPath(); p.forEach(([x, y], k) => (k ? ctx.lineTo(x, y) : ctx.moveTo(x, y))); ctx.stroke();
219
+ ctx.restore();
220
+ });
221
+ // label positions (nudged to avoid collisions)
222
+ const labY = { 'Cloud infrastructure': 0, 'Cybersecurity': 0, 'Robotics': 0, 'AR/VR headsets': 0 };
223
+ const ends = SERIES.map((s, i) => ({ s, y: pts[i][pts[i].length - 1][1] })).filter((e) => !e.s.hi).sort((a, b) => a.y - b.y);
224
+ for (let k = 1; k < ends.length; k++) if (ends[k].y - ends[k - 1].y < 17) ends[k].y = ends[k - 1].y + 17;
225
+ ctx.textAlign = 'left'; ctx.font = '400 13px "Inter"';
226
+ ends.forEach((e) => {
227
+ const last = pts[SERIES.indexOf(e.s)].slice(-1)[0];
228
+ ctx.globalAlpha = seg(t, 2.0, 2.4);
229
+ ctx.fillStyle = '#857D75'; ctx.fillText(e.s.name, R + 10, e.y + 4.5);
230
+ if (Math.abs(e.y - last[1]) > 2) { ctx.strokeStyle = 'rgba(133,125,117,0.6)'; ctx.lineWidth = 0.8; ctx.beginPath(); ctx.moveTo(R + 2, last[1]); ctx.lineTo(R + 8, e.y); ctx.stroke(); }
231
+ ctx.globalAlpha = 1;
232
+ });
233
+
234
+ // ---- highlight series: area wash + line, revealed by clip ----
235
+ const hp = pts[0];
236
+ const clipX = L + (R - L) * reveal;
237
+ ctx.save();
238
+ ctx.beginPath(); ctx.rect(0, 0, reveal > 0 ? clipX + 2 : 0, H_); ctx.clip();
239
+ // area
240
+ const g = ctx.createLinearGradient(0, T, 0, B);
241
+ g.addColorStop(0, 'rgba(153,15,61,0.20)'); g.addColorStop(1, 'rgba(153,15,61,0.04)');
242
+ ctx.fillStyle = g; ctx.beginPath(); ctx.moveTo(hp[0][0], B);
243
+ hp.forEach(([x, y]) => ctx.lineTo(x, y)); ctx.lineTo(hp[hp.length - 1][0], B); ctx.closePath(); ctx.fill();
244
+ // solid actuals, dashed forecast
245
+ const split = hp.findIndex(([x]) => x >= fx0 - 0.1);
246
+ ctx.strokeStyle = HI; ctx.lineWidth = 3.4;
247
+ ctx.beginPath(); for (let k = 0; k <= split; k++) (k ? ctx.lineTo(...hp[k]) : ctx.moveTo(...hp[k])); ctx.stroke();
248
+ ctx.setLineDash([5, 5]); ctx.lineWidth = 3;
249
+ ctx.beginPath(); for (let k = split; k < hp.length; k++) (k > split ? ctx.lineTo(...hp[k]) : ctx.moveTo(...hp[k])); ctx.stroke();
250
+ ctx.setLineDash([]);
251
+ ctx.restore();
252
+
253
+ // drawing head: a small dot riding the tip of the line while it draws
254
+ if (reveal > 0 && reveal < 1) {
255
+ let k = hp.findIndex(([x]) => x >= clipX); if (k < 1) k = 1;
256
+ const [ax, ay] = hp[k - 1], [bx, by] = hp[k], f = H.clamp((clipX - ax) / (bx - ax || 1));
257
+ ctx.fillStyle = HI; ctx.beginPath(); ctx.arc(ax + (bx - ax) * f, ay + (by - ay) * f, 4.2, 0, 7); ctx.fill();
258
+ }
259
+ // end marker + direct label
260
+ const endP = hp[hp.length - 1];
261
+ const pop = seg(t, 3.95, 4.3);
262
+ if (pop > 0) {
263
+ // live pulse on the latest value during the hold
264
+ for (let k = 0; k < 2; k++) {
265
+ const ph = ((t - 4.3) / 1.4 + k * 0.5) % 1;
266
+ if (t > 4.3) { ctx.strokeStyle = H.rgba(HI, 0.35 * (1 - ph)); ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(endP[0], endP[1], 6 + ph * 16, 0, 7); ctx.stroke(); }
267
+ }
268
+ const sc = H.ease.back(pop);
269
+ ctx.fillStyle = BG; ctx.beginPath(); ctx.arc(endP[0], endP[1], 6.5 * sc, 0, 7); ctx.fill();
270
+ ctx.strokeStyle = HI; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(endP[0], endP[1], Math.max(0.1, 5 * sc), 0, 7); ctx.stroke();
271
+ ctx.textAlign = 'left'; ctx.globalAlpha = seg(t, 4.05, 4.45);
272
+ ctx.font = '800 13px "Inter"'; ctx.fillStyle = HI;
273
+ ctx.fillText('Artificial', R + 14, endP[1] - 3);
274
+ ctx.fillText('intelligence', R + 14, endP[1] + 13);
275
+ ctx.font = '400 13px "Inter"'; ctx.fillText('$405bn', R + 14, endP[1] + 30);
276
+ ctx.globalAlpha = 1;
277
+ }
278
+
279
+ // ---- annotations: written on by hand once the line has passed ----
280
+ const ann = (x) => H.clamp((clipX - x) / 110);
281
+ ctx.font = '700 22px "Caveat"'; ctx.textAlign = 'left';
282
+ // 1: chatbot moment
283
+ let a = ann(X(2023.4));
284
+ ctx.fillStyle = INK;
285
+ writeOn(ctx, 'Chatbots go mainstream —', X(2018.75), Y(330), a * 3.2);
286
+ writeOn(ctx, 'spending doubles in 18 months', X(2018.75), Y(330) + 22, a * 3.2 - 1.1);
287
+ leader(ctx, X(2021.55), Y(330) + 34, X(2022.9), Y(292), X(2023.55) - 9, Y(222) - 4, 11, INK, seg(a, 0.68, 1));
288
+ // 2: pandemic dip
289
+ const dx = X(2020), dy = Y(58);
290
+ a = ann(dx + 30);
291
+ ctx.fillStyle = MUTED;
292
+ writeOn(ctx, 'A brief pandemic', X(2016.9), Y(178), a * 3);
293
+ writeOn(ctx, 'pause', X(2016.9), Y(178) + 21, a * 3 - 1);
294
+ leader(ctx, X(2017.6), Y(178) + 30, X(2019.2), Y(118), dx - 3, dy - 11, 23, MUTED, seg(a, 0.66, 1));
295
+ // 3: overtakes cloud
296
+ const cxp = X(2022.95), cyp = Y(170);
297
+ a = ann(cxp + 20);
298
+ if (a > 0) {
299
+ ctx.strokeStyle = MUTED; ctx.lineWidth = 1.2; ctx.beginPath(); ctx.arc(cxp, cyp, 9, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * seg(a, 0, 0.35)); ctx.stroke();
300
+ }
301
+ ctx.fillStyle = MUTED;
302
+ writeOn(ctx, 'AI passes cloud', X(2023.55), Y(118), (a - 0.55) * 2.6);
303
+ leader(ctx, X(2023.5), Y(124), X(2023.05), Y(128), cxp + 3, cyp + 12, 31, MUTED, seg(a, 0.3, 0.6));
304
+
305
+ // ---- footer: source line in small caps ----
306
+ const fp = seg(t, 4.2, 4.7);
307
+ ctx.fillStyle = 'rgba(51,48,46,0.35)'; ctx.fillRect(L, 648, 1116 * H.ease.out(fp), 1);
308
+ ctx.globalAlpha = fp;
309
+ smallCaps(ctx, 'Source: Illustrative estimates compiled for this graphic', L, 672, 12, MUTED, 1.1);
310
+ smallCaps(ctx, 'Graphic by the Data Desk', L + 1116, 672, 12, MUTED, 1.1, 'right');
311
+ ctx.globalAlpha = 1;
312
+ ctx.restore();
313
+
314
+ // paper tooth (quietly alive)
315
+ H.grain(ctx, W, H_, 0.025, 5 + Math.floor(t * 12));
316
+ },
317
+ // Sound: a quiet newsroom reading an online graphic. Scrolls are mouse-wheel notches (one per 120 px of the
318
+ // eased scroll) over a soft page glide, Caveat notes and leaders are a felt pen of matching length, numerals
319
+ // advance with small type clicks, the 97% gets the one big hit. Times mirror explainer() below.
320
+ explainerSound(k) {
321
+ const D = 15;
322
+ const pan = (px) => H.clamp((px / 1280) * 2 - 1, -1, 1) * 0.6;
323
+ 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; };
324
+ // felt pen note: nib touch + stroke for the write-on span
325
+ const pen = (a, b, g = 1, p = 0, minor = true) => {
326
+ k.noise(a, { dur: 0.018, filter: 'bandpass', f: 1600, q: 1.3, gain: 0.12 * g, a: 0.001, pan: p, minor });
327
+ k.play('stroke', a, { material: 'pen', dur: b - a, gain: 1.5 * g, pan: p, minor });
328
+ };
329
+ // a small type click for counter digits (dry, mid-low, never a chirp)
330
+ const type = (t, g, p) => k.withMinor(() => { k.noise(t, { dur: 0.016, filter: 'bandpass', f: 2300, q: 1.6, gain: 0.2 * g, a: 0.001, pan: p }); k.tone(t, { f: 240, dur: 0.03, gain: 0.05 * g, pan: p }); });
331
+ // scroll: wheel notches following the eased scroll + a smooth low page glide
332
+ const scroll = (t0, t1, dist) => {
333
+ k.noise(t0, { dur: t1 - t0 + 0.1, color: 'pink', filter: 'bandpass', f: [[0, 260], [(t1 - t0) * 0.5, 520], [t1 - t0 + 0.1, 240]], q: 0.7, gain: 0.09, a: (t1 - t0) * 0.45 });
334
+ for (let s = 120; s <= dist; s += 120) {
335
+ const tn = when(t0, t1, H.ease.inOut, s / dist);
336
+ k.withMinor(() => { k.noise(tn, { dur: 0.012, filter: 'bandpass', f: 1900, q: 2, gain: 0.22, a: 0.001, pan: 0.35 }); k.tone(tn, { f: 330, dur: 0.025, gain: 0.05, pan: 0.35 }); });
337
+ }
338
+ };
339
+ // counter ticks: one per 1/24 of the eased progress, never closer than 1/12 s (slows as it settles)
340
+ const counter = (t0, t1, ease, steps, p, f0) => {
341
+ let last = -1, lastT = -1;
342
+ for (let t = t0; t <= t1 + 1e-6; t += 1 / 120) {
343
+ const v = Math.floor(ease(seg(t, t0, t1)) * steps);
344
+ if (v !== last && t - lastT >= 1 / 12) { type(t, 0.8 + 0.3 * (v / steps), p); last = v; lastT = t; }
345
+ }
346
+ };
347
+
348
+ k.bed('room', 0, D, { gain: 0.35 });
349
+
350
+ // ---- panel 0: the rule draws, the headline sets
351
+ k.noise(0.0, { dur: 0.5, filter: 'bandpass', f: [[0, 500], [0.5, 900]], q: 0.8, gain: 0.06, a: 0.1, pan: -0.1, minor: true }); // rule runs across
352
+ k.play('tock', 0.42, { f: 300, gain: 0.45 }); // headline settles into place
353
+ k.play('tock', 0.95, { f: 380, gain: 0.22, minor: true }); // dek + byline
354
+ scroll(1.5, 2.4, 720);
355
+ k.play('click', 2.0, { f: 900, gain: 0.8, minor: true }); // sticky header docks
356
+
357
+ // ---- panel 1: the data
358
+ k.play('tock', 3.15, { f: 340, gain: 0.3, pan: pan(1000), minor: true }); // the model box fades in
359
+ pen(3.0, 3.25, 1, pan(140)); // "cat"
360
+ pen(3.3, 3.7, 0.8, pan(200)); // cat leader
361
+ pen(3.4, 3.6, 0.9, pan(380)); // "dog"
362
+ pen(3.6, 4.0, 0.9, pan(600)); // dog leader + the flow leader into the model
363
+ counter(2.6, 5.3, H.ease.inOut, 24, pan(930));
364
+ k.play('tock', 5.3, { f: 260, gain: 0.6, pan: pan(930) }); // 1,000,000 sets
365
+ scroll(5.9, 6.6, 720);
366
+ k.play('click', 6.2, { f: 900, gain: 0.7, pan: 0.6, minor: true }); // header tab: The loop
367
+
368
+ // ---- panel 2: guess, check, adjust
369
+ k.play('tock', 6.82, { f: 320, gain: 0.3, pan: pan(170), minor: true }); // a new photo
370
+ pen(6.9, 7.3, 0.8, pan(300)); // photo -> model
371
+ pen(7.2, 7.5, 0.8, pan(800)); // model -> output
372
+ pen(7.4, 7.71, 1.1, pan(950)); // "dog?"
373
+ // the wrong guess: claret X in two quick slashes + a muted descending pair
374
+ pen(7.68, 7.77, 2.0, pan(1110), false); // the X appears in one quick double slash
375
+ k.tone(7.68, { f: 'D4', type: 'triangle', dur: 0.24, gain: 0.2, hold: 0.08, filter: 'lowpass', f2: 1200, pan: pan(1100) });
376
+ k.tone(7.86, { f: 'A3', type: 'triangle', dur: 0.4, gain: 0.2, hold: 0.12, filter: 'lowpass', f2: 900, pan: pan(1100) });
377
+ pen(7.78, 8.1, 1.1, pan(960)); // emphasis ring (drawing since 7.7)
378
+ k.play('click', 8.08, { f: 900, gain: 0.7, pan: -0.5, minor: true }); // step: Check
379
+ k.play('tock', 8.15, { f: 240, gain: 0.55, pan: pan(970) }); // "Answer: cat" tag sets
380
+ pen(8.3, 9.1, 0.55, pan(800)); // error line dashes back
381
+ pen(8.5, 9.0, 0.7, pan(900)); // "error flows back"
382
+ k.noise(9.1, { dur: 0.03, filter: 'bandpass', f: 1300, q: 1.2, gain: 0.18, a: 0.001, pan: pan(570), minor: true }); // arrowhead dot
383
+ k.play('click', 8.9, { f: 900, gain: 0.7, pan: -0.3, minor: true }); // step: Adjust
384
+ for (let i = 1; i <= 6; i++) k.play('tock', when(8.9, 9.6, H.ease.inOut, i / 6), { f: 520 + i * 25, gain: 0.42, pan: pan(570), minor: true }); // weights turn
385
+ pen(8.95, 9.4, 0.5, pan(600)); // "weights adjust"
386
+ pen(9.7, 10.01, 1.1, pan(950)); // "cat"
387
+ pen(10.0, 10.12, 1.3, pan(1050), false); // green tick
388
+ k.play('success', 10.0, { f: 'G5', gain: 0.45, pan: pan(1000) }); // the correction
389
+ pen(10.0, 10.4, 0.6, pan(950)); // "...and round it goes again"
390
+ scroll(10.5, 11.2, 720);
391
+ k.play('click', 10.85, { f: 900, gain: 0.7, pan: 0.6, minor: true }); // header tab: Practice
392
+
393
+ // ---- panel 3: practice
394
+ pen(11.2, 12.8, 0.45, pan(450)); // the accuracy line draws
395
+ pen(11.3, 11.8, 0.6, pan(200)); // "starts at 61%"
396
+ pen(11.6, 11.9, 0.5, pan(110)); // its leader
397
+ counter(11.2, 12.8, H.ease.inOut, 36, pan(1060));
398
+ k.play('impact', 12.8, { gain: 0.8, pan: pan(1060) }); // 97% lands: the big hit
399
+ k.play('ding', 12.82, { f: 'C5', gain: 0.5, pan: pan(1060) });
400
+ pen(12.8, 13.3, 1.3, pan(1060), false); // the ring around 97%
401
+ // takeaway sets (ease.out: mostly there by ~13.45), then resolves
402
+ k.noise(13.2, { dur: 0.3, filter: 'bandpass', f: [[0, 400], [0.3, 800]], q: 0.8, gain: 0.07, a: 0.08, pan: -0.6, minor: true }); // claret rule
403
+ k.play('tock', 13.3, { f: 280, gain: 0.5 });
404
+ ['F4', 'C5', 'A5'].forEach((f, i) => k.play('ding', 13.45 + i * 0.08, { f, gain: 0.4 - i * 0.07, pan: (i - 1) * 0.25 }));
405
+ },
406
+ explainerDuration: 15,
407
+ explainerNote: 'Told as a scrolling newspaper story: the page scrolls panel to panel under a sticky serif header, hand-drawn Caveat arrows and rings carry the cause and emphasis, a waffle chart and set numerals count the data, and the accuracy line draws itself like the still.',
408
+ explainer(ctx, W, H_, t) {
409
+ const S = (a, b) => seg(t, a, b);
410
+ ctx.fillStyle = BG; ctx.fillRect(0, 0, W, H_);
411
+ // ---- scroll position (the transition: the page scrolls like an online graphic) ----
412
+ const KEYS = [[0, 0], [1.5, 0], [2.4, 720], [5.9, 720], [6.6, 1440], [10.5, 1440], [11.2, 2160], [15, 2160]];
413
+ let sy = 2160;
414
+ for (let i = 0; i < KEYS.length - 1; i++) if (t >= KEYS[i][0] && t <= KEYS[i + 1][0]) { sy = H.lerp(KEYS[i][1], KEYS[i + 1][1], H.ease.inOut((t - KEYS[i][0]) / (KEYS[i + 1][0] - KEYS[i][0]))); break; }
415
+ const L = 70;
416
+ ctx.save(); ctx.translate(0, -sy);
417
+ ctx.textAlign = 'left'; ctx.textBaseline = 'alphabetic';
418
+ const head = (n, title, T0) => {
419
+ // each panel opens with the hero's masthead rule: 3px ink with a claret tab
420
+ ctx.fillStyle = INK; ctx.fillRect(L, T0 + 78, 1140, 3); ctx.fillStyle = HI; ctx.fillRect(L, T0 + 78, 46, 7);
421
+ smallCaps(ctx, n, L, T0 + 112, 22, HI, 2); ctx.fillStyle = INK; ctx.font = '700 46px "Libre Baskerville"'; ctx.fillText(title, L, T0 + 166); };
422
+
423
+ // ===== panel 0: the hook =====
424
+ {
425
+ const a = H.ease.out(S(0, 0.6));
426
+ ctx.fillStyle = INK; ctx.fillRect(L, 210, 1140 * H.ease.out(S(0, 0.5)), 4);
427
+ ctx.fillStyle = HI; ctx.fillRect(L, 210, 60, 9);
428
+ ctx.globalAlpha = a; smallCaps(ctx, 'Artificial Intelligence', L, 270, 24, HI, 2);
429
+ ctx.fillStyle = INK; ctx.font = '700 92px "Libre Baskerville"'; ctx.fillText('How does AI learn?', L, 390 + (1 - a) * 14);
430
+ ctx.globalAlpha = H.ease.out(S(0.4, 1.0));
431
+ ctx.fillStyle = MUTED; ctx.font = '400 30px "Libre Baskerville"'; ctx.fillText('A visual guide to how machines get better by guessing', L, 452);
432
+ smallCaps(ctx, 'By the Data Desk', L, 512, 20, MUTED, 1.4);
433
+ ctx.globalAlpha = 1;
434
+ }
435
+
436
+ // ===== panel 1: the data (top at 720) =====
437
+ {
438
+ const T0 = 720;
439
+ head('1. The data', 'It starts with labelled examples', T0);
440
+ // waffle: each square = 5,000 examples; claret = cat, grey = dog
441
+ const cols = 20, rows = 10, sq = 25, gp = 4, wx = L, wy = T0 + 260;
442
+ const filled = Math.floor(cols * rows * H.ease.inOut(S(2.6, 5.3)));
443
+ const wr = H.rng(31);
444
+ let catCell = null, dogCell = null;
445
+ for (let i = 0; i < cols * rows; i++) {
446
+ const c = i % cols, r = Math.floor(i / cols), isCat = wr.chance(0.52);
447
+ const x = wx + c * (sq + gp), y = wy + r * (sq + gp);
448
+ if (i === 24) catCell = [x + sq / 2, y + sq / 2]; if (i === 47) dogCell = [x + sq / 2, y + sq / 2];
449
+ ctx.fillStyle = i < filled ? (isCat ? H.rgba(HI, 0.85) : '#B9AFA5') : 'rgba(51,48,46,0.06)';
450
+ ctx.fillRect(x, y, sq, sq);
451
+ }
452
+ // direct labels with hand-drawn leaders landing on a cat square and a dog square
453
+ ctx.font = '700 36px "Caveat"';
454
+ const la = S(3.0, 3.5);
455
+ if (la > 0 && catCell) {
456
+ catIcon(ctx, wx + 22, wy - 36, 1.15 * H.ease.back(la), HI);
457
+ ctx.fillStyle = HI; writeOn(ctx, 'cat', wx + 50, wy - 26, la * 2);
458
+ leader(ctx, wx + 104, wy - 32, wx + 150, wy - 30, catCell[0] + 2, catCell[1] - 9, 3, HI, S(3.3, 3.7), 2);
459
+ dogIcon(ctx, wx + 270, wy - 36, 1.15 * H.ease.back(S(3.4, 3.8)), MUTED);
460
+ ctx.fillStyle = MUTED; writeOn(ctx, 'dog', wx + 300, wy - 26, S(3.4, 3.8) * 2);
461
+ leader(ctx, wx + 312, wy - 12, wx + 260, wy + 10, dogCell[0] + 5, dogCell[1] - 8, 5, MUTED, S(3.6, 4.0), 2);
462
+ }
463
+ // the counter: hero numeral in set type
464
+ const n = Math.round(1e6 * H.ease.inOut(S(2.6, 5.3)));
465
+ const nx = 800;
466
+ ctx.fillStyle = INK; ctx.font = '700 84px "Libre Baskerville"'; ctx.fillText(n.toLocaleString('en-US'), nx - 20, T0 + 300);
467
+ ctx.font = '400 26px "Inter"'; ctx.fillStyle = MUTED; ctx.fillText('labelled examples', nx - 16, T0 + 340);
468
+ ctx.font = '400 28px "Inter"'; ctx.fillText('each square = 5,000', wx, wy + rows * (sq + gp) + 34);
469
+ // the model box and the flow of examples into it
470
+ const mx = 800, my = T0 + 400, mw = 400, mh = 220;
471
+ const ma = S(3.0, 3.5);
472
+ ctx.globalAlpha = ma;
473
+ ctx.strokeStyle = INK; ctx.lineWidth = 2; ctx.strokeRect(mx, my, mw, mh);
474
+ smallCaps(ctx, 'The model', mx + 14, my + 38, 26, INK, 1.6);
475
+ miniNet(ctx, mx + 70, my + 52, 260, 156, [0.1, -0.2, 0.15, 0.05, -0.1, 0.2, 0.1, -0.05, 0.12, 0.1, -0.15, 0.08, 0.2, -0.1, 0.05, 0.1, 0.07, -0.12, 0.1, 0.02], false, 10);
476
+ ctx.globalAlpha = 1;
477
+ const p0 = [wx + cols * (sq + gp) + 10, wy + 210], p1 = [mx - 8, my + mh / 2], pc = [(p0[0] + p1[0]) / 2, wy + 330];
478
+ leader(ctx, p0[0], p0[1], pc[0], pc[1], p1[0], p1[1], 7, INK, S(3.4, 4.0), 2);
479
+ const fl = S(4.0, 6.2);
480
+ if (fl > 0 && fl < 1) for (let k = 0; k < 6; k++) {
481
+ const u = ((t * 0.9 + k / 6) % 1);
482
+ const a = (1 - u) * (1 - u), b = 2 * u * (1 - u), c = u * u;
483
+ const x = a * p0[0] + b * pc[0] + c * p1[0], y = a * p0[1] + b * pc[1] + c * p1[1];
484
+ ctx.fillStyle = k % 2 ? H.rgba(HI, 0.85) : '#B9AFA5'; ctx.fillRect(x - 8, y - 8, 16, 16);
485
+ }
486
+ }
487
+
488
+ // ===== panel 2: guess, check, adjust (top at 1440) =====
489
+ {
490
+ const T0 = 1440;
491
+ head('2. Guess, check, adjust', 'It learns from its mistakes', T0);
492
+ // step strip
493
+ const steps = ['Guess', 'Check', 'Adjust'];
494
+ const active = t < 8.1 ? 0 : t < 8.9 ? 1 : t < 9.6 ? 2 : 0;
495
+ let sx = L;
496
+ steps.forEach((st, i) => {
497
+ const on = t > 6.8 && i === active;
498
+ ctx.font = '700 34px "Libre Baskerville"'; ctx.fillStyle = on ? HI : 'rgba(51,48,46,0.35)';
499
+ ctx.fillText(st, sx, T0 + 232); const w = ctx.measureText(st).width;
500
+ if (on) ctx.fillRect(sx, T0 + 244, w, 3.5);
501
+ sx += w + 22;
502
+ if (i < 2) { ctx.fillStyle = 'rgba(51,48,46,0.45)'; ctx.fillText('→', sx, T0 + 232); sx += 58; }
503
+ });
504
+ // new photo card
505
+ const ca = H.ease.out(S(6.7, 7.1));
506
+ const cx = L, cy = T0 + 330;
507
+ ctx.globalAlpha = ca;
508
+ ctx.strokeStyle = INK; ctx.lineWidth = 2; ctx.strokeRect(cx, cy, 210, 210);
509
+ catIcon(ctx, cx + 105, cy + 112, 3.8, INK);
510
+ ctx.font = '400 24px "Inter"'; ctx.fillStyle = MUTED; ctx.fillText('A new photo', cx, cy + 244);
511
+ ctx.globalAlpha = 1;
512
+ // model
513
+ const mx = 380, my = T0 + 320, mw = 380, mh = 240;
514
+ const adj = H.ease.inOut(S(8.9, 9.6));
515
+ const w0 = [0.1, -0.2, 0.15, 0.05, -0.1, 0.2, 0.1, -0.05, 0.12, 0.1, -0.15, 0.08, 0.2, -0.1, 0.05, 0.1, 0.07, -0.12, 0.1, 0.02];
516
+ const w1 = [0.5, -0.1, 0.05, 0.6, -0.4, 0.1, 0.45, -0.3, 0.05, 0.7, -0.05, 0.3, 0.1, -0.5, 0.6, 0.05, 0.55, -0.1, 0.65, 0.1];
517
+ const ww = w0.map((v, i) => H.lerp(v, w1[i], adj));
518
+ ctx.strokeStyle = INK; ctx.lineWidth = 2; ctx.strokeRect(mx, my, mw, mh);
519
+ smallCaps(ctx, 'The model', mx + 14, my + 38, 26, INK, 1.6);
520
+ miniNet(ctx, mx + 60, my + 54, 260, 170, ww, t > 8.9 && t < 9.7, 10);
521
+ leader(ctx, cx + 222, cy + 105, (cx + 222 + mx) / 2, cy + 80, mx - 8, my + mh / 2, 12, INK, S(6.9, 7.3), 2);
522
+ // output
523
+ const ox = 850, oy = T0 + 450;
524
+ leader(ctx, mx + mw + 8, my + mh / 2, ox - 40, oy - 40, ox - 12, oy - 14, 14, INK, S(7.2, 7.5), 2);
525
+ ctx.font = '700 70px "Caveat"';
526
+ const g1 = S(7.4, 7.8) * (1 - S(9.5, 9.7));
527
+ if (g1 > 0) {
528
+ ctx.fillStyle = INK; writeOn(ctx, '“dog?”', ox, oy, g1 * 1.3);
529
+ if (g1 > 0.7) { ctx.strokeStyle = HI; ctx.lineWidth = 5; ctx.lineCap = 'round'; const x = ox + 196, y = oy - 46; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + 34, y + 34); ctx.moveTo(x + 34, y); ctx.lineTo(x, y + 34); ctx.stroke(); }
530
+ handRing(ctx, ox + 112, oy - 22, 150, 56, S(7.7, 8.1), 21, HI, 3);
531
+ }
532
+ // check: the truth tag + the error signal flowing back
533
+ const ck = S(8.1, 8.4) * (1 - S(9.5, 9.7));
534
+ if (ck > 0) {
535
+ ctx.globalAlpha = ck;
536
+ ctx.fillStyle = HI; ctx.fillRect(ox, oy + 50, 230, 50);
537
+ ctx.fillStyle = BG; ctx.font = '600 28px "Inter"'; ctx.fillText('Answer: cat', ox + 18, oy + 85);
538
+ ctx.globalAlpha = 1;
539
+ const ep = S(8.3, 9.1);
540
+ if (ep > 0) {
541
+ ctx.save(); ctx.strokeStyle = HI; ctx.lineWidth = 3.4; ctx.setLineDash([10, 8]); ctx.lineDashOffset = t * 50;
542
+ ctx.beginPath();
543
+ const n = Math.floor(40 * ep);
544
+ for (let k = 0; k <= n; k++) { const u = k / 40, x = H.lerp(ox + 110, mx + mw / 2, u), y = H.lerp(oy - 80, my - 14, u) - Math.sin(u * Math.PI) * 50; k ? ctx.lineTo(x, y) : ctx.moveTo(x, y); }
545
+ ctx.stroke(); ctx.restore();
546
+ if (ep >= 1) { const ex = mx + mw / 2, ey = my - 14; ctx.fillStyle = HI; ctx.beginPath(); ctx.moveTo(ex, ey + 10); ctx.lineTo(ex - 9, ey - 6); ctx.lineTo(ex + 9, ey - 8); ctx.closePath(); ctx.fill(); }
547
+ ctx.font = '700 38px "Caveat"'; ctx.fillStyle = HI; writeOn(ctx, 'error flows back', ox - 10, my - 26, S(8.5, 9.0));
548
+ }
549
+ }
550
+ if (t > 8.9 && t < 9.9) { ctx.font = '700 38px "Caveat"'; ctx.fillStyle = HI; writeOn(ctx, 'weights adjust', mx + 90, my + mh + 50, S(8.95, 9.4)); }
551
+ const g2 = S(9.7, 10.1);
552
+ if (g2 > 0) {
553
+ ctx.font = '700 70px "Caveat"'; ctx.fillStyle = INK; writeOn(ctx, '“cat”', ox, oy, g2 * 1.3);
554
+ if (g2 > 0.75) { ctx.strokeStyle = '#2E7D4F'; ctx.lineWidth = 6; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; const x = ox + 168, y = oy - 30; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + 12, y + 15); ctx.lineTo(x + 40, y - 22); ctx.stroke(); }
555
+ ctx.font = '700 36px "Caveat"'; ctx.fillStyle = MUTED; writeOn(ctx, '…and round it goes again', ox - 10, oy + 80, S(10.0, 10.4));
556
+ }
557
+ }
558
+
559
+ // ===== panel 3: progress + takeaway (top at 2160) =====
560
+ {
561
+ const T0 = 2160;
562
+ head('3. Practice', 'Millions of loops later, it rarely misses', T0);
563
+ const cl = L, cr = 880, ct = T0 + 230, cb = T0 + 560;
564
+ const Y = (v) => cb - (v - 50) / 50 * (cb - ct), X = (u) => cl + u * (cr - cl);
565
+ ctx.font = '400 27px "Inter"';
566
+ for (let v = 50; v <= 100; v += 10) {
567
+ ctx.fillStyle = v === 50 ? INK : GRID; ctx.fillRect(cl, Math.round(Y(v)), cr - cl + 20, v === 50 ? 1.6 : 1);
568
+ if (v === 100) { ctx.fillStyle = MUTED; ctx.fillText('100% accuracy', cl, Y(v) - 9); }
569
+ // numeric ticks hang in the left margin, centred on their gridline, so the curve start never runs through "60"
570
+ else if (v > 50) { ctx.fillStyle = MUTED; ctx.textAlign = 'right'; ctx.fillText(String(v), cl - 9, Y(v) + 9); ctx.textAlign = 'left'; }
571
+ }
572
+ ctx.fillStyle = MUTED; ctx.textAlign = 'right'; ctx.font = '400 28px "Inter"'; ctx.fillText('Training rounds →', cr + 20, cb + 36); ctx.textAlign = 'left';
573
+ const acc = (u) => 61 + 36 * (1 - Math.exp(-u * 4.2)) / (1 - Math.exp(-4.2)) + Math.sin(u * 40) * 1.2 * (1 - u);
574
+ const rp = H.ease.inOut(S(11.2, 12.8));
575
+ const g = ctx.createLinearGradient(0, ct, 0, cb); g.addColorStop(0, 'rgba(153,15,61,0.18)'); g.addColorStop(1, 'rgba(153,15,61,0.03)');
576
+ const N = 120, n = Math.floor(N * rp);
577
+ if (n > 1) {
578
+ ctx.fillStyle = g; ctx.beginPath(); ctx.moveTo(X(0), cb);
579
+ for (let k = 0; k <= n; k++) ctx.lineTo(X(k / N), Y(acc(k / N))); ctx.lineTo(X(n / N), cb); ctx.closePath(); ctx.fill();
580
+ ctx.strokeStyle = HI; ctx.lineWidth = 5; ctx.lineJoin = 'round'; ctx.beginPath();
581
+ for (let k = 0; k <= n; k++) (k ? ctx.lineTo(X(k / N), Y(acc(k / N))) : ctx.moveTo(X(0), Y(acc(0)))); ctx.stroke();
582
+ const hx = X(n / N), hy = Y(acc(n / N));
583
+ ctx.fillStyle = HI; ctx.beginPath(); ctx.arc(hx, hy, 7, 0, 7); ctx.fill();
584
+ }
585
+ ctx.font = '700 36px "Caveat"'; ctx.fillStyle = MUTED;
586
+ writeOn(ctx, 'starts at 61%', X(0.06), Y(53.5), S(11.3, 11.8));
587
+ leader(ctx, X(0.055), Y(56.2), X(0.02), Y(57.2), X(0.009), Y(60.4), 41, MUTED, S(11.6, 11.9), 2);
588
+ // hero numeral at right + emphasis ring
589
+ const big = rp >= 1 ? 97 : Math.round(61 + 36 * rp);
590
+ ctx.globalAlpha = S(11.2, 11.5);
591
+ ctx.fillStyle = HI; ctx.font = '700 128px "Libre Baskerville"'; ctx.fillText(big + '%', 920, T0 + 400);
592
+ ctx.font = '400 28px "Inter"'; ctx.fillStyle = MUTED; ctx.fillText('accuracy', 928, T0 + 462);
593
+ ctx.globalAlpha = 1;
594
+ handRing(ctx, 1062, T0 + 354, 158, 68, S(12.8, 13.3), 77, HI, 3.4);
595
+ // takeaway
596
+ const tk = H.ease.out(S(13.2, 13.8));
597
+ ctx.globalAlpha = tk;
598
+ ctx.fillStyle = HI; ctx.fillRect(L, T0 + 610, 80 * tk, 5);
599
+ ctx.fillStyle = INK; ctx.font = '700 50px "Libre Baskerville"'; ctx.fillText('More examples → better guesses', L, T0 + 676 + (1 - tk) * 8);
600
+ ctx.globalAlpha = 1;
601
+ }
602
+ ctx.restore();
603
+
604
+ // ---- sticky header (docks as the headline scrolls away) ----
605
+ const hd = S(1.7, 2.2);
606
+ if (hd > 0) {
607
+ ctx.save();
608
+ // the header's paper backing turns opaque faster than its type fades in, so the scrolling byline never shows through the title
609
+ ctx.globalAlpha = Math.min(1, hd * 2.5); ctx.fillStyle = BG; ctx.fillRect(0, 0, W, 66);
610
+ ctx.globalAlpha = hd;
611
+ ctx.fillStyle = 'rgba(51,48,46,0.35)'; ctx.fillRect(0, 66, W, 1);
612
+ ctx.fillStyle = INK; ctx.font = '700 28px "Libre Baskerville"'; ctx.textAlign = 'left'; ctx.fillText('How does AI learn?', L, 44);
613
+ const secs = ['Data', 'The loop', 'Practice'];
614
+ const cur = t < 6.2 ? 0 : t < 10.85 ? 1 : 2;
615
+ let x = 1210;
616
+ ctx.font = '600 22px "Inter"';
617
+ for (let i = secs.length - 1; i >= 0; i--) {
618
+ const w = ctx.measureText(secs[i]).width; x -= w;
619
+ ctx.fillStyle = i === cur ? HI : 'rgba(51,48,46,0.42)'; ctx.fillText(secs[i], x, 42);
620
+ if (i === cur) ctx.fillRect(x, 50, w, 3);
621
+ x -= 30;
622
+ }
623
+ ctx.restore();
624
+ // printed-graphic footer: small-caps source line pinned bottom right, as in the still
625
+ ctx.save(); ctx.globalAlpha = hd;
626
+ ctx.font = '600 19px "Inter"';
627
+ smallCaps(ctx, 'Source: Illustrative', 1210, 704, 19, MUTED, 1.4, 'right');
628
+ ctx.restore();
629
+ }
630
+ H.grain(ctx, W, H_, 0.025, 5 + Math.floor(t * 12));
631
+ },
632
+ });
633
+ }