@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": 48,
3
+ "name": "Transit Map",
4
+ "nameZh": "公共交通线路图",
5
+ "family": "Data & Information",
6
+ "familyZh": "数据与信息",
7
+ "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.",
8
+ "motionNotes": "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.",
9
+ "explainerNotes": "Explainer: 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.",
10
+ "code": "code.js",
11
+ "registeredAs": "transit-map",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }
@@ -0,0 +1,559 @@
1
+ {
2
+ // ---------------------------------------------------------------
3
+ // Gouache travel poster — flat opaque matte shapes, dry-brush edges
4
+ // ---------------------------------------------------------------
5
+ const PAL = {
6
+ navy: [30, 52, 66], teal: [47, 127, 131], orange: [224, 97, 43],
7
+ mustard: [242, 180, 64], cream: [243, 231, 204], rose: [234, 165, 138],
8
+ };
9
+ const tint = (c, k) => H.mix(c, PAL.cream, k);
10
+
11
+ // anisotropic value noise field (0..1): long cells along the brush direction, short across it
12
+ function field(W, H_, cx, cy, seed) {
13
+ const r = H.rng(seed);
14
+ const gw = Math.ceil(W / cx) + 2, gh = Math.ceil(H_ / cy) + 2, g = new Float32Array(gw * gh);
15
+ for (let i = 0; i < g.length; i++) g[i] = r();
16
+ const out = new Float32Array(W * H_);
17
+ for (let y = 0; y < H_; y++) {
18
+ const fy = y / cy, iy = fy | 0, ty = fy - iy, sy = ty * ty * (3 - 2 * ty);
19
+ for (let x = 0; x < W; x++) {
20
+ const fx = x / cx, ix = fx | 0, tx = fx - ix, sx = tx * tx * (3 - 2 * tx);
21
+ const a = g[iy * gw + ix], b = g[iy * gw + ix + 1], c = g[(iy + 1) * gw + ix], d = g[(iy + 1) * gw + ix + 1];
22
+ out[y * W + x] = a + (b - a) * sx + (c - a) * sy + (a - b - c + d) * sx * sy;
23
+ }
24
+ }
25
+ return out;
26
+ }
27
+
28
+
29
+ // =================================================================
30
+ // EXPLAINER — "How does AI learn?" as a run of hand-painted posters. Scene changes are a fresh coat of
31
+ // cream gouache swept across the board, then the next poster is painted over it, flat colour by flat colour.
32
+ // =================================================================
33
+ let GXC = null;
34
+ function gpCache(W, H_) {
35
+ if (GXC && GXC.W === W && GXC.H === H_) return GXC;
36
+ GXC = { W, H: H_, hS: field(W, H_, 46, 2.2, 11), hS2: field(W, H_, 120, 6, 12), vS: field(W, H_, 2.4, 40, 13), grain: null, SP: {}, BG: {} };
37
+ // static matte board grain (added to every frame)
38
+ const g = new Float32Array(W * H_), pr = H.rng(4), nz = H.makeNoise(5);
39
+ for (let i = 0; i < W * H_; i++) { const x = i % W, y = (i / W) | 0; g[i] = (pr() - 0.5) * 9 + (GXC.hS2[i] - 0.5) * 4 + nz.n2(x * 0.5, y * 0.5) * 3; }
40
+ GXC.grain = g;
41
+ // texture is split from edges: a mostly isotropic field raggeds the edges, broad brush bands (not 2px streaks)
42
+ // carry the body tone, and a slow lumpy field gives the chalky bloom of dried matte gouache
43
+ GXC.eF = field(W, H_, 7, 5, 14);
44
+ GXC.bH = field(W, H_, 90, 9, 15); GXC.bV = field(W, H_, 9, 90, 16);
45
+ const ch = field(W, H_, 26, 22, 17), cm = new Float32Array(W * H_);
46
+ for (let i = 0; i < cm.length; i++) { const u = H.clamp((ch[i] - 0.6) / 0.25); cm[i] = 1 + 0.03 * u * u * (3 - 2 * u); }
47
+ GXC.chalk = cm;
48
+ GXC.S = H.layer(W, H_); GXC.B = H.layer(W, H_);
49
+ GXC.S.getContext('2d', { willReadFrequently: true }); GXC.B.getContext('2d', { willReadFrequently: true }); // CPU-backed: cheap readback
50
+ return GXC;
51
+ }
52
+
53
+ // paint(): one opaque colour, one pass of the brush. Mask -> blurred mask -> ragged coverage, then a matte body
54
+ // with broad stroke bands, a slightly darker dried ridge just inside the edge and chalky bloom.
55
+ // dir: 'h' / 'v' brushing, 'd' diagonal (sheared) brushing for slopes. key: cache a finished (p = 1) static pass.
56
+ function makePainter(buf, W, H_, Cx, defEdge, defRag) {
57
+ const { hS, hS2, vS, eF, bH, bV, chalk, SP } = Cx, sx = Cx.S.getContext('2d'), bx = Cx.B.getContext('2d');
58
+ return function paint(col, bb, drawFn, { edge = defEdge, rag = defRag, dir = 'h', tex = 0.07, p = 1, key = null } = {}) {
59
+ if (p <= 0) return;
60
+ if (key && p >= 1 && SP[key]) {
61
+ const { idx, cv, cov } = SP[key];
62
+ for (let j = 0, n = idx.length; j < n; j++) { const o = idx[j] * 4, c = cov[j], q = j * 3; buf[o] += (cv[q] - buf[o]) * c; buf[o + 1] += (cv[q + 1] - buf[o + 1]) * c; buf[o + 2] += (cv[q + 2] - buf[o + 2]) * c; }
63
+ return;
64
+ }
65
+ const rec = key && p >= 1 ? { idx: [], cv: [], cov: [] } : null;
66
+ let [x0, y0, x1, y1] = bb;
67
+ x0 = Math.max(0, Math.floor(x0 - edge * 2)); y0 = Math.max(0, Math.floor(y0 - edge * 2));
68
+ x1 = Math.min(W, Math.ceil(x1 + edge * 2)); y1 = Math.min(H_, Math.ceil(y1 + edge * 2));
69
+ const w = x1 - x0, h = y1 - y0; if (w <= 0 || h <= 0) return;
70
+ sx.clearRect(x0, y0, w, h); sx.save(); sx.fillStyle = '#fff'; sx.strokeStyle = '#fff'; drawFn(sx); sx.restore();
71
+ bx.clearRect(x0, y0, w, h); bx.filter = `blur(${edge}px)`; bx.drawImage(Cx.S, x0, y0, w, h, x0, y0, w, h); bx.filter = 'none';
72
+ const A = bx.getImageData(x0, y0, w, h).data, F = dir === 'v' ? vS : hS, BD = dir === 'v' ? bV : bH, diag = dir === 'd';
73
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
74
+ const k = (y * w + x) * 4, b = A[k + 3] / 255;
75
+ if (b < 0.02) continue;
76
+ const gi = (y + y0) * W + (x + x0);
77
+ const si = diag ? ((y + y0 + (((x + x0) * 0.42) | 0)) % H_) * W + (x + x0) : gi; // sheared lookup = diagonal strokes
78
+ const n = F[si];
79
+ const thr = Math.min(0.88, 0.5 + ((eF[gi] - 0.5) * 0.65 + (n - 0.5) * 0.35) * rag * 0.7);
80
+ let cov = (b - thr) / 0.08 + 0.5; cov = cov < 0 ? 0 : cov > 1 ? 1 : cov;
81
+ if (cov <= 0) continue;
82
+ if (p < 1) {
83
+ // the brush is mid-sweep: left->right for horizontal brushing, bottom->top for vertical; ragged dry front
84
+ const sw = dir === 'v' ? 1 - y / h : x / w, front = p * 1.15 - sw + (n - 0.5) * 0.1;
85
+ if (front <= 0) continue;
86
+ cov *= Math.min(1, front / 0.035);
87
+ }
88
+ let v = 1 + (hS2[gi] - 0.5) * tex * 0.65 + (BD[si] - 0.5) * tex * 0.5 + (n - 0.5) * tex * 0.25;
89
+ const rim = b - thr; if (rim < 0.3) v *= 1 - 0.06 * (1 - rim / 0.3); // paint piles up a touch at the edge
90
+ v *= chalk[gi];
91
+ const o = gi * 4, r0 = col[0] * v, g0 = col[1] * v, b0 = col[2] * v;
92
+ buf[o] += (r0 - buf[o]) * cov; buf[o + 1] += (g0 - buf[o + 1]) * cov; buf[o + 2] += (b0 - buf[o + 2]) * cov;
93
+ if (rec) { rec.idx.push(gi); rec.cv.push(r0, g0, b0); rec.cov.push(cov); }
94
+ }
95
+ if (rec) SP[key] = { idx: Int32Array.from(rec.idx), cv: Float32Array.from(rec.cv), cov: Float32Array.from(rec.cov) };
96
+ };
97
+ }
98
+
99
+ function explainGouache(ctx, W, H_, t) {
100
+ const { navy, teal, orange, mustard, cream, rose } = PAL;
101
+ const Cx = gpCache(W, H_), { grain } = Cx;
102
+ const buf = new Uint8ClampedArray(W * H_ * 4);
103
+ for (let i = 0; i < buf.length; i += 4) { buf[i] = cream[0]; buf[i + 1] = cream[1]; buf[i + 2] = cream[2]; buf[i + 3] = 255; }
104
+ const paint = makePainter(buf, W, H_, Cx, 2.5, 0.8);
105
+ const G = (a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
106
+ const poly = (c, pts) => { c.beginPath(); pts.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); c.fill(); };
107
+ const txt = (c, s, x, y, size, align = 'left', font = 'Bebas Neue', sp = 3) => { c.font = `400 ${size}px "${font}"`; c.textAlign = align; c.textBaseline = 'alphabetic'; if (c.letterSpacing !== undefined) c.letterSpacing = sp + 'px'; c.fillText(s, x, y); };
108
+ const fmt = (n) => Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
109
+ // flat poster animals
110
+ const head = (c, x, y, s, dog) => {
111
+ if (dog) { c.beginPath(); c.ellipse(x, y, s * 0.36, s * 0.34, 0, 0, 7); c.fill(); }
112
+ else poly(c, [[x - s * 0.4, y - s * 0.05], [x - s * 0.38, y - s * 0.46], [x - s * 0.14, y - s * 0.26], [x + s * 0.14, y - s * 0.26], [x + s * 0.38, y - s * 0.46], [x + s * 0.4, y - s * 0.05], [x + s * 0.3, y + s * 0.26], [x, y + s * 0.36], [x - s * 0.3, y + s * 0.26]]);
113
+ };
114
+ const ears = (c, x, y, s) => { for (const k of [-1, 1]) { c.beginPath(); c.ellipse(x + k * s * 0.36, y + s * 0.02, s * 0.11, s * 0.24, k * 0.3, 0, 7); c.fill(); } };
115
+ const eyes = (c, x, y, s) => { c.beginPath(); c.arc(x - s * 0.13, y - s * 0.02, s * 0.05, 0, 7); c.arc(x + s * 0.13, y - s * 0.02, s * 0.05, 0, 7); c.fill(); poly(c, [[x - s * 0.06, y + s * 0.12], [x + s * 0.06, y + s * 0.12], [x, y + s * 0.19]]); };
116
+ // a whole labelled card, painted colour by colour (each colour is one brush pass)
117
+ // key: cache each finished pass (only for a card set that no longer moves)
118
+ function cards(list, bb, p = 1, key = null) {
119
+ const K = (i) => (key ? key + i : null);
120
+ paint(cream, bb, (c) => list.forEach(({ x, y, s }) => c.fillRect(x - s / 2, y - s / 2, s, s)), { edge: 1.4, rag: 0.5, p, key: K(1) });
121
+ paint(orange, bb, (c) => list.forEach(({ x, y, s, dog }) => { if (!dog) head(c, x, y + s * 0.04, s * 0.8, false); }), { edge: 1, rag: 0.4, p, key: K(2) });
122
+ paint(H.mix(rose, navy, 0.25), bb, (c) => list.forEach(({ x, y, s, dog }) => { if (dog) head(c, x, y, s * 0.8, true); }), { edge: 1, rag: 0.4, p, key: K(3) });
123
+ paint(H.mix(orange, navy, 0.55), bb, (c) => list.forEach(({ x, y, s, dog }) => { if (dog) ears(c, x, y, s * 0.8); }), { edge: 1, rag: 0.4, p, key: K(4) });
124
+ paint(navy, bb, (c) => list.forEach(({ x, y, s, dog, tag }) => { eyes(c, x, y + s * 0.02, s * 0.8); if (tag !== false && s > 80) c.fillRect(x - s * 0.36, y + s * 0.5, s * 0.72, s * 0.3); }), { edge: 0.8, rag: 0.3, p, key: K(5) });
125
+ paint(cream, bb, (c) => list.forEach(({ x, y, s, dog, tag }) => { if (tag !== false && s > 80) txt(c, dog ? 'DOG' : 'CAT', x, y + s * 0.74, Math.max(24, Math.round(s * 0.27)), 'center', 'Bebas Neue', 2); }), { edge: 0.6, rag: 0.2, p, key: K(6) });
126
+ }
127
+ // the learning machine (retro factory), with three gauges = its weights
128
+ function machine(x, y, s, p0, gauges, key = null, gStatic = false) {
129
+ const K = (i) => (key ? key + i : null);
130
+ const P = (a) => G(p0 + a, p0 + a + 0.35);
131
+ const bb = [x - 20, y - 260 * s, x + 330 * s, y + 10];
132
+ paint(teal, bb, (c) => { c.fillRect(x, y - 200 * s, 300 * s, 200 * s); poly(c, [[x - 10, y - 200 * s], [x + 150 * s, y - 250 * s], [x + 310 * s, y - 200 * s]]); }, { dir: 'v', p: P(0), key: K('a') });
133
+ paint(H.mix(teal, navy, 0.5), bb, (c) => { c.fillRect(x + 220 * s, y - 200 * s, 80 * s, 200 * s); c.fillRect(x + 236 * s, y - 300 * s, 34 * s, 80 * s); }, { dir: 'v', p: P(0.15), key: K('b') });
134
+ paint(cream, bb, (c) => { for (let k = 0; k < 3; k++) { c.beginPath(); c.arc(x + (50 + k * 64) * s, y - 120 * s, 26 * s, 0, 7); c.fill(); } c.fillRect(x + 6 * s, y - 72 * s, 208 * s, 36 * s); }, { edge: 1.2, rag: 0.4, p: P(0.3), key: K('c') });
135
+ paint(navy, bb, (c) => {
136
+ for (let k = 0; k < 3; k++) { const a = gauges[k], gx = x + (50 + k * 64) * s, gy = y - 120 * s; c.save(); c.translate(gx, gy); c.rotate(a); c.fillRect(-2.5 * s, -22 * s, 5 * s, 24 * s); c.restore(); c.beginPath(); c.arc(gx, gy, 4 * s, 0, 7); c.fill(); }
137
+ txt(c, 'LEARNING MACHINE', x + 110 * s, y - 45 * s, Math.max(24, Math.round(22 * s)), 'center', 'Oswald', 1);
138
+ }, { edge: 0.8, rag: 0.3, p: P(0.45), key: gStatic ? K('d' + gauges.map((a) => a.toFixed(3)).join()) : null });
139
+ paint(orange, bb, (c) => { c.fillRect(x + 236 * s, y - 306 * s, 34 * s, 14 * s); }, { edge: 0.8, rag: 0.3, p: P(0.5), key: K('e') });
140
+ }
141
+ const navyBand = () => paint(navy, [0, 560, W, H_], (c) => c.fillRect(0, 572, W, H_ - 572), { p: G(0.2, 0.7), key: 'band' });
142
+ const bandText = (s, a, size = 96, key = null) => paint(cream, [30, 570, W - 30, 715], (c) => txt(c, s, 52, 680, size), { edge: 1.2, rag: 0.6, p: G(a, a + 0.45), key });
143
+
144
+ const scene = t < 6.35 ? 1 : t < 10.85 ? 2 : 3;
145
+ // ======== scene backgrounds (sky + ground), re-painted for each new poster ========
146
+ const bgStart = scene === 1 ? 0 : scene === 2 ? 6.35 : 10.85;
147
+ const skyCol = scene === 1 ? tint(teal, 0.55) : scene === 2 ? tint(rose, 0.35) : tint(mustard, 0.55);
148
+ // once every background pass of the current poster is finished, the painted board is the same on every frame:
149
+ // it is kept as a snapshot instead of being repainted
150
+ const bgP = [G(bgStart, bgStart + 0.45), G(bgStart + 0.1, bgStart + 0.5), G(bgStart + 0.15, bgStart + 0.55), G(bgStart + 0.2, bgStart + 0.6), G(0.2, 0.7)];
151
+ const bgKey = 's' + scene, bgDone = bgP.every((v) => v >= 1);
152
+ if (bgDone && Cx.BG[bgKey]) buf.set(Cx.BG[bgKey]);
153
+ else {
154
+ paint(skyCol, [0, 0, W, scene === 3 ? 572 : 470], (c) => c.fillRect(0, 0, W, scene === 3 ? 566 : 470), { edge: 4, p: bgP[0] });
155
+ // tonal depth like the still's sky bands and receding ranges (two extra values per poster)
156
+ const ridge = (c, y, amp, ph) => { c.beginPath(); c.moveTo(0, 480); for (let x = 0; x <= W; x += 40) c.lineTo(x, y + Math.sin(x * 0.006 + ph) * amp + Math.sin(x * 0.017 + ph * 2) * amp * 0.35); c.lineTo(W, 480); c.closePath(); c.fill(); };
157
+ if (scene === 1) {
158
+ paint(tint(teal, 0.68), [0, 250, W, 470], (c) => c.fillRect(0, 262, W, 208), { edge: 4, rag: 1, p: bgP[1] });
159
+ paint(tint(teal, 0.42), [0, 330, W, 480], (c) => ridge(c, 392, 26, 0.4), { edge: 3, rag: 1, dir: 'd', p: bgP[2] });
160
+ } else if (scene === 2) {
161
+ paint(tint(rose, 0.58), [0, 250, W, 470], (c) => c.fillRect(0, 268, W, 202), { edge: 4, rag: 1, p: bgP[1] });
162
+ paint(tint(teal, 0.48), [0, 360, W, 480], (c) => ridge(c, 424, 18, 2.1), { edge: 3, rag: 1, dir: 'd', p: bgP[2] });
163
+ } else {
164
+ paint(tint(mustard, 0.36), [0, 0, W, 170], (c) => c.fillRect(0, 0, W, 150), { edge: 4, rag: 1, p: bgP[1] });
165
+ paint(tint(mustard, 0.74), [0, 380, W, 572], (c) => c.fillRect(0, 398, W, 168), { edge: 4, rag: 1, p: bgP[2] });
166
+ }
167
+ if (scene !== 3) paint(scene === 1 ? H.mix(teal, navy, 0.25) : tint(teal, 0.25), [0, 420, W, 572], (c) => { c.beginPath(); c.moveTo(0, 470); c.bezierCurveTo(400, 440, 900, 480, 1280, 450); c.lineTo(1280, 572); c.lineTo(0, 572); c.fill(); }, { edge: 3, p: bgP[3] });
168
+ navyBand();
169
+ if (bgDone) Cx.BG[bgKey] = buf.slice();
170
+ }
171
+
172
+ if (scene === 1) {
173
+ // ---- the machine and its conveyor of labelled examples ----
174
+ machine(900, 470, 1.05, 0.9, [-0.9, 0.2, 1.0], 'm1', true);
175
+ paint(mustard, [0, 440, 900, 500], (c) => { c.fillRect(0, 448, 900, 22); }, { p: G(1.6, 2.1), key: 'belt' });
176
+ paint(navy, [0, 440, 900, 520], (c) => { const off = (t * 60) % 40; for (let x = -40 + off; x < 900; x += 40) c.fillRect(x, 452, 16, 14); for (let x = 30; x < 900; x += 110) { c.beginPath(); c.arc(x, 482, 12, 0, 7); c.fill(); } }, { edge: 1, rag: 0.4, p: G(1.9, 2.3) });
177
+ // hopper on the machine's side
178
+ paint(orange, [790, 330, 910, 470], (c) => poly(c, [[800, 360], [898, 360], [884, 450], [856, 450]]), { dir: 'v', p: G(2.0, 2.3), key: 'hopper' });
179
+ const list = [];
180
+ for (let i = 0; i < 20; i++) { const s0 = 2.4 + i * 0.18, u = (t - s0) / 1.4; if (u <= 0 || u >= 1) continue; const x = -60 + u * 940, lift = x > 800 ? (x - 800) * 0.9 : 0; list.push({ x: Math.min(x, 860), y: 390 - lift, s: x > 800 ? 96 * (1 - (x - 800) / 140) : 96, dog: (i % 2 === 1) !== (i % 5 === 0) }); }
181
+ if (list.length) cards(list, [-120, 260, 960, 470]);
182
+ // counter in the band
183
+ if (t > 2.6) { const n = t > 5.6 ? 1e6 : Math.round(1e6 * H.ease.in(H.clamp((t - 2.7) / 2.9)) / 1000) * 1000; bandText(fmt(n) + ' EXAMPLES', 2.6, 96, n >= 1e6 ? 'bt1' : null); }
184
+ // a flat poster arrow pointing into the hopper with its label
185
+ paint(cream, [380, 200, 860, 330], (c) => { c.fillRect(420, 250, 330, 34); poly(c, [[750, 228], [810, 267], [750, 306]]); }, { p: G(2.6, 3.0), key: 'arr1' });
186
+ paint(navy, [380, 200, 860, 330], (c) => txt(c, 'LABELLED EXAMPLES', 432, 279, 30, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(2.9, 3.2), key: 'arr1t' });
187
+ if (t > 5.6) paint(orange, [30, 570, 900, 700], (c) => c.fillRect(52, 690, 700, 6), { p: G(5.6, 5.9), key: 'ul1' });
188
+ }
189
+ if (scene === 2) {
190
+ const ga = [-0.9, 0.2, 1.0].map((a, k) => a + G(8.6, 9.2) * [0.9, -0.8, -1.3][k]);
191
+ machine(120, 500, 1.15, 6.6, ga, 'm2', t < 8.6 || t > 9.2);
192
+ // the new picture drops in at the top of the board
193
+ cards([{ x: 270, y: 120 + (1 - G(6.9, 7.2)) * -140, s: 110, dog: false, tag: false }], [190, 20, 360, 200], 1, t >= 7.2 ? 'np' : null);
194
+ paint(navy, [340, 80, 700, 170], (c) => txt(c, 'NEW PICTURE', 350, 132, 34, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(7.0, 7.3), key: 'npt' });
195
+ paint(mustard, [250, 170, 300, 215], (c) => poly(c, [[262, 178], [278, 178], [278, 196], [288, 196], [270, 214], [252, 196], [262, 196]]), { dir: 'v', p: G(7.15, 7.35), key: 'npa' });
196
+ // GUESS: output ticket
197
+ const second = t > 9.4;
198
+ paint(mustard, [470, 300, 820, 380], (c) => { c.fillRect(480, 318, 240, 40); poly(c, [[720, 300], [790, 338], [720, 376]]); }, { p: G(7.1, 7.4), key: 'gt' });
199
+ paint(navy, [470, 300, 820, 380], (c) => txt(c, 'GUESS', 500, 350, 34, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(7.2, 7.5), key: 'gtt' });
200
+ paint(cream, [800, 240, 1180, 440], (c) => c.fillRect(820, 260, 320, 160), { edge: 2, p: G(7.3, 7.6), key: 'card2' });
201
+ paint(second ? teal : H.mix(rose, navy, 0.62), [800, 240, 1180, 440], (c) => txt(c, second ? 'CAT' : 'DOG?', 980, 380, 120, 'center'), { edge: 1, rag: 0.5, p: second ? G(9.4, 9.7) : G(7.4, 7.7), key: second ? 'ansC' : 'ansD' });
202
+ // CHECK: wrong! an orange starburst stamp and a big cross
203
+ if (t > 7.9 && !second) {
204
+ const k = G(7.9, 8.05);
205
+ paint(orange, [760, 180, 1220, 500], (c) => { c.beginPath(); for (let i = 0; i < 28; i++) { const a = i / 28 * Math.PI * 2, r = (i % 2 ? 150 : 210) * (0.6 + 0.4 * k); c.lineTo(980 + Math.cos(a) * r * 1.15, 340 + Math.sin(a) * r * 0.8); } c.closePath(); c.fill(); }, { edge: 1.5, rag: 0.5, p: k, key: k >= 1 ? 'burst2' : null });
206
+ paint(cream, [800, 240, 1180, 440], (c) => c.fillRect(820, 260, 320, 160), { edge: 2, key: 'card2' });
207
+ paint(H.mix(rose, navy, 0.62), [800, 240, 1180, 440], (c) => txt(c, 'DOG?', 980, 380, 120, 'center'), { edge: 1, rag: 0.5, key: 'ansD' });
208
+ paint(navy, [800, 240, 1180, 440], (c) => { c.save(); c.translate(980, 340); for (const a of [0.7, -0.7]) { c.save(); c.rotate(a); c.fillRect(-120, -11, 240, 22); c.restore(); } c.restore(); }, { edge: 1, rag: 0.4, p: G(8.0, 8.2), key: 'x2' });
209
+ paint(cream, [800, 150, 1170, 240], (c) => c.fillRect(812, 172, 336, 56), { edge: 1.5, p: G(8.05, 8.25), key: 'cks' });
210
+ paint(navy, [800, 150, 1170, 240], (c) => txt(c, "CHECK: IT'S A CAT", 980, 216, 46, 'center', 'Bebas Neue', 3), { edge: 0.8, rag: 0.3, p: G(8.15, 8.45), key: 'ckt' });
211
+ }
212
+ // ADJUST: a flat arrow loops back under the ticket into the machine
213
+ paint(rose, [380, 420, 1180, 560], (c) => { c.beginPath(); c.moveTo(1000, 430); c.lineTo(1000, 520); c.lineTo(480, 520); c.lineTo(480, 470); c.lineTo(450, 470); c.lineTo(500, 425); c.lineTo(550, 470); c.lineTo(520, 470); c.lineTo(520, 490); c.lineTo(960, 490); c.lineTo(960, 430); c.closePath(); c.fill(); }, { dir: 'h', p: G(8.3, 8.7), key: 'adj' });
214
+ paint(navy, [560, 480, 960, 540], (c) => txt(c, 'ADJUST', 640, 514, 30, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(8.6, 8.8), key: 'adjt' });
215
+ if (second) paint(teal, [1120, 240, 1260, 420], (c) => { c.lineWidth = 22; c.lineCap = 'round'; c.lineJoin = 'round'; c.beginPath(); c.moveTo(1150, 330); c.lineTo(1180, 370); c.lineTo(1240, 270); c.stroke(); }, { edge: 1, rag: 0.4, p: G(9.7, 9.95), key: 'tick' });
216
+ bandText('GUESS · CHECK · ADJUST', 6.8, 96, 'bt2');
217
+ }
218
+ if (scene === 3) {
219
+ // a flat bar chart climbing over training rounds
220
+ const N = 10, x0 = 110, base = 510, bw = 64, gap = 22; // chart sits 10px higher so the axis label clears the band
221
+ paint(navy, [100, 120, 1200, 530], (c) => { c.fillRect(110, 514, 1020, 8); }, { p: G(11.0, 11.3), key: 'axis' });
222
+ const pr = H.clamp((t - 11.2) / 1.6);
223
+ for (let i = 0; i < N; i++) {
224
+ const acc = 61 + 36 * (1 - Math.exp(-i / 2.6)) / (1 - Math.exp(-(N - 1) / 2.6)), h = (acc - 45) * 6;
225
+ const a = 11.2 + i * 0.15;
226
+ paint(i === N - 1 ? orange : H.mix(teal, cream, 0.15 * (N - i) / N), [x0 + i * (bw + gap) - 6, base - h - 6, x0 + i * (bw + gap) + bw + 6, base + 6], (c) => c.fillRect(x0 + i * (bw + gap), base - h, bw, h), { dir: 'v', p: G(a, a + 0.3), key: 'bar' + i });
227
+ }
228
+ paint(navy, [100, 524, 1200, 572], (c) => txt(c, 'TRAINING ROUNDS →', 140, 553, 30, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(11.2, 11.5), key: 'tr' });
229
+ const acc = Math.round(t < 11.2 ? 61 : t > 12.8 ? 97 : 61 + 36 * H.ease.out(pr));
230
+ if (t > 12.8) { const k = G(12.8, 12.95); paint(orange, [960, 40, 1260, 300], (c) => { c.beginPath(); for (let i = 0; i < 24; i++) { const a = i / 24 * Math.PI * 2, r = (i % 2 ? 80 : 118) * (0.5 + 0.5 * k); c.lineTo(1115 + Math.cos(a) * r, 170 + Math.sin(a) * r); } c.closePath(); c.fill(); }, { edge: 1.5, rag: 0.5, p: k, key: k >= 1 ? 'burst3' : null }); }
231
+ paint(navy, [900, 80, 1260, 260], (c) => txt(c, acc + '%', 1115, 212, 110, 'center'), { edge: 1, rag: 0.4, p: G(11.1, 11.3), key: acc === 97 ? 'acc97' : null });
232
+ paint(navy, [100, 300, 200, 420], (c) => txt(c, '61%', 142, 390, 34, 'center'), { edge: 0.7, rag: 0.3, p: G(11.3, 11.5), key: 'l61' });
233
+ if (t < 13.15) bandText('ACCURACY ' + acc + '%', 11.0);
234
+ else bandText('MORE EXAMPLES → BETTER GUESSES', 13.15, 84, 'bt3');
235
+ }
236
+ // ======== the medium's scene change: a fresh coat of cream swept across the board ========
237
+ for (const [a, b] of [[5.9, 6.35], [10.4, 10.85]]) {
238
+ if (t > a && t < b + 0.01) paint(cream, [0, 0, W, 572], (c) => c.fillRect(0, 0, W, 566), { edge: 6, rag: 1.4, p: G(a, b) });
239
+ }
240
+ // ======== hook -> header tab ========
241
+ {
242
+ const mc = H.layer(4, 4).getContext('2d'); mc.font = '400 140px "Bebas Neue"'; if (mc.letterSpacing !== undefined) mc.letterSpacing = '6px';
243
+ const tw = mc.measureText('HOW DOES AI LEARN?').width;
244
+ const k = G(1.5, 2.2), s = 1 - 0.68 * k, x = (640 - tw / 2) + (40 - (640 - tw / 2)) * k, y = 330 + (64 - 330) * k;
245
+ if (k > 0.3) paint(navy, [20, 20, 520, 110], (c) => c.fillRect(22, 22, 470, 62), { p: G(1.9, 2.3), key: 'tab' });
246
+ paint(k > 0.6 ? cream : navy, [0, 0, W, 470], (c) => { c.translate(x, y); c.scale(s, s); txt(c, 'HOW DOES AI LEARN?', 0, 0, 140, 'left', 'Bebas Neue', 6); }, { edge: 1.2, rag: 0.6, p: G(0.3, 0.9), key: k >= 1 ? 'title' : null });
247
+ }
248
+ // poster border + grain
249
+ paint(cream, [0, 0, W, H_], (c) => { c.beginPath(); c.rect(0, 0, W, H_); c.moveTo(20, 20); c.lineTo(20, H_ - 20); c.lineTo(W - 20, H_ - 20); c.lineTo(W - 20, 20); c.closePath(); c.fill('evenodd'); c.fillRect(20, 560, W - 40, 9); }, { edge: 2, rag: 0.9, key: 'border' });
250
+ for (let i = 0, p = 0; i < W * H_; i++, p += 4) { const g = grain[i]; buf[p] += g; buf[p + 1] += g; buf[p + 2] += g * 0.92; }
251
+ ctx.putImageData(new ImageData(buf, W, H_), 0, 0);
252
+ }
253
+
254
+ registerStyle({
255
+ id: 'gouache-poster',
256
+ name: 'Gouache Travel Poster',
257
+ category: 'Painterly',
258
+ description: 'Mid-century travel poster hand-painted in gouache: six flat, opaque, matte colours stacked in receding layers, dry-brush streaks where the paint thins at each edge, and a bold hand-lettered block.',
259
+ motion: 'Layers slide in parallax like stacked paper cut-outs, the sun rises behind the ridges, steam puffs bob from the cooling towers and server windows blink on in rows.',
260
+ fonts: ['400 132px "Bebas Neue"', '700 24px "Oswald"', '400 18px "Oswald"'],
261
+ duration: 6,
262
+ frameT: 5.9,
263
+ readsPixels: true,
264
+ explainerDuration: 15,
265
+ explainerNote: 'A run of hand-painted posters: each scene change is a fresh coat of cream swept across the board before the next poster is painted flat colour by flat colour; arrows are flat poster arrow shapes, numbers are repainted in the lettering band, and an orange starburst stamps the key moments.',
266
+ explainer(ctx, W, H_, t) { explainGouache(ctx, W, H_, t); },
267
+ explainerSound(k) {
268
+ // all times copied from explainGouache() above (its G(a, b) paint windows)
269
+ const D = 15, px = (x) => H.clamp(x / 1280 * 2 - 1, -1, 1) * 0.7;
270
+ const when = (f, v, a, b) => { for (let t = a; t <= b; t += 0.001) if (f(t) >= v) return t; return b; };
271
+ // a flat brush loaded with opaque gouache pulled across hardboard: a creamy drag with a bristle grain,
272
+ // panned along the stroke (p0 -> p1), with a soft knock as the ferrule touches the board
273
+ const flat = (a, b, g = 1, p0 = 0, p1 = p0, minor = true) => {
274
+ const d = b - a, r = H.rng(Math.round(a * 997) + 3);
275
+ k.noise(a, { dur: 0.04, color: 'brown', filter: 'lowpass', f: 450, gain: 0.2 * g, a: 0.004, pan: p0, minor });
276
+ for (let x = 0; x < d; x += 0.035 + r() * 0.02) {
277
+ const u = x / d, gd = 0.06 + r() * 0.03;
278
+ k.noise(a + x, { dur: gd, color: 'pink', filter: 'bandpass', f: 650 + r() * 350, q: 0.7, gain: 0.13 * g * (0.65 + r() * 0.5) * (1 - 0.35 * u), a: gd * 0.35, pan: p0 + (p1 - p0) * u, minor });
279
+ }
280
+ k.play('stroke', a, { material: 'brush', dur: d, gain: 0.9 * g, pan: (p0 + p1) / 2, minor });
281
+ };
282
+ // the scene change: a wide house-painter's brush sweeps a fresh coat of cream across the whole board, left to right
283
+ const coat = (a, b) => { flat(a, b, 2.0, -0.7, 0.7, false); k.noise(a, { dur: b - a + 0.1, color: 'brown', filter: 'lowpass', f: 300, gain: 0.14, a: 0.05, hold: (b - a) * 0.5 }); };
284
+ // repainted number in the band: a quick dab of the lettering brush
285
+ const dab = (t, g = 1, pan = 0) => { k.withMinor(() => { k.noise(t, { dur: 0.04, color: 'pink', filter: 'bandpass', f: 950, q: 1.0, gain: 0.3 * g, a: 0.004, pan }); k.tone(t, { f: 170, dur: 0.05, gain: 0.08 * g, pan }); }); };
286
+ const counter = (val, a, b, gap, pan, avoid = []) => { let last = val(a), lastT = -1;
287
+ for (let t = a; t <= b; t += 1 / 240) { const v = val(t); if (v !== last && t - lastT >= gap && !avoid.some((x) => t > x - 0.03 && t < x + 0.07)) { dab(t, 1, pan); lastT = t; } last = v; } };
288
+
289
+ k.bed('room', 0, D, { gain: 0.45 });
290
+ // a light mid-century pad (C6, Am7, Fmaj7, G6; 3.75 s bars) under the paint
291
+ k.music('pad', 0, D, { bpm: 64, gain: 0.3, chords: [['C3', 'E3', 'G3', 'A3'], ['A2', 'E3', 'G3', 'C4'], ['F2', 'C3', 'E3', 'A3'], ['G2', 'D3', 'E3', 'B3']] });
292
+
293
+ // ---- 1 HOOK: sky, ground and band painted 0-0.7, title lettered 0.3-0.9, then it shrinks to the tab 1.5-2.2
294
+ flat(0.0, 0.3, 0.7, -0.6, 0.2); // sky (0-0.45), quieter, the title takes over at 0.3
295
+ flat(0.3, 0.9, 1.2, -0.5, 0.5, false);
296
+ k.noise(1.5, { dur: 0.7, color: 'pink', filter: 'bandpass', f: [[0, 320], [0.7, 650]], q: 0.7, gain: 0.07, a: 0.25, minor: true }); // soft slide
297
+ flat(1.9, 2.3, 0.7, -0.9, -0.3); // navy header tab
298
+
299
+ // ---- 2 DATA: the machine painted colour by colour from 0.9, conveyor 1.6-2.3, hopper 2.0-2.3
300
+ flat(0.9, 1.25, 0.9, px(900), px(1200), false); // teal body
301
+ flat(1.2, 1.55, 0.7, px(950), px(1100)); // gauges
302
+ flat(1.6, 2.1, 0.8, px(0), px(900)); // mustard conveyor
303
+ // the belt runs under the cards (2.4 -> ~5.9): a soft low rumble
304
+ k.noise(2.3, { dur: 3.6, color: 'brown', filter: 'bandpass', f: 220, q: 1.2, gain: 0.12, a: 0.4, hold: 2.9, pan: -0.2, minor: true });
305
+ flat(2.6, 3.0, 0.9, px(420), px(810), false); // cream poster arrow
306
+ flat(2.6, 3.05, 0.6, -0.8, 0.2); // counter lettered in the band
307
+ // counter: n = 1e6 * ease.in((t - 2.7) / 2.9), rounded to thousands
308
+ counter((t) => Math.round(1e6 * H.ease.in(H.clamp((t - 2.7) / 2.9)) / 1000), 3.05, 5.58, 1 / 12, -0.5);
309
+ k.play('tock', 5.6, { f: 300, gain: 0.7, pan: -0.5 }); // 1,000,000 lands
310
+ flat(5.6, 5.9, 1.0, -0.8, 0.3, false); // orange underline
311
+
312
+ // ---- fresh coat 5.9-6.35
313
+ coat(5.9, 6.35);
314
+
315
+ // ---- 3 GUESS -> CHECK -> ADJUST
316
+ flat(6.6, 6.95, 0.8, px(120), px(420)); // machine repainted at the left
317
+ flat(6.9, 7.25, 0.6, px(150), px(300));
318
+ k.play('slap', 7.2, { gain: 0.3, pan: px(270), minor: true }); // new picture drops in
319
+ flat(7.1, 7.4, 1.0, px(480), px(790), false); // GUESS ticket arrow
320
+ flat(7.4, 7.7, 0.9, px(860), px(1100)); // DOG?
321
+ // the wrong guess: an orange starburst splats on, the navy cross is brushed across it
322
+ k.play('slap', 7.9, { gain: 0.55, pan: px(980) });
323
+ k.play('thud', 7.9, { f: 120, gain: 0.4, pan: px(980) });
324
+ k.tone(7.92, { f: 'A3', type: 'triangle', dur: 0.2, gain: 0.18, a: 0.01, hold: 0.07, pan: px(980) });
325
+ k.tone(8.1, { f: 'F3', type: 'triangle', dur: 0.36, gain: 0.18, a: 0.01, hold: 0.12, pan: px(980) });
326
+ flat(8.0, 8.1, 1.4, px(860), px(1100), false); flat(8.1, 8.2, 1.4, px(1100), px(860), false);
327
+ flat(8.3, 8.7, 1.1, px(1000), px(480), false); // rose ADJUST arrow loops back
328
+ // gauges turn G(8.6, 9.2): a few mechanical detents
329
+ const G2 = (t) => H.ease.inOut(H.clamp((t - 8.6) / 0.6));
330
+ for (let s = 1; s <= 3; s++) k.play('tock', when((t) => G2(t) * 4, s, 8.6, 9.2), { f: 520 + s * 30, gain: 0.45, pan: px(250), minor: true });
331
+ flat(9.4, 9.7, 1.0, px(860), px(1100)); // CAT
332
+ flat(9.7, 9.95, 1.1, px(1150), px(1240), false); // teal tick
333
+ k.play('success', 9.95, { f: 'G5', gain: 0.5, pan: px(1180) });
334
+
335
+ // ---- fresh coat 10.4-10.85
336
+ coat(10.4, 10.85);
337
+
338
+ // ---- 4 PROGRESS: mustard sky 10.85, axis 11.0-11.3, ten bars painted upward from 11.2 + i*0.15
339
+ flat(10.85, 11.3, 0.7, -0.6, 0.6);
340
+ for (let i = 0; i < 10; i++) {
341
+ const a = 11.2 + i * 0.15, p = px(110 + i * 86 + 32);
342
+ k.withMinor(() => k.noise(a, { dur: 0.25, color: 'pink', filter: 'bandpass', f: [[0, 420 + i * 25], [0.25, 800 + i * 40]], q: 0.8, gain: 0.16, a: 0.03, pan: p }));
343
+ k.play('tock', a, { f: 260 + i * 12, gain: 0.36, pan: p, minor: true });
344
+ }
345
+ // % counter: 61 + 36 * ease.out((t - 11.2) / 1.6); soft dabs, sparser than the bars
346
+ const acc = (t) => Math.round(61 + 36 * H.ease.out(H.clamp((t - 11.2) / 1.6)));
347
+ const t97 = when(acc, 97, 11.2, 12.8);
348
+ counter(acc, 11.25, t97 - 0.05, 1 / 7, px(1115), Array.from({ length: 10 }, (_, i) => 11.2 + i * 0.15));
349
+ k.play('tock', t97, { f: 380, gain: 0.55, pan: px(1115) }); // 97% painted
350
+ // the orange starburst splats behind 97% (12.8-12.95): the biggest hit
351
+ k.play('impact', 12.8, { gain: 0.9, pan: px(1115) });
352
+ k.play('slap', 12.8, { gain: 0.9, pan: px(1115) });
353
+ k.play('ding', 12.83, { f: 'E5', gain: 0.5, pan: 0.3 });
354
+ ['C5', 'G5'].forEach((f, i) => k.tone(12.83 + 0.03 * (i + 1), { f, dur: 1.2, gain: 0.07, verb: 0.3, pan: 0.15 + i * 0.3 }));
355
+
356
+ // ---- 5 TAKEAWAY: band repainted 13.15-13.6, then a resolving chord
357
+ flat(13.15, 13.6, 1.2, -0.7, 0.7, false);
358
+ k.play('ding', 13.65, { f: 'C5', gain: 0.45 });
359
+ ['E5', 'G5', 'C6'].forEach((f, i) => k.tone(13.7 + i * 0.05, { f, dur: 1.2, gain: 0.06, verb: 0.3, pan: (i - 1) * 0.3 }));
360
+ },
361
+ draw(ctx, W, H_, t) {
362
+ const { navy, teal, orange, mustard, cream, rose } = PAL;
363
+ const Cx = gpCache(W, H_); // brushing fields + board grain, built once (same seeds as before)
364
+ // the board starts with the artist's pencil layout, which the opaque paint then buries
365
+ const PB = H.layer(W, H_), pb = PB.getContext('2d');
366
+ pb.fillStyle = `rgb(${cream})`; pb.fillRect(0, 0, W, H_);
367
+ pb.strokeStyle = 'rgba(90,80,70,0.38)'; pb.lineWidth = 1; pb.lineCap = 'round';
368
+ const pl = (pts) => { pb.beginPath(); pts.forEach(([x, y], i) => (i ? pb.lineTo(x, y) : pb.moveTo(x, y))); pb.stroke(); };
369
+ pl([[0, 300], [90, 262], [170, 296], [300, 220], [380, 270], [470, 236], [560, 288], [660, 250], [720, 276], [980, 214], [1080, 268], [1160, 236], [1280, 280]]);
370
+ pl([[0, 352], [120, 318], [230, 350], [340, 300], [450, 344], [560, 330], [700, 352], [780, 318], [900, 346], [1040, 306], [1150, 340], [1280, 322]]);
371
+ pl([[0, 392], [1280, 392]]); pl([[440, 452], [1280, 452]]); pl([[0, 160], [1280, 160]]); pl([[0, 250], [1280, 250]]);
372
+ pb.beginPath(); pb.arc(860, 234, 112, 0, 7); pb.stroke();
373
+ pb.strokeRect(470, 360, 760, 92); pb.strokeRect(36, 586, 840, 104); pb.strokeRect(930, 600, 300, 90);
374
+ pl([[160, 560], [150, 420], [176, 250]]); pb.beginPath(); pb.ellipse(200, 160, 180, 50, 0, Math.PI, 0); pb.stroke();
375
+ pl([[0, 470], [300, 470], [600, 540], [900, 520], [1280, 504]]);
376
+ const buf = pb.getImageData(0, 0, W, H_).data;
377
+ const paint = makePainter(buf, W, H_, Cx, 3, 0.9);
378
+ const polyPath = (c, pts) => { c.beginPath(); pts.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); c.fill(); };
379
+ const rise = H.ease.inOut(H.clamp((t - 0.8) / 4.2));
380
+ const G = (a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
381
+
382
+ // ---------- sky: three flat bands ----------
383
+ paint(rose, [0, 0, W, 170], (c) => c.fillRect(0, 0, W, 160), { edge: 4, rag: 0.8, p: G(0, 0.55) });
384
+ paint(tint(rose, 0.45), [0, 140, W, 260], (c) => c.fillRect(0, 158, W, 92), { edge: 4, rag: 0.8, p: G(0.25, 0.75) });
385
+ paint(tint(mustard, 0.5), [0, 230, W, 420], (c) => c.fillRect(0, 248, W, 170), { edge: 4, rag: 0.8, p: G(0.45, 0.95) });
386
+
387
+ // ---------- sun ----------
388
+ const sunY = 300 - rise * 66, sunX = 860;
389
+ paint(orange, [sunX - 130, sunY - 130, sunX + 130, sunY + 130], (c) => { c.beginPath(); c.arc(sunX, sunY, 112, 0, 7); c.fill(); }, { edge: 2.5, rag: 0.8, p: G(0.9, 1.2) });
390
+ // retro sunset bars cut back in with the sky colour
391
+ paint(tint(mustard, 0.5), [sunX - 130, sunY - 10, sunX + 130, sunY + 130], (c) => {
392
+ for (let k = 0; k < 4; k++) c.fillRect(sunX - 120, sunY + 4 + k * 17, 240, 3 + k * 2.2);
393
+ }, { edge: 1.2, rag: 0.5, p: G(1.15, 1.3) });
394
+
395
+ // ---------- birds ----------
396
+ paint(navy, [480, 120, 640, 200], (c) => {
397
+ c.lineWidth = 4; c.lineCap = 'round';
398
+ [[520, 160, 1], [570, 140, 0.8], [600, 172, 0.65]].forEach(([x, y, s]) => {
399
+ const yy = y + Math.sin(t * 2 + x) * 3; x += (t - 5.9) * 26;
400
+ c.beginPath(); c.moveTo(x - 16 * s, yy - 4 * s); c.quadraticCurveTo(x - 6 * s, yy - 10 * s, x, yy); c.quadraticCurveTo(x + 6 * s, yy - 10 * s, x + 16 * s, yy - 4 * s); c.stroke();
401
+ });
402
+ }, { edge: 1, rag: 0.5, p: G(1.0, 1.1) });
403
+
404
+ // ---------- far range + snow caps ----------
405
+ const farPts = [[0, 300]];
406
+ const farPeaks = [[0, 300], [90, 262], [170, 296], [300, 220], [380, 270], [470, 236], [560, 288], [660, 250], [720, 276], [980, 214], [1080, 268], [1160, 236], [1280, 280]];
407
+ farPeaks.forEach((p) => farPts.push(p)); farPts.push([1280, 420], [0, 420]);
408
+ paint(tint(teal, 0.5), [0, 200, W, 430], (c) => polyPath(c, farPts), { edge: 3, rag: 1, dir: 'd', p: G(1.0, 1.5) });
409
+ paint(cream, [0, 200, W, 300], (c) => {
410
+ [[300, 220], [470, 236], [980, 214], [1160, 236]].forEach(([x, y]) => {
411
+ polyPath(c, [[x, y], [x + 34, y + 20], [x + 20, y + 16], [x + 8, y + 26], [x - 6, y + 17], [x - 20, y + 25], [x - 32, y + 19]]);
412
+ });
413
+ }, { edge: 1.5, rag: 0.6, p: G(1.45, 1.6) });
414
+ // shadow planes on the far range
415
+ paint(tint(teal, 0.3), [0, 200, W, 430], (c) => {
416
+ for (let k = 1; k < farPeaks.length - 1; k++) {
417
+ const [x, y] = farPeaks[k], [nx, ny] = farPeaks[k + 1];
418
+ if (y < farPeaks[k - 1][1] && y < ny) polyPath(c, [[x, y + 6], [nx, ny], [nx - 10, 420], [x + 14, 420]]);
419
+ }
420
+ }, { edge: 2, rag: 1, p: G(1.55, 1.75) });
421
+
422
+ // ---------- mid range ----------
423
+ const mid = [[0, 352], [120, 318], [230, 350], [340, 300], [450, 344], [560, 330], [700, 352], [780, 318], [900, 346], [1040, 306], [1150, 340], [1280, 322], [1280, 440], [0, 440]];
424
+ paint(teal, [0, 290, W, 450], (c) => polyPath(c, mid), { edge: 3, rag: 1, dir: 'd', p: G(1.6, 2.0) });
425
+ paint(H.mix(teal, navy, 0.35), [0, 290, W, 450], (c) => {
426
+ for (let k = 1; k < mid.length - 3; k++) {
427
+ const [x, y] = mid[k], [nx, ny] = mid[k + 1];
428
+ if (y < mid[k - 1][1] && y < ny) polyPath(c, [[x, y], [nx, ny], [nx + 10, 440], [x + 40, 440]]);
429
+ }
430
+ }, { edge: 2, rag: 1, p: G(1.95, 2.15) });
431
+
432
+ // ---------- lake with broken sun reflection ----------
433
+ paint(tint(teal, 0.25), [0, 380, W, 480], (c) => c.fillRect(0, 392, W, 80), { edge: 3, rag: 1.1, p: G(2.0, 2.35) });
434
+ paint(mustard, [600, 380, 1120, 480], (c) => {
435
+ const rr = H.rng(77);
436
+ for (let k = 0; k < 9; k++) {
437
+ const y = 398 + k * 8, half = (130 - k * 11) * (0.7 + rr() * 0.4), cx = sunX + rr.gauss() * 10 + Math.sin(t * 1.5 + k) * 6;
438
+ c.fillRect(cx - half, y, half * 2 * (0.3 + rr() * 0.3), 3.5); c.fillRect(cx + half * 0.2, y, half * 0.8, 3.5);
439
+ }
440
+ }, { edge: 1, rag: 0.6, p: G(2.3, 2.5) });
441
+
442
+ // ---------- data-centre city on the far shore ----------
443
+ const city = [];
444
+ const cr = H.rng(901);
445
+ let bx0 = 470;
446
+ while (bx0 < 1230) {
447
+ const w = cr.range(46, 96), h = cr.range(40, 120) * (1 - Math.abs(bx0 - 860) / 900), d = cr.range(14, 24);
448
+ city.push({ x: bx0, w, h, d, base: 452 + cr.range(-4, 6) });
449
+ bx0 += w + cr.range(6, 30);
450
+ }
451
+ // shadowed sides (rose) first, lit fronts (cream), roofs (orange)
452
+ paint(H.mix(rose, orange, 0.25), [460, 280, 1260, 470], (c) => city.forEach((b) => polyPath(c, [[b.x + b.w, b.base - b.h], [b.x + b.w + b.d, b.base - b.h - 8], [b.x + b.w + b.d, b.base - 6], [b.x + b.w, b.base]])), { edge: 1.6, rag: 0.8, dir: 'v', p: G(2.4, 2.7) });
453
+ paint(cream, [460, 280, 1260, 470], (c) => city.forEach((b) => c.fillRect(b.x, b.base - b.h, b.w, b.h)), { edge: 1.6, rag: 0.8, dir: 'v', p: G(2.55, 2.85) });
454
+ paint(orange, [460, 280, 1260, 470], (c) => city.forEach((b) => polyPath(c, [[b.x, b.base - b.h], [b.x + b.w, b.base - b.h], [b.x + b.w + b.d, b.base - b.h - 8], [b.x + b.d, b.base - b.h - 8]])), { edge: 1.2, rag: 0.6, p: G(2.8, 2.95) });
455
+ // server windows: rows of slits; some glowing
456
+ paint(navy, [460, 280, 1260, 470], (c) => city.forEach((b) => {
457
+ for (let y = b.base - b.h + 12; y < b.base - 10; y += 12) for (let x = b.x + 8; x < b.x + b.w - 10; x += 10) c.fillRect(x, y, 6, 4);
458
+ }), { edge: 0.8, rag: 0.5, p: G(2.95, 3.1) });
459
+ paint(mustard, [460, 280, 1260, 470], (c) => {
460
+ const wr = H.rng(902);
461
+ city.forEach((b) => {
462
+ for (let y = b.base - b.h + 12; y < b.base - 10; y += 12) for (let x = b.x + 8; x < b.x + b.w - 10; x += 10) {
463
+ const on = wr() < (0.22 + 0.25 * (0.5 + 0.5 * Math.sin(Math.floor(t * 4) * 1.7 + x * 0.3 + y))) * H.clamp((t - 3.1 - (b.base - y) / 300) / 0.1);
464
+ if (on) c.fillRect(x, y, 6, 4);
465
+ }
466
+ });
467
+ }, { edge: 0.7, rag: 0.4, p: G(3.1, 3.2) });
468
+ // cooling towers + steam
469
+ const towers = [[648, 452, 36, 70], [724, 452, 30, 58]];
470
+ paint(cream, [580, 330, 780, 460], (c) => towers.forEach(([x, y, r, h]) => {
471
+ c.beginPath(); c.moveTo(x - r, y); c.quadraticCurveTo(x - r * 0.45, y - h * 0.55, x - r * 0.62, y - h); c.lineTo(x + r * 0.62, y - h); c.quadraticCurveTo(x + r * 0.45, y - h * 0.55, x + r, y); c.closePath(); c.fill();
472
+ }), { edge: 1.5, rag: 0.7, dir: 'v', p: G(2.6, 2.85) });
473
+ paint(H.mix(rose, orange, 0.25), [580, 330, 780, 460], (c) => towers.forEach(([x, y, r, h]) => {
474
+ c.beginPath(); c.moveTo(x + r * 0.15, y); c.quadraticCurveTo(x + r * 0.2, y - h * 0.55, x + r * 0.2, y - h); c.lineTo(x + r * 0.62, y - h); c.quadraticCurveTo(x + r * 0.45, y - h * 0.55, x + r, y); c.closePath(); c.fill();
475
+ }), { edge: 1.5, rag: 0.7, dir: 'v', p: G(2.8, 2.95) });
476
+ paint(tint(rose, 0.75), [540, 160, 860, 400], (c) => towers.forEach(([x, y, r, h], k) => {
477
+ for (let j = 0; j < 4; j++) {
478
+ const ph = (t * 0.5 + j / 4) % 1, yy = y - h - 10 - ph * 120, rr = r * (0.35 + ph * 0.6), xx = x - 6 + ph * 70;
479
+ c.beginPath(); c.arc(xx, yy, rr, 0, 7); c.arc(xx + rr * 0.8, yy + rr * 0.2, rr * 0.7, 0, 7); c.fill();
480
+ }
481
+ }), { edge: 2, rag: 0.6, p: G(3.0, 3.25) });
482
+ // radio mast with beacon
483
+ paint(navy, [1000, 230, 1060, 460], (c) => {
484
+ c.lineWidth = 3; c.beginPath(); c.moveTo(1018, 452); c.lineTo(1030, 260); c.lineTo(1042, 452); c.stroke();
485
+ for (let y = 280; y < 452; y += 22) { const u = (y - 260) / 192; c.beginPath(); c.moveTo(1030 - u * 12, y); c.lineTo(1030 + u * 12, y + 22); c.stroke(); }
486
+ c.fillRect(1029, 244, 2, 18);
487
+ }, { edge: 0.8, rag: 0.4, p: G(3.0, 3.2) });
488
+ paint(orange, [1020, 230, 1040, 260], (c) => { c.beginPath(); c.arc(1030, 244, 4 + Math.sin(t * 4) * 1.2, 0, 7); c.fill(); }, { edge: 0.6, rag: 0.3, p: G(3.2, 3.25) });
489
+ // shore line
490
+ paint(H.mix(teal, navy, 0.5), [440, 440, W, 470], (c) => c.fillRect(440, 451, W - 440, 9), { edge: 1.5, rag: 0.8, p: G(2.35, 2.5) });
491
+
492
+ // ---------- near hills (navy) with the road into the city ----------
493
+ paint(H.mix(navy, teal, 0.35), [0, 400, W, 560], (c) => { c.beginPath(); c.moveTo(0, 430); c.bezierCurveTo(260, 400, 520, 470, 760, 482); c.bezierCurveTo(940, 492, 1120, 466, 1280, 474); c.lineTo(1280, 560); c.lineTo(0, 560); c.closePath(); c.fill(); }, { edge: 3, rag: 1, p: G(2.1, 2.4) });
494
+ paint(navy, [0, 450, W, 560], (c) => { c.beginPath(); c.moveTo(0, 470); c.bezierCurveTo(300, 470, 600, 540, 900, 520); c.bezierCurveTo(1060, 510, 1180, 500, 1280, 504); c.lineTo(1280, 560); c.lineTo(0, 560); c.closePath(); c.fill(); }, { edge: 3, rag: 1, p: G(2.3, 2.6) });
495
+ // road narrowing into the city (painted as one tapered ribbon)
496
+ paint(tint(mustard, 0.2), [300, 440, 1000, 560], (c) => {
497
+ const P = (u) => { const a = 1 - u; return [a * a * a * 360 + 3 * a * a * u * 540 + 3 * a * u * u * 560 + u * u * u * 840, a * a * a * 562 + 3 * a * a * u * 516 + 3 * a * u * u * 490 + u * u * u * 470]; };
498
+ const L = [], R = [];
499
+ for (let i = 0; i <= 30; i++) { const u = i / 30, [x, y] = P(u), w = 26 * Math.pow(1 - u, 1.6) + 1.5; L.push([x - w * 0.2, y - w * 0.5]); R.push([x + w * 0.2, y + w * 0.5]); }
500
+ polyPath(c, L.concat(R.reverse()));
501
+ }, { edge: 1.5, rag: 0.6, p: G(2.6, 2.8) });
502
+ // two travellers taking in the view
503
+ const fig = (c, x, y, s) => { c.beginPath(); c.arc(x, y - 44 * s, 7 * s, 0, 7); c.fill(); polyPath(c, [[x - 9 * s, y - 34 * s], [x + 9 * s, y - 34 * s], [x + 13 * s, y - 4 * s], [x - 13 * s, y - 4 * s]]); c.fillRect(x - 7 * s, y - 6 * s, 5 * s, 8 * s); c.fillRect(x + 2 * s, y - 6 * s, 5 * s, 8 * s); };
504
+ paint(orange, [240, 420, 330, 520], (c) => fig(c, 262, 506, 1.1), { edge: 1, rag: 0.4, dir: 'v', p: G(3.2, 3.35) });
505
+ paint(cream, [270, 420, 340, 520], (c) => { fig(c, 296, 508, 1.0); }, { edge: 1, rag: 0.4, dir: 'v', p: G(3.25, 3.4) });
506
+ paint(mustard, [240, 420, 340, 520], (c) => { c.fillRect(272, 482, 14, 12); c.lineWidth = 3; c.beginPath(); c.moveTo(304, 474); c.lineTo(330, 452); c.stroke(); }, { edge: 0.8, rag: 0.4, p: G(3.35, 3.45) });
507
+
508
+ // ---------- foreground umbrella pine ----------
509
+ paint(navy, [0, 200, 420, 560], (c) => {
510
+ c.beginPath(); c.moveTo(150, 560); c.bezierCurveTo(160, 440, 120, 360, 170, 250); c.lineTo(186, 254); c.bezierCurveTo(146, 360, 190, 450, 184, 560); c.closePath(); c.fill();
511
+ c.beginPath(); c.moveTo(166, 300); c.quadraticCurveTo(220, 250, 280, 214); c.lineTo(284, 224); c.quadraticCurveTo(226, 262, 176, 312); c.closePath(); c.fill();
512
+ c.beginPath(); c.moveTo(168, 270); c.quadraticCurveTo(120, 220, 70, 196); c.lineTo(66, 206); c.quadraticCurveTo(116, 232, 162, 282); c.closePath(); c.fill();
513
+ }, { edge: 1.8, rag: 0.6, dir: 'v', p: G(2.5, 2.8) });
514
+ paint(navy, [0, 40, 420, 240], (c) => {
515
+ [[180, 170, 150, 46], [300, 196, 110, 34], [70, 190, 90, 30], [240, 120, 110, 36]].forEach(([x, y, a, b]) => {
516
+ c.beginPath(); c.ellipse(x, y, a, b, 0, Math.PI, 0); c.bezierCurveTo(x + a, y + b * 0.4, x - a, y + b * 0.4, x - a, y); c.fill();
517
+ });
518
+ }, { edge: 2.5, rag: 0.8, dir: 'h', p: G(2.75, 3.0) });
519
+ paint(teal, [0, 60, 420, 220], (c) => {
520
+ [[180, 148, 120, 22], [300, 180, 84, 16], [74, 176, 64, 14], [240, 100, 84, 18]].forEach(([x, y, a, b]) => { c.beginPath(); c.ellipse(x - 6, y, a, b, 0, Math.PI, 0); c.closePath(); c.fill(); });
521
+ }, { edge: 2, rag: 1, p: G(2.95, 3.1) });
522
+
523
+ // ---------- lettering band ----------
524
+ paint(navy, [0, 560, W, H_], (c) => c.fillRect(0, 572, W, H_ - 572), { edge: 2.5, rag: 0.9, p: G(3.3, 3.6) });
525
+ paint(cream, [40, 570, 900, 715], (c) => {
526
+ c.font = '400 132px "Bebas Neue"'; c.textBaseline = 'alphabetic';
527
+ if (c.letterSpacing !== undefined) c.letterSpacing = '4px';
528
+ c.fillText('VISIT THE CLOUD', 52, 692);
529
+ }, { edge: 1.4, rag: 0.7, p: G(3.6, 4.1) });
530
+ paint(mustard, [880, 590, W, 700], (c) => {
531
+ c.font = '700 24px "Oswald"'; c.textAlign = 'right';
532
+ if (c.letterSpacing !== undefined) c.letterSpacing = '5px';
533
+ c.fillText('DATA VALLEY LINE', 1230, 630);
534
+ c.fillRect(940, 646, 290, 4);
535
+ c.font = '400 18px "Oswald"'; if (c.letterSpacing !== undefined) c.letterSpacing = '4px';
536
+ c.fillText('EXPRESS SERVICE NIGHTLY', 1230, 680);
537
+ }, { edge: 1, rag: 0.5, p: G(4.0, 4.3) });
538
+ paint(orange, [880, 590, W, 700], (c) => { c.beginPath(); c.arc(926, 648, 9, 0, 7); c.fill(); }, { edge: 0.8, rag: 0.4, p: G(4.25, 4.35) });
539
+
540
+ // ---------- poster border (cream, hand-trimmed) ----------
541
+ paint(cream, [0, 0, W, H_], (c) => {
542
+ c.beginPath(); c.rect(0, 0, W, H_);
543
+ c.moveTo(20, 20); c.lineTo(20, H_ - 20); c.lineTo(W - 20, H_ - 20); c.lineTo(W - 20, 20); c.closePath();
544
+ c.fill('evenodd');
545
+ c.fillRect(20, 560, W - 40, 9);
546
+ }, { edge: 2, rag: 0.9, key: 'border' });
547
+
548
+ // ---------- board texture, matte grain ----------
549
+ const grain = Cx.grain;
550
+ for (let i = 0, p = 0; i < W * H_; i++, p += 4) { const g = grain[i]; buf[p] += g; buf[p + 1] += g; buf[p + 2] += g * 0.92; }
551
+ // slow push-in on the finished poster (the border stays put: it is the poster's edge)
552
+ const OUT = H.layer(W, H_); OUT.getContext('2d').putImageData(new ImageData(buf, W, H_), 0, 0);
553
+ const z = 1 + 0.035 * H.ease.inOut(H.clamp(t / 6));
554
+ ctx.drawImage(OUT, 0, 0);
555
+ ctx.save(); ctx.beginPath(); ctx.rect(22, 22, W - 44, 536); ctx.clip();
556
+ ctx.translate(640, 300); ctx.scale(z, z); ctx.translate(-640, -300); ctx.drawImage(OUT, 0, 0); ctx.restore();
557
+ }
558
+ });
559
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": 49,
3
+ "name": "Gouache Travel Poster",
4
+ "nameZh": "水粉旅行海报",
5
+ "family": "Painterly",
6
+ "familyZh": "绘画质感",
7
+ "description": "Mid-century travel poster hand-painted in gouache: six flat, opaque, matte colours stacked in receding layers, dry-brush streaks where the paint thins at each edge, and a bold hand-lettered block.",
8
+ "motionNotes": "Motion: Layers slide in parallax like stacked paper cut-outs, the sun rises behind the ridges, steam puffs bob from the cooling towers and server windows blink on in rows.",
9
+ "explainerNotes": "Explainer: A run of hand-painted posters: each scene change is a fresh coat of cream swept across the board before the next poster is painted flat colour by flat colour; arrows are flat poster arrow shapes, numbers are repainted in the lettering band, and an orange starburst stamps the key moments.",
10
+ "code": "code.js",
11
+ "registeredAs": "gouache-poster",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }