@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,782 @@
1
+ {
2
+ const HZ = 452; // horizon line
3
+
4
+ function sky(c, W) {
5
+ const g = c.createLinearGradient(0, 0, 0, HZ);
6
+ g.addColorStop(0, '#1b0b3f');
7
+ g.addColorStop(0.32, '#4a1d7a');
8
+ g.addColorStop(0.62, '#b03aa8');
9
+ g.addColorStop(0.86, '#ff7ac0');
10
+ g.addColorStop(1, '#ffc0d8');
11
+ c.fillStyle = g; c.fillRect(0, 0, W, HZ);
12
+ }
13
+
14
+ function sun(c0, cx, cy, R, t) {
15
+ const L = H.layer(c0.canvas.width, c0.canvas.height), c = L.getContext('2d');
16
+ c.save();
17
+ c.beginPath(); c.arc(cx, cy, R, 0, Math.PI * 2); c.clip();
18
+ const g = c.createLinearGradient(0, cy - R, 0, cy + R);
19
+ g.addColorStop(0, '#fff27a'); g.addColorStop(0.45, '#ffa45c'); g.addColorStop(0.75, '#ff4f9a'); g.addColorStop(1, '#d9208e');
20
+ c.fillStyle = g; c.fillRect(cx - R, cy - R, R * 2, R * 2);
21
+ // slices: gaps get thicker toward the bottom and scroll down slowly
22
+ c.globalCompositeOperation = 'destination-out';
23
+ const scroll = (t * 14) % 26;
24
+ for (let k = -1; k < 12; k++) {
25
+ const yy = cy - R * 0.42 + k * 24 + scroll;
26
+ const f = H.clamp((yy - (cy - R * 0.45)) / (R * 0.75));
27
+ const th = 1.5 + f * 12;
28
+ c.fillRect(cx - R, yy, R * 2, th);
29
+ }
30
+ c.restore();
31
+ // soft glow behind the disc
32
+ c0.save();
33
+ const gg = c0.createRadialGradient(cx, cy, R * 0.8, cx, cy, R * 1.6);
34
+ gg.addColorStop(0, 'rgba(255,170,120,0.45)'); gg.addColorStop(1, 'rgba(255,100,180,0)');
35
+ c0.fillStyle = gg; c0.fillRect(cx - R * 1.7, cy - R * 1.7, R * 3.4, R * 3.4);
36
+ c0.drawImage(L, 0, 0);
37
+ c0.restore();
38
+ }
39
+
40
+ function floor(c, W, Hh, t) {
41
+ const g = c.createLinearGradient(0, HZ, 0, Hh);
42
+ g.addColorStop(0, '#2a0a52'); g.addColorStop(0.5, '#1a0538'); g.addColorStop(1, '#0e0224');
43
+ c.fillStyle = g; c.fillRect(0, HZ, W, Hh - HZ);
44
+ // horizon haze
45
+ const hg = c.createLinearGradient(0, HZ - 30, 0, HZ + 60);
46
+ hg.addColorStop(0, 'rgba(255,120,200,0)'); hg.addColorStop(0.35, 'rgba(255,140,210,0.55)'); hg.addColorStop(1, 'rgba(255,120,200,0)');
47
+ c.fillStyle = hg; c.fillRect(0, HZ - 30, W, 90);
48
+
49
+ const vpx = W / 2, f = 260, camH = 1.0;
50
+ const lines = (pass) => {
51
+ c.beginPath();
52
+ // longitudinal lines
53
+ for (let i = -26; i <= 26; i++) {
54
+ const xw = i * 0.9;
55
+ const zNear = 0.35, zFar = 60;
56
+ c.moveTo(vpx + (xw / zNear) * f, HZ + (camH / zNear) * f);
57
+ c.lineTo(vpx + (xw / zFar) * f, HZ + (camH / zFar) * f);
58
+ }
59
+ // transverse lines (scrolling toward viewer)
60
+ const off = (t * 1.6) % 0.9;
61
+ for (let k = 0; k < 70; k++) {
62
+ const z = 0.4 + k * 0.9 - off;
63
+ if (z < 0.4) continue;
64
+ const y = HZ + (camH / z) * f;
65
+ if (y > Hh + 2) continue;
66
+ c.moveTo(0, y); c.lineTo(W, y);
67
+ }
68
+ c.stroke();
69
+ };
70
+ c.save();
71
+ c.beginPath(); c.rect(0, HZ, W, Hh - HZ); c.clip();
72
+ c.globalCompositeOperation = 'lighter';
73
+ c.strokeStyle = 'rgba(255,60,200,0.22)'; c.lineWidth = 7; lines();
74
+ c.strokeStyle = 'rgba(120,240,255,0.35)'; c.lineWidth = 3; lines();
75
+ c.strokeStyle = 'rgba(200,255,255,0.9)'; c.lineWidth = 1.2; lines();
76
+ c.restore();
77
+ // fade grid into horizon haze
78
+ const fg = c.createLinearGradient(0, HZ, 0, HZ + 70);
79
+ fg.addColorStop(0, 'rgba(255,150,215,0.85)'); fg.addColorStop(1, 'rgba(255,150,215,0)');
80
+ c.fillStyle = fg; c.fillRect(0, HZ, W, 70);
81
+ }
82
+
83
+ // Palm silhouette
84
+ function palm(c, x, y, h, lean, seed, col) {
85
+ const r = H.rng(seed);
86
+ c.save(); c.fillStyle = col; c.strokeStyle = col;
87
+ // trunk: segmented, tapering, curved
88
+ const segs = 22; let px = x, py = y;
89
+ const topX = x + lean * h, topY = y - h;
90
+ const pts = [];
91
+ for (let i = 0; i <= segs; i++) {
92
+ const u = i / segs;
93
+ const bx = x + lean * h * (u * u * 0.6 + u * 0.4), by = y - h * u;
94
+ pts.push([bx, by, H.lerp(h * 0.055, h * 0.028, u)]);
95
+ }
96
+ for (let i = 0; i < segs; i++) {
97
+ const [ax, ay, aw] = pts[i], [bx, by, bw] = pts[i + 1];
98
+ c.beginPath();
99
+ c.moveTo(ax - aw, ay); c.lineTo(ax + aw, ay); c.lineTo(bx + bw * 1.15, by + 1); c.lineTo(bx - bw * 1.15, by + 1); c.closePath(); c.fill();
100
+ }
101
+ const [tx, ty] = pts[segs];
102
+ // coconuts
103
+ for (let i = 0; i < 3; i++) { c.beginPath(); c.arc(tx + (i - 1) * h * 0.025, ty + h * 0.03, h * 0.022, 0, 7); c.fill(); }
104
+ // fronds
105
+ const fr = 9;
106
+ for (let i = 0; i < fr; i++) {
107
+ const a = -Math.PI / 2 + (i / (fr - 1) - 0.5) * Math.PI * 1.55 + r.range(-0.12, 0.12);
108
+ const len = h * r.range(0.42, 0.6);
109
+ const droop = h * r.range(0.18, 0.32);
110
+ const ex = tx + Math.cos(a) * len, ey = ty + Math.sin(a) * len * 0.55 + droop;
111
+ const cx1 = tx + Math.cos(a) * len * 0.55, cy1 = ty + Math.sin(a) * len * 0.75 - h * 0.06;
112
+ // rib
113
+ c.lineWidth = h * 0.009; c.beginPath(); c.moveTo(tx, ty); c.quadraticCurveTo(cx1, cy1, ex, ey); c.stroke();
114
+ // leaflets along rib
115
+ const nL = 16;
116
+ for (let k = 1; k <= nL; k++) {
117
+ const u = k / (nL + 1);
118
+ const qx = (1 - u) * (1 - u) * tx + 2 * (1 - u) * u * cx1 + u * u * ex;
119
+ const qy = (1 - u) * (1 - u) * ty + 2 * (1 - u) * u * cy1 + u * u * ey;
120
+ const dx = 2 * (1 - u) * (cx1 - tx) + 2 * u * (ex - cx1), dy = 2 * (1 - u) * (cy1 - ty) + 2 * u * (ey - cy1);
121
+ const dl = Math.hypot(dx, dy), nx = -dy / dl, ny = dx / dl;
122
+ const ll = h * 0.11 * Math.sin(u * Math.PI) + h * 0.02;
123
+ for (const s of [-1, 1]) {
124
+ c.beginPath();
125
+ c.moveTo(qx, qy);
126
+ c.lineTo(qx + nx * ll * s * 0.6 + dx / dl * ll * 0.25, qy + ny * ll * s * 0.6 + Math.abs(ll) * 0.75 + dy / dl * ll * 0.25);
127
+ c.lineTo(qx + dx / dl * h * 0.018, qy + dy / dl * h * 0.018);
128
+ c.closePath(); c.fill();
129
+ }
130
+ }
131
+ }
132
+ c.restore();
133
+ }
134
+
135
+ // Classical bust (profile facing left) as path in local units; origin = crown, approx 420 tall
136
+ function bustPath(c) {
137
+ c.beginPath();
138
+ c.moveTo(0, 0);
139
+ c.bezierCurveTo(-30, -2, -58, 12, -68, 40); // forehead curve
140
+ c.bezierCurveTo(-72, 54, -72, 66, -76, 76); // brow
141
+ c.bezierCurveTo(-78, 80, -77, 84, -76, 88); // brow ridge
142
+ c.bezierCurveTo(-82, 100, -92, 112, -96, 122); // nose ridge
143
+ c.bezierCurveTo(-96, 128, -90, 130, -80, 130); // nose tip -> nostril
144
+ c.bezierCurveTo(-80, 134, -82, 138, -84, 142); // philtrum
145
+ c.bezierCurveTo(-86, 146, -82, 149, -80, 150); // upper lip
146
+ c.bezierCurveTo(-82, 153, -82, 157, -78, 160); // lower lip
147
+ c.bezierCurveTo(-74, 164, -78, 170, -78, 176); // under-lip
148
+ c.bezierCurveTo(-78, 188, -70, 198, -56, 200); // chin
149
+ c.bezierCurveTo(-40, 202, -28, 206, -22, 214); // jaw underside
150
+ c.bezierCurveTo(-24, 236, -30, 254, -36, 268); // neck front
151
+ c.bezierCurveTo(-70, 282, -120, 290, -150, 316); // collarbone to shoulder
152
+ c.bezierCurveTo(-172, 336, -180, 372, -176, 410); // shoulder down
153
+ c.bezierCurveTo(-120, 424, -40, 432, 30, 428); // bust base curve
154
+ c.bezierCurveTo(90, 424, 140, 414, 160, 404);
155
+ c.bezierCurveTo(162, 370, 150, 338, 118, 312); // back shoulder
156
+ c.bezierCurveTo(92, 292, 70, 276, 62, 250); // trapezius
157
+ c.bezierCurveTo(58, 230, 60, 214, 66, 200); // neck back
158
+ // curly hair along the back of head and crown
159
+ const curls = [[78, 182], [92, 158], [98, 130], [100, 100], [94, 72], [80, 46], [60, 24], [36, 8], [12, 0]];
160
+ let px = 66, py = 200;
161
+ for (const [qx, qy] of curls) {
162
+ const mx = (px + qx) / 2, my = (py + qy) / 2;
163
+ const nx = (qy - py), ny = -(qx - px), nl = Math.hypot(nx, ny);
164
+ c.quadraticCurveTo(mx - (nx / nl) * 14, my - (ny / nl) * 14, qx, qy);
165
+ px = qx; py = qy;
166
+ }
167
+ c.closePath();
168
+ }
169
+
170
+ function drawBust(c, ox, oy, s, seed) {
171
+ const nz = H.makeNoise(seed);
172
+ c.save();
173
+ c.translate(ox, oy); c.scale(s, s);
174
+ bustPath(c);
175
+ c.save(); c.clip();
176
+ // marble base: soft light from upper-left (pink sun rim from behind-right)
177
+ const g = c.createLinearGradient(-180, 0, 170, 60);
178
+ g.addColorStop(0, '#fbf6fb'); g.addColorStop(0.45, '#e6dcea'); g.addColorStop(0.8, '#a996c4'); g.addColorStop(1, '#6f5a9e');
179
+ c.fillStyle = g; c.fillRect(-200, -20, 400, 470);
180
+ // form shading
181
+ const sh = (x, y, r, col) => { const rg = c.createRadialGradient(x, y, 0, x, y, r); rg.addColorStop(0, col); rg.addColorStop(1, 'rgba(0,0,0,0)'); c.fillStyle = rg; c.fillRect(x - r, y - r, r * 2, r * 2); };
182
+ sh(-56, 94, 22, 'rgba(76,46,124,0.58)'); // eye socket
183
+ sh(-60, 132, 19, 'rgba(80,50,125,0.48)'); // under nose
184
+ sh(-66, 170, 14, 'rgba(80,50,125,0.48)'); // under lip
185
+ sh(-74, 76, 10, 'rgba(255,255,255,0.55)'); // brow ridge catch-light
186
+ sh(-88, 116, 9, 'rgba(255,255,255,0.6)'); // nose bridge catch-light
187
+ sh(-50, 196, 22, 'rgba(255,255,255,0.4)'); // chin highlight
188
+ sh(-82, 60, 22, 'rgba(255,255,255,0.6)'); // forehead highlight
189
+ sh(-30, 130, 40, 'rgba(255,255,255,0.45)'); // cheek highlight
190
+ sh(10, 120, 60, 'rgba(110,80,160,0.30)'); // temple/jaw turn
191
+ sh(-10, 250, 46, 'rgba(90,60,140,0.45)'); // under jaw / neck shadow
192
+ sh(-110, 360, 80, 'rgba(255,255,255,0.35)'); // chest highlight
193
+ sh(40, 420, 120, 'rgba(70,40,120,0.35)');
194
+ // ear
195
+ c.strokeStyle = 'rgba(100,70,150,0.6)'; c.lineWidth = 3;
196
+ c.beginPath(); c.ellipse(28, 118, 13, 22, 0.2, -1.2, 2.2); c.stroke();
197
+ // hair: sculpted snail curls in a cap
198
+ const r = H.rng(seed + 4);
199
+ c.save();
200
+ c.beginPath(); c.ellipse(30, 78, 82, 84, 0.15, 0, Math.PI * 2); c.clip();
201
+ for (let j = 0; j < 9; j++) for (let i = 0; i < 9; i++) {
202
+ const hx = -58 + i * 19 + (j % 2) * 9 + r.range(-3, 3), hy = -4 + j * 19 + r.range(-3, 3);
203
+ if (hx < -40 + Math.max(0, hy - 20) * 0.9 && hy > 18) continue; // keep the face clear
204
+ if (hx < 40 && hx > -10 && hy > 92) continue; // ear / cheek area
205
+ const rr = r.range(8, 11);
206
+ const cg = c.createRadialGradient(hx - rr * 0.35, hy - rr * 0.4, 1, hx, hy, rr * 1.1);
207
+ cg.addColorStop(0, 'rgba(255,255,255,0.75)'); cg.addColorStop(0.55, 'rgba(210,195,225,0.2)'); cg.addColorStop(1, 'rgba(90,60,140,0.55)');
208
+ c.fillStyle = cg; c.beginPath(); c.arc(hx, hy, rr, 0, 7); c.fill();
209
+ c.strokeStyle = 'rgba(90,60,140,0.55)'; c.lineWidth = 1.6;
210
+ c.beginPath(); for (let a = 0; a < 9; a += 0.3) { const q = rr * (1 - a / 10); const X = hx + Math.cos(a + i) * q * 0.8, Y = hy + Math.sin(a + i) * q * 0.8; a === 0 ? c.moveTo(X, Y) : c.lineTo(X, Y); } c.stroke();
211
+ }
212
+ c.restore();
213
+ // hairline edge shadow
214
+ c.strokeStyle = 'rgba(100,70,150,0.45)'; c.lineWidth = 5;
215
+ c.beginPath(); c.moveTo(-58, 20); c.quadraticCurveTo(-34, 30, -14, 74); c.quadraticCurveTo(0, 96, 6, 100); c.stroke();
216
+ // marble veins
217
+ c.lineCap = 'round';
218
+ for (let v = 0; v < 7; v++) {
219
+ let x = r.range(-170, 150), y = r.range(-10, 420);
220
+ c.strokeStyle = `rgba(120,100,150,${r.range(0.18, 0.4)})`; c.lineWidth = r.range(0.6, 1.8);
221
+ c.beginPath(); c.moveTo(x, y);
222
+ for (let k = 0; k < 40; k++) { const a = nz.n2(x * 0.01, y * 0.01 + v) * 3 + 0.6; x += Math.cos(a) * 7; y += Math.sin(a) * 7; c.lineTo(x, y); }
223
+ c.stroke();
224
+ }
225
+ // rim light from the sun (pink) along the back edge
226
+ c.globalCompositeOperation = 'source-atop';
227
+ c.restore();
228
+ c.save(); bustPath(c); c.clip();
229
+ c.lineWidth = 16; c.strokeStyle = 'rgba(255,120,200,0.55)'; c.filter = 'blur(6px)';
230
+ c.translate(-10, 0); bustPath(c); c.stroke(); c.filter = 'none';
231
+ c.restore();
232
+ // eye line + lips detail
233
+ c.strokeStyle = 'rgba(80,50,120,0.6)'; c.lineWidth = 2;
234
+ c.beginPath(); c.moveTo(-68, 90); c.quadraticCurveTo(-58, 84, -44, 90); c.stroke(); // upper lid
235
+ c.lineWidth = 1.4; c.beginPath(); c.moveTo(-66, 97); c.quadraticCurveTo(-56, 101, -46, 96); c.stroke();
236
+ c.fillStyle = 'rgba(70,40,110,0.3)'; c.beginPath(); c.ellipse(-57, 93, 6, 3, 0, 0, 7); c.fill(); // blank eye
237
+ c.lineWidth = 2.2; c.beginPath(); c.moveTo(-82, 151); c.quadraticCurveTo(-74, 154, -64, 150); c.stroke(); // mouth
238
+ c.beginPath(); c.moveTo(-84, 128); c.quadraticCurveTo(-74, 128, -70, 120); c.stroke(); // nostril wing
239
+ c.fillStyle = 'rgba(60,30,100,0.45)'; c.beginPath(); c.ellipse(-80, 127, 4, 2.2, 0.3, 0, 7); c.fill(); // nostril
240
+ c.restore();
241
+ // sculpted edges: a crisp lilac-white specular rim on the edges facing the key light (upper left) and a
242
+ // narrow core shadow on the edges turning away (lower right) -> reads as carved stone, not a soft blob
243
+ const cw = c.canvas.width, chh = c.canvas.height;
244
+ const edge = (dx, dy, col, a) => {
245
+ const E = H.layer(cw, chh), ec = E.getContext('2d');
246
+ ec.setTransform(c.getTransform()); ec.translate(ox, oy); ec.scale(s, s);
247
+ ec.fillStyle = col; bustPath(ec); ec.fill();
248
+ ec.globalCompositeOperation = 'destination-out'; ec.translate(dx, dy); bustPath(ec); ec.fill();
249
+ c.save(); c.setTransform(1, 0, 0, 1, 0, 0); c.globalAlpha = a; c.drawImage(E, 0, 0); c.restore();
250
+ };
251
+ edge(3.2, 2.4, '#fff6ff', 0.75);
252
+ edge(-2.6, -3.4, '#4a2a7e', 0.4);
253
+ }
254
+
255
+ // drawBust is static (fixed seed), so render it once per scale into a sprite and blit it.
256
+ const bustCache = {};
257
+ function bustSprite(s) {
258
+ const key = s.toFixed(3);
259
+ if (bustCache[key]) return bustCache[key];
260
+ const pad = 12, ox = pad + 190 * s, oy = pad + 12 * s;
261
+ const C = H.layer(Math.ceil(360 * s + pad * 2), Math.ceil(452 * s + pad * 2));
262
+ drawBust(C.getContext('2d'), ox, oy, s, 21);
263
+ return (bustCache[key] = { C, ox, oy });
264
+ }
265
+ function blitBust(c, bx, by, s) { const b = bustSprite(s); c.drawImage(b.C, bx - b.ox, by - b.oy); }
266
+
267
+ function pedestal(c, x, y, w, h, t) {
268
+ // front face checkerboard + top face in perspective
269
+ const n = 8, cw = w / n, chh = h / 4;
270
+ c.save();
271
+ for (let j = 0; j < 4; j++) for (let i = 0; i < n; i++) {
272
+ c.fillStyle = (i + j) % 2 ? '#111' : '#f4f0f6';
273
+ c.fillRect(x + i * cw, y + j * chh, cw + 0.5, chh + 0.5);
274
+ }
275
+ const d = 26;
276
+ c.beginPath(); c.moveTo(x, y); c.lineTo(x + d, y - d * 0.6); c.lineTo(x + w + d, y - d * 0.6); c.lineTo(x + w, y); c.closePath();
277
+ c.fillStyle = '#d8cfe6'; c.fill();
278
+ c.beginPath(); c.moveTo(x + w, y); c.lineTo(x + w + d, y - d * 0.6); c.lineTo(x + w + d, y + h - d * 0.6); c.lineTo(x + w, y + h); c.closePath();
279
+ c.fillStyle = '#3a2066'; c.fill();
280
+ // side checker hint
281
+ c.fillStyle = 'rgba(255,255,255,0.18)';
282
+ for (let j = 0; j < 4; j += 2) { c.beginPath(); c.moveTo(x + w, y + j * chh); c.lineTo(x + w + d, y + j * chh - d * 0.6); c.lineTo(x + w + d, y + (j + 1) * chh - d * 0.6); c.lineTo(x + w, y + (j + 1) * chh); c.fill(); }
283
+ c.restore();
284
+ }
285
+
286
+ // =====================================================================================
287
+ // EXPLAINER: told on a worn VHS tape. Scenes change with a tape cut (VCR blue screen + vertical roll),
288
+ // the persistent header is the VCR's on-screen menu, the counter is the tape counter, and the
289
+ // "model" is the marble bust. Pastel pop-up windows and neon floor chevrons carry the logic.
290
+ // =====================================================================================
291
+ const cl = H.clamp, EZ = H.ease;
292
+ const FP = (xw, z) => [640 + (xw / z) * 260, HZ + (1 / z) * 260]; // floor projection (matches floor())
293
+ function chroma(c, txt, x, y, font, col, off, align = 'left', spacing = '0px', legible = 0) {
294
+ c.save(); c.font = font; c.textAlign = align; c.textBaseline = 'alphabetic'; c.letterSpacing = spacing;
295
+ if (legible > 0) { // dark aubergine underlay so pale text holds on the pink haze and the checker plinth
296
+ c.lineJoin = 'round'; c.shadowColor = `rgba(30,0,60,${legible})`; c.shadowBlur = 14;
297
+ c.strokeStyle = `rgba(40,0,70,${0.5 * legible})`; c.lineWidth = 4 + 4 * legible;
298
+ for (let k = 0; k < 3; k++) c.strokeText(txt, x, y + 2);
299
+ c.shadowColor = 'transparent'; c.shadowBlur = 0;
300
+ }
301
+ c.fillStyle = '#00f0ff'; c.fillText(txt, x + off, y + off * 0.6);
302
+ c.fillStyle = '#ff2bd6'; c.fillText(txt, x - off, y - off * 0.4);
303
+ c.fillStyle = col; c.fillText(txt, x, y);
304
+ c.restore();
305
+ }
306
+ function osd(c, txt, x, y, px = 34, align = 'left') {
307
+ c.save(); c.font = `400 ${px}px "VT323"`; c.textAlign = align; c.fillStyle = '#fff'; c.shadowColor = 'rgba(0,0,0,0.7)'; c.shadowOffsetX = 2; c.shadowOffsetY = 2; c.fillText(txt, x, y); c.restore();
308
+ }
309
+ function silhouette(c, kind, x, y, s, col) {
310
+ c.save(); c.fillStyle = col; c.translate(x, y); c.scale(s, s); c.beginPath();
311
+ if (kind === 'cat') { c.moveTo(-20, -6); c.lineTo(-17, -30); c.lineTo(-5, -18); c.lineTo(5, -18); c.lineTo(17, -30); c.lineTo(20, -6); c.ellipse(0, 2, 21, 17, 0, 0, Math.PI); c.closePath(); c.fill(); c.fillStyle = '#fff'; c.beginPath(); c.ellipse(-8, -2, 3, 4, 0, 0, 7); c.ellipse(8, -2, 3, 4, 0, 0, 7); c.fill(); }
312
+ else { c.ellipse(0, 0, 15, 19, 0, 0, 7); c.fill(); c.beginPath(); c.ellipse(-17, 0, 7, 15, 0.35, 0, 7); c.ellipse(17, 0, 7, 15, -0.35, 0, 7); c.fill(); c.fillStyle = '#fff'; c.beginPath(); c.ellipse(-6, -4, 2.5, 3.5, 0, 0, 7); c.ellipse(6, -4, 2.5, 3.5, 0, 0, 7); c.fill(); c.fillStyle = '#1a0634'; c.beginPath(); c.ellipse(0, 8, 4, 3, 0, 0, 7); c.fill(); }
313
+ c.restore();
314
+ }
315
+ function polaroid(c, x, y, s, kind, label, rot = 0, a = 1) {
316
+ c.save(); c.globalAlpha = a; c.translate(x, y); c.rotate(rot); c.scale(s, s);
317
+ c.shadowColor = 'rgba(40,0,80,0.45)'; c.shadowBlur = 14; c.shadowOffsetY = 6;
318
+ c.fillStyle = '#fbf6ff'; c.fillRect(-46, -50, 92, 112); c.shadowColor = 'transparent';
319
+ const g = c.createLinearGradient(0, -42, 0, 34); g.addColorStop(0, kind === 'cat' ? '#ffb3e6' : '#9ff0ff'); g.addColorStop(1, kind === 'cat' ? '#c9a6ff' : '#ffd6a5');
320
+ c.fillStyle = g; c.fillRect(-38, -42, 76, 76);
321
+ silhouette(c, kind, 0, 0, 1, '#2a0b4c');
322
+ if (label) { c.font = '400 36px "VT323"'; c.textAlign = 'center'; c.fillStyle = '#2a0b4c'; c.fillText(label, 0, 59); }
323
+ c.restore();
324
+ }
325
+ function popup(c, x, y, w, h, title, a = 1) {
326
+ c.save(); c.globalAlpha = a;
327
+ c.fillStyle = 'rgba(40,0,80,0.35)'; c.fillRect(x + 8, y + 10, w, h);
328
+ c.fillStyle = '#e9ddff'; c.fillRect(x, y, w, h);
329
+ c.fillStyle = '#fff'; c.fillRect(x, y, w, 2); c.fillRect(x, y, 2, h); c.fillStyle = '#6a4a9a'; c.fillRect(x, y + h - 2, w, 2); c.fillRect(x + w - 2, y, 2, h);
330
+ const g = c.createLinearGradient(x, 0, x + w, 0); g.addColorStop(0, '#ff71ce'); g.addColorStop(1, '#01cdfe');
331
+ c.fillStyle = g; c.fillRect(x + 4, y + 4, w - 8, 26);
332
+ c.font = '400 24px "VT323"'; c.fillStyle = '#fff'; c.fillText(title, x + 12, y + 24);
333
+ c.fillStyle = '#e9ddff'; c.fillRect(x + w - 30, y + 7, 21, 20); c.fillStyle = '#2a0b4c'; c.font = '400 22px "VT323"'; c.fillText('x', x + w - 24, y + 23);
334
+ c.restore();
335
+ }
336
+ function neonLine(c, pts, prog, col, w = 3) {
337
+ let tot = 0; const L = []; for (let i = 0; i < pts.length - 1; i++) { const l = Math.hypot(pts[i + 1][0] - pts[i][0], pts[i + 1][1] - pts[i][1]); L.push(l); tot += l; }
338
+ let rem = tot * cl(prog); const out = [pts[0]]; let a = 0;
339
+ for (let i = 0; i < L.length && rem > 0; i++) { const u = Math.min(1, rem / L[i]); out.push([H.lerp(pts[i][0], pts[i + 1][0], u), H.lerp(pts[i][1], pts[i + 1][1], u)]); a = Math.atan2(pts[i + 1][1] - pts[i][1], pts[i + 1][0] - pts[i][0]); rem -= L[i]; }
340
+ c.save(); c.lineCap = 'round'; c.lineJoin = 'round'; c.shadowColor = col; c.shadowBlur = 16; c.strokeStyle = col; c.lineWidth = w;
341
+ c.beginPath(); c.moveTo(...out[0]); for (const p of out) c.lineTo(...p);
342
+ if (prog >= 1) { const [ex, ey] = out[out.length - 1]; c.moveTo(ex - Math.cos(a - 0.5) * 18, ey - Math.sin(a - 0.5) * 18); c.lineTo(ex, ey); c.lineTo(ex - Math.cos(a + 0.5) * 18, ey - Math.sin(a + 0.5) * 18); }
343
+ c.stroke(); c.shadowBlur = 0; c.strokeStyle = 'rgba(255,255,255,0.8)'; c.lineWidth = w * 0.35; c.stroke(); c.restore();
344
+ }
345
+ function bustLayer(W, H_, bx, by, bs, withPlinth) {
346
+ const L = H.layer(W, H_), lc = L.getContext('2d');
347
+ blitBust(lc, bx, by, bs);
348
+ if (withPlinth) pedestal(lc, bx - 175 * bs - 8, by + 428 * bs - 4, 336 * bs + 16, 150, 0);
349
+ return L;
350
+ }
351
+ function glitchDraw(ctx, L, W, H_, t, gl, seed) {
352
+ ctx.save(); ctx.globalAlpha = 0.4; ctx.globalCompositeOperation = 'lighter';
353
+ const tint = (col, dx) => { const T = H.layer(W, H_), tc = T.getContext('2d'); tc.drawImage(L, 0, 0); tc.globalCompositeOperation = 'source-in'; tc.fillStyle = col; tc.fillRect(0, 0, W, H_); ctx.drawImage(T, dx, 0); };
354
+ tint('#00e5ff', -10 * gl); tint('#ff2bd6', 9 * gl);
355
+ ctx.restore();
356
+ const gr = H.rng(Math.floor(t * 8) + seed); let y = 0;
357
+ while (y < H_) { const hgt = gr.int(4, 40); const sh = gr.chance(0.15 * Math.min(2, gl)) ? gr.range(-40, 40) * gl : 0; ctx.drawImage(L, 0, y, W, hgt, sh, y, W, hgt); y += hgt; }
358
+ }
359
+ // VHS playback pass shared by the still and the explainer.
360
+ // o: { roll, bandY, bandH, wobK, streakP, streakL, speck, desat, protect: [y0, y1, k] }
361
+ // Luma stays sharp; chroma is low-passed (VHS colour-under has ~1/8 the luma bandwidth), delayed to the right,
362
+ // with red and blue slightly misregistered. The bottom rows carry head-switching skew, like a real VCR.
363
+ const vbuf = {};
364
+ function vhsPass(ctx, W, H_, t, o) {
365
+ const img = ctx.getImageData(0, 0, W, H_), d = img.data, src = new Uint8ClampedArray(d);
366
+ const nz = H.makeNoise(31), tr = H.rng(Math.floor(t * 24) + 1), hr = H.rng(Math.floor(t * 24) + 77);
367
+ const { roll, bandY, bandH } = o;
368
+ if (!vbuf.W || vbuf.W !== W) { vbuf.W = W; vbuf.pY = new Float32Array(W + 1); vbuf.pR = new Float32Array(W + 1); vbuf.pB = new Float32Array(W + 1); }
369
+ const pY = vbuf.pY, pR = vbuf.pR, pB = vbuf.pB, CW = 5, CB = 0.42, HS = 9;
370
+ const pv = o.protect;
371
+ for (let yy = 0; yy < H_; yy++) {
372
+ const sy = (yy + roll) % H_;
373
+ if (roll > 0 && (yy + roll) % H_ > H_ - 22) { for (let xx = 0; xx < W; xx++) { const i = (yy * W + xx) * 4; d[i] = d[i + 1] = d[i + 2] = 8; } continue; }
374
+ const inBand = yy > bandY && yy < bandY + bandH;
375
+ let bk = inBand ? Math.sin(((yy - bandY) / bandH) * Math.PI) : 0;
376
+ if (pv && yy > pv[0] && yy < pv[1]) bk *= pv[2];
377
+ let wob = nz.n2(yy * 0.05, t * 2) * 2.2 + (inBand ? (tr() - 0.3) * o.wobK * bk : 0);
378
+ const hsw = yy >= H_ - HS ? (yy - (H_ - HS) + 1) / HS : 0;
379
+ if (hsw) wob -= 6 + hsw * hsw * 26 + hr() * 6;
380
+ const scan = (yy & 1) ? 0.9 : 1.0;
381
+ let s0 = -1, s1 = -1, sv = 0;
382
+ if (inBand && tr() < o.streakP * bk + 0.03 * Math.min(1, bk * 4)) { s0 = tr() * W; s1 = s0 + tr() * o.streakL * bk + 20; sv = tr() * 0.8 + 0.2; }
383
+ // row prefix sums of luma, red, blue for the chroma low-pass
384
+ const row = sy * W * 4;
385
+ for (let xx = 0; xx < W; xx++) {
386
+ const j = row + xx * 4, R = src[j], G = src[j + 1], B = src[j + 2];
387
+ pY[xx + 1] = pY[xx] + 0.299 * R + 0.587 * G + 0.114 * B; pR[xx + 1] = pR[xx] + R; pB[xx + 1] = pB[xx] + B;
388
+ }
389
+ const W1 = W - 1;
390
+ for (let xx = 0; xx < W; xx++) {
391
+ const i = (yy * W + xx) * 4;
392
+ const sx = Math.min(W1, Math.max(0, Math.round(xx - wob)));
393
+ const j = row + sx * 4;
394
+ // classic R/B misregistration (sharp, offset samples) ...
395
+ let R = src[row + Math.min(W1, sx + 3) * 4], G = src[j + 1], B = src[row + Math.max(0, sx - 3) * 4 + 2];
396
+ // ... plus colour-under bleed: chroma partly replaced by a low-passed, ~2px-delayed copy
397
+ const Y = 0.299 * R + 0.587 * G + 0.114 * B;
398
+ let c0 = sx - 2 - CW, c1 = sx - 2 + CW + 1; if (c0 < 0) c0 = 0; if (c1 > W) c1 = W; if (c1 <= c0) { c0 = 0; c1 = 1; }
399
+ const n = 1 / (c1 - c0), mY = (pY[c1] - pY[c0]) * n;
400
+ R += (((pR[c1] - pR[c0]) * n - mY) - (R - Y)) * CB;
401
+ B += (((pB[c1] - pB[c0]) * n - mY) - (B - Y)) * CB;
402
+ G = (Y - 0.299 * R - 0.114 * B) * 1.7036;
403
+ if (inBand) {
404
+ const nn = tr();
405
+ if (xx > s0 && xx < s1) { R = H.lerp(R, 245, sv); G = H.lerp(G, 240, sv); B = H.lerp(B, 255, sv); }
406
+ else if (nn < o.speck * bk + 0.004 * Math.min(1, bk * 4)) { R = G = B = 235; }
407
+ else if (o.desat) { const l = (R + G + B) / 3; R = H.lerp(R, l + 30, bk * 0.6); G = H.lerp(G, l + 30, bk * 0.6); B = H.lerp(B, l + 40, bk * 0.6); }
408
+ }
409
+ if (hsw && hr() < 0.08 * hsw) { R = G = B = 200; }
410
+ d[i] = R * scan; d[i + 1] = G * scan; d[i + 2] = B * scan;
411
+ }
412
+ }
413
+ ctx.putImageData(img, 0, 0);
414
+ }
415
+ function vhs(ctx, W, H_, t, roll, bandY, protect) {
416
+ vhsPass(ctx, W, H_, t, { roll, bandY, bandH: 60, wobK: 24, streakP: 0.3, streakL: 380, speck: 0.05, desat: false, protect });
417
+ }
418
+
419
+ function explainVW(ctx, W, H_, t) {
420
+ const CUT1 = 6.0, CUT2 = 10.4, CUT3 = 12.7;
421
+ const scene = t < CUT1 ? 0 : t < CUT2 ? 1 : t < CUT3 ? 2 : 3;
422
+ const cutT = [0, CUT1, CUT2, CUT3][scene];
423
+ const lt = t - cutT;
424
+ // world background (all scenes)
425
+ sky(ctx, W);
426
+ const r = H.rng(77);
427
+ for (let i = 0; i < 90; i++) { ctx.fillStyle = `rgba(255,230,255,${r.range(0.2, 0.8) * (0.6 + 0.4 * Math.sin(t * 3 + i))})`; ctx.fillRect(r.range(0, W), r.range(0, HZ * 0.55), 1.5, 1.5); }
428
+ const sunX = scene === 1 ? 300 : scene === 2 ? 640 : 470;
429
+ sun(ctx, sunX, HZ - 70, 210, t);
430
+ floor(ctx, W, H_, t);
431
+ if (scene !== 2) {
432
+ palm(ctx, 175, HZ + 40, 260, -0.12, 8, '#2a0b4c'); palm(ctx, 1120, HZ + 50, 250, 0.16, 11, '#2a0b4c');
433
+ palm(ctx, 70, HZ + 120, 400, 0.28, 3, '#1a0634'); palm(ctx, 1225, HZ + 140, 430, -0.3, 5, '#1a0634');
434
+ }
435
+
436
+ if (scene === 0) {
437
+ // bust + plinth rise up through the floor as "the model"
438
+ const rise = EZ.out(cl((t - 2.0) / 0.8));
439
+ if (rise > 0) {
440
+ const L = bustLayer(W, H_, 860, 132 + (1 - rise) * 420, 0.86, true);
441
+ glitchDraw(ctx, L, W, H_, t, 0.6 + 2.5 * (1 - rise) + 0.3 * Math.sin(t * 5), 5);
442
+ }
443
+ // neon chevrons on the floor pointing at the bust
444
+ if (t > 2.5) for (let k = 0; k < 4; k++) {
445
+ const xw = -2.2 + k * 0.9 + ((t * 0.9) % 0.9), z = 1.35;
446
+ const [x, y] = FP(xw, z); const a = cl((t - 2.5 - k * 0.1) / 0.2);
447
+ ctx.save(); ctx.globalAlpha = a; ctx.shadowColor = '#00f0ff'; ctx.shadowBlur = 14; ctx.strokeStyle = '#7ff8ff'; ctx.lineWidth = 6; ctx.lineCap = 'round';
448
+ ctx.beginPath(); ctx.moveTo(x - 18, y - 22); ctx.lineTo(x + 6, y); ctx.lineTo(x - 18, y + 22); ctx.stroke(); ctx.restore();
449
+ }
450
+ if (rise >= 1) { // caption on a dark OSD-style plate so it separates from the checker plinth
451
+ ctx.save(); ctx.font = '700 36px "Noto Serif JP"'; const tw = ctx.measureText('THE MODEL モデル').width, lx = 848;
452
+ ctx.fillStyle = 'rgba(20,0,40,0.62)'; ctx.strokeStyle = 'rgba(255,190,240,0.45)'; ctx.lineWidth = 1.5;
453
+ ctx.beginPath(); ctx.roundRect(lx - tw / 2 - 12, 668 - 36, tw + 24, 50, 8); ctx.fill(); ctx.stroke(); ctx.restore();
454
+ chroma(ctx, 'THE MODEL モデル', lx, 668, '700 36px "Noto Serif JP"', '#fff4fb', 3, 'center', '0px', 0.6);
455
+ }
456
+ // labelled polaroids slide in along the floor and vanish into the bust
457
+ if (t > 2.6) for (let k = 0; k < 12; k++) {
458
+ const t0 = 2.6 + k * 0.3, p = (t - t0) / 1.1; if (p < 0 || p > 1) continue;
459
+ const x = H.lerp(80, 830, EZ.inOut(p)), y = H.lerp(470, 300, EZ.inOut(p)) - Math.sin(p * Math.PI) * 60;
460
+ const kind = k % 2 ? 'dog' : 'cat';
461
+ polaroid(ctx, x, y, H.lerp(1.7, 0.45, p), kind, kind, Math.sin(k * 1.7) * 0.15, cl((1 - p) * 5));
462
+ }
463
+ const n = 1e6 * EZ.inOut(cl((t - 2.7) / 2.9));
464
+ if (t > 2.6) { osd(ctx, Math.round(n).toLocaleString('en-US'), 40, H_ - 74, 80); osd(ctx, 'LABELLED EXAMPLES', 44, H_ - 30, 40); }
465
+ }
466
+ if (scene === 1) {
467
+ // close-up: bust on the left, test polaroid, pastel guess.exe window
468
+ const L = bustLayer(W, H_, 470, 70, 1.25, false);
469
+ const burst = (t > 8.0 && t < 8.25) ? 2.4 : 0;
470
+ glitchDraw(ctx, L, W, H_, t, 0.5 + burst + (lt < 0.3 ? 2 : 0), 9);
471
+ polaroid(ctx, 150, 500, 1.5, 'cat', '?', -0.12);
472
+ neonLine(ctx, [[225, 420], [300, 360]], cl((lt - 0.4) / 0.3), '#7ff8ff');
473
+ const step = t < 7.0 ? -1 : t < 7.9 ? 0 : t < 8.5 ? 1 : t < 9.6 ? 2 : t < 10.0 ? 0 : 1;
474
+ const pa = cl((t - 6.9) / 0.15);
475
+ if (pa > 0) {
476
+ neonLine(ctx, [[560, 230], [680, 200]], cl((t - 6.7) / 0.25), '#7ff8ff');
477
+ popup(ctx, 690, 100, 540, 320, 'guess.exe', pa);
478
+ ctx.save(); ctx.globalAlpha = pa;
479
+ if (t < 9.6) {
480
+ chroma(ctx, 'dog?', 800, 250, '400 110px "VT323"', '#2a0b4c', 2);
481
+ if (t > 7.9) {
482
+ chroma(ctx, '✕', 1040, 252, '400 120px "VT323"', '#ff2b6a', 4 + burst * 4);
483
+ ctx.font = '400 46px "VT323"'; ctx.fillStyle = '#c0004a'; ctx.fillText("check: it's a CAT", 740, 312);
484
+ ctx.strokeStyle = '#ff2b6a'; ctx.lineWidth = 4; ctx.beginPath(); ctx.ellipse(890, 215, 130, 62, -0.05, -1.6, -1.6 + cl((t - 7.95) / 0.3) * 6.3); ctx.stroke();
485
+ }
486
+ } else {
487
+ chroma(ctx, 'cat', 800, 250, '400 110px "VT323"', '#2a0b4c', 2);
488
+ chroma(ctx, '✓', 1010, 252, '400 120px "VT323"', '#00c896', 3);
489
+ ctx.font = '400 46px "VT323"'; ctx.fillStyle = '#008a66'; ctx.fillText('correct!', 740, 312);
490
+ }
491
+ // weights: pastel EQ sliders
492
+ ctx.font = '400 38px "VT323"'; ctx.fillStyle = '#6a4a9a'; ctx.fillText('weights', 720, 384);
493
+ const adj = EZ.inOut(cl((t - 8.9) / 0.6));
494
+ [[0.2, 0.7], [0.8, 0.35], [0.45, 0.9]].forEach(([a, b], i) => {
495
+ const x = 860 + i * 115, v = H.lerp(a, b, adj);
496
+ ctx.fillStyle = '#c9b6f0'; ctx.fillRect(x, 370, 90, 8);
497
+ ctx.fillStyle = t > 8.85 && t < 9.6 ? '#ff2bd6' : '#01cdfe'; ctx.fillRect(x + v * 74, 356, 16, 36);
498
+ });
499
+ ctx.restore();
500
+ }
501
+ if (t > 8.5 && t < 9.6) { // error pulse back into the bust
502
+ neonLine(ctx, [[760, 360], [650, 420], [560, 330]], cl((t - 8.5) / 0.4), '#ff2bd6', 4);
503
+ osd(ctx, 'ERROR', 600, 468, 48);
504
+ }
505
+ // loop pills
506
+ const labs = ['GUESS', 'CHECK', 'ADJUST'];
507
+ labs.forEach((lab, i) => {
508
+ const x = 700 + i * 180, y = 462, act = i === step;
509
+ ctx.save(); ctx.fillStyle = act ? '#ff71ce' : 'rgba(40,0,80,0.6)'; ctx.strokeStyle = '#fff'; ctx.lineWidth = 2;
510
+ ctx.beginPath(); ctx.roundRect(x, y, 150, 56, 28); ctx.fill(); ctx.stroke();
511
+ ctx.font = '400 44px "VT323"'; ctx.textAlign = 'center'; ctx.fillStyle = '#fff'; ctx.fillText(lab, x + 75, y + 41); ctx.restore();
512
+ if (i < 2) { ctx.save(); ctx.fillStyle = '#fff'; ctx.font = '400 44px "VT323"'; ctx.fillText('›', x + 157, y + 41); ctx.restore(); }
513
+ });
514
+ neonLine(ctx, [[1170, 520], [1170, 552], [775, 552], [775, 524]], 1, '#b9a2ff', 3);
515
+ osd(ctx, 'LOOP - again and again', 970, 594, 36, 'center');
516
+ }
517
+ if (scene === 2) {
518
+ // chart drawn across the sky, number with chroma split
519
+ const gx0 = 110, gx1 = 760, gy0 = 150, gy1 = 440;
520
+ ctx.save(); ctx.fillStyle = 'rgba(30,5,60,0.55)'; ctx.fillRect(gx0 - 20, gy0 - 30, gx1 - gx0 + 40, gy1 - gy0 + 50); ctx.strokeStyle = 'rgba(255,255,255,0.45)'; ctx.lineWidth = 1.5;
521
+ for (let i = 0; i <= 4; i++) { ctx.beginPath(); ctx.moveTo(gx0, H.lerp(gy1, gy0, i / 4)); ctx.lineTo(gx1, H.lerp(gy1, gy0, i / 4)); ctx.stroke(); }
522
+ ctx.restore();
523
+ osd(ctx, 'TRAINING ROUNDS →', gx0, gy1 + 50, 42);
524
+ const pr = cl((lt - 0.45) / 1.2), N = 20, val = (u) => 61 + 36 * (1 - Math.pow(1 - u, 2.2));
525
+ const P = (u) => [H.lerp(gx0, gx1, u), H.lerp(gy1, gy0, (val(u) - 50) / 50)];
526
+ if (pr > 0) { const pts = []; for (let i = 0; i <= N * pr; i++) pts.push(P(i / N)); pts.push(P(pr)); neonLine(ctx, pts, 1, '#ff71ce', 8); for (let i = 0; i <= N * pr; i += 4) { const [x, y] = P(i / N); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(x, y, 6, 0, 7); ctx.fill(); } }
527
+ const acc = Math.round(val(pr));
528
+ chroma(ctx, 'ACCURACY せいど', 830, 196, '700 38px "Noto Serif JP"', '#fff4fb', 2, 'left', '0px', 0.8);
529
+ const hit = lt > 1.7;
530
+ chroma(ctx, acc + '%', 830, 330, '900 130px "Noto Serif JP"', '#fff4fb', hit ? 6 + 4 * Math.sin(t * 30) : 3);
531
+ if (hit) {
532
+ ctx.save(); ctx.strokeStyle = '#7ff8ff'; ctx.shadowColor = '#00f0ff'; ctx.shadowBlur = 18; ctx.lineWidth = 4;
533
+ ctx.beginPath(); ctx.ellipse(985, 285, 190, 92, -0.05, -1.6, -1.6 + cl((lt - 1.7) / 0.3) * 6.3); ctx.stroke(); ctx.restore();
534
+ osd(ctx, 'up from 61%', 850, 420, 46);
535
+ }
536
+ }
537
+ if (scene === 3) {
538
+ const L = bustLayer(W, H_, 1010, 180, 0.76, true);
539
+ glitchDraw(ctx, L, W, H_, t, 0.5, 13);
540
+ const n1 = cl(Math.floor((lt - 0.15) / 0.03), 0, 13), n2 = cl(Math.floor((lt - 0.6) / 0.03), 0, 14);
541
+ chroma(ctx, 'MORE EXAMPLES'.slice(0, n1), 60, 330, '600 56px "Inter"', '#fff4fb', 3, 'left', '6px', 0.7);
542
+ chroma(ctx, '→ BETTER GUESSES'.slice(0, n2 + 2), 60, 420, '600 56px "Inter"', '#fff4fb', 3, 'left', '6px', 0.7);
543
+ if (lt > 1.1) chroma(ctx, 'もっとデータ・もっとかしこい', 64, 484, '700 32px "Noto Serif JP"', '#ffe9f6', 2, 'left', '0px', 1);
544
+ }
545
+ // ---- header: question glitches in big, then becomes the VCR on-screen menu line ----
546
+ if (t < 2.1) {
547
+ const k = cl(Math.floor((t - 0.3) / 0.035), 0, 18);
548
+ const jitter = t > 1.75 ? 30 * Math.sin(t * 90) : 0;
549
+ chroma(ctx, 'HOW DOES AI LEARN?'.slice(0, k), 640 + jitter, 300, '600 58px "Inter"', '#fff4fb', 5, 'center', '12px', 0.6);
550
+ if (k >= 18) chroma(ctx, 'エーアイはどうまなぶ?', 640, 360, '700 30px "Noto Serif JP"', '#ffe9f6', 3, 'center', '0px', 1);
551
+ } else {
552
+ osd(ctx, '▶ HOW DOES AI LEARN?', 40, 58, 36);
553
+ osd(ctx, ['CH 1 — TRAINING DATA', 'CH 2 — GUESS/CHECK/ADJUST', 'CH 3 — PROGRESS', 'CH 4 — TAKEAWAY'][scene], 40, 94, 30);
554
+ }
555
+ // ---- tape cut transition: blue screen + roll ----
556
+ let roll = 0;
557
+ const sinceCut = t - cutT;
558
+ if (scene > 0 && sinceCut < 0.5) roll = Math.round((1 - EZ.out(cl((sinceCut - 0.12) / 0.38))) * H_ * 0.85);
559
+ const bandY = ((t * 0.21 + 0.3) % 1) * (H_ + 160) - 80;
560
+ // keep the takeaway readable: once it has typed on, the tracking band only grazes the two headline rows
561
+ const prot = scene === 3 ? [262, 446, 1 - 0.85 * cl((lt - 0.6) / 0.25)] : null;
562
+ vhs(ctx, W, H_, t, roll, bandY, prot);
563
+ if (scene > 0 && sinceCut < 0.12) {
564
+ ctx.fillStyle = '#1f33d6'; ctx.fillRect(0, 0, W, H_);
565
+ const nr = H.rng(Math.floor(t * 24) + 40);
566
+ for (let i = 0; i < 50; i++) { ctx.fillStyle = `rgba(255,255,255,${nr.range(0.05, 0.4)})`; ctx.fillRect(0, nr.range(0, H_), W, nr.range(1, 4)); }
567
+ osd(ctx, '▶▶', W - 120, 58, 40);
568
+ }
569
+ H.grain(ctx, W, H_, 0.07, Math.floor(t * 24) + 9, true);
570
+ ctx.save(); ctx.font = '400 34px "VT323"'; ctx.fillStyle = '#fff'; ctx.shadowColor = 'rgba(0,0,0,0.6)'; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2;
571
+ ctx.fillText('PLAY ▶', W - 170, 58);
572
+ const secs = 1312 + Math.floor(t);
573
+ ctx.fillText(`SP 0:${String(Math.floor(secs / 60)).padStart(2, '0')}:${String(secs % 60).padStart(2, '0')}`, W - 230, H_ - 34);
574
+ ctx.restore();
575
+ H.vignette(ctx, W, H_, 0.45, '20,0,40');
576
+ }
577
+
578
+ // ---- SOUND: a worn VHS tape. Hiss + VCR motor under a slow dreamy maj7 pad; scene cuts are tape cuts (the pad
579
+ // slows to a stop, the VCR mechanism clunks, a fast-forward whir under the blue screen, the picture rolls into lock).
580
+ // Soft Rhodes-like bell notes for things landing. Times copy explainVW (CUT1/2/3 and the per-scene lt offsets).
581
+ function vwSound(k) {
582
+ const END = 15, CUT1 = 6.0, CUT2 = 10.4, CUT3 = 12.7;
583
+ // soft electric-piano bell: sine + quiet 2nd partial, long reverb tail. Stays below ~1.5 kHz.
584
+ const ep = (t, n, g = 1, pan = 0, minor = false) => {
585
+ k.tone(t, { f: n, dur: 1.4, gain: 0.12 * g, a: 0.006, verb: 0.45, pan, minor });
586
+ k.tone(t, { f: k.hz(n) * 2, dur: 0.4, gain: 0.03 * g, a: 0.004, verb: 0.3, pan, minor });
587
+ };
588
+ // tape tracking crackle: short band of hissy static
589
+ const crackle = (t, d, g = 1, minor = true) => k.noise(t, { dur: d, filter: 'bandpass', f: [[0, 2400], [d, 1400]], q: 0.7, gain: 0.07 * g, a: 0.004, minor });
590
+ // tape cut at c: pad slows to a stop, mechanism clunk, FF whir under the blue screen (0.12 s), roll settles (to 0.5 s)
591
+ const cut = (c, root) => {
592
+ [root, k.hz(root) * 1.5].forEach((f, i) => k.tone(c - 0.28, { f: [[0, f], [0.28, k.hz(f) * 0.3]], type: 'sawtooth', dur: 0.3, gain: 0.07 - i * 0.02, a: 0.01, hold: 0.18, filter: 'lowpass', f2: [[0, 1200], [0.28, 300]] }));
593
+ k.play('thud', c, { f: 85, gain: 0.45 });
594
+ k.noise(c, { dur: 0.015, filter: 'bandpass', f: 1300, q: 2, gain: 0.3, a: 0.0005 });
595
+ k.noise(c, { dur: 0.14, filter: 'bandpass', f: [[0, 1400], [0.12, 2200]], q: 3, gain: 0.1, a: 0.01, hold: 0.09 }); // fast-forward whir
596
+ k.noise(c + 0.12, { dur: 0.4, color: 'pink', filter: 'lowpass', f: [[0, 1800], [0.38, 250]], gain: 0.09, a: 0.02 }); // roll sliding down
597
+ k.noise(c + 0.5, { dur: 0.06, color: 'brown', filter: 'lowpass', f: 400, gain: 0.25, a: 0.002, minor: true }); // lock
598
+ };
599
+ // a neon line drawing on: the tube strikes (small zap) and hums while it draws
600
+ const neonDraw = (t, d, g = 1, pan = 0, minor = true) => {
601
+ k.noise(t, { dur: 0.03, filter: 'bandpass', f: 1200, q: 1.5, gain: 0.12 * g, a: 0.001, pan, minor });
602
+ k.tone(t, { f: 120, type: 'sawtooth', dur: d + 0.1, gain: 0.07 * g, a: 0.01, hold: d * 0.7, filter: 'bandpass', f2: 700, q: 1.2, pan, minor });
603
+ k.play('neon', t, { dur: d, gain: 0.8 * g, pan, minor });
604
+ };
605
+ // counter ticks: one tick each time value advances by `step`, never closer than 1/12 s (so it slows as it settles)
606
+ const counter = (t0, t1, fn, step, play) => {
607
+ let last = -1, lastV = fn(t0);
608
+ for (let t = t0; t <= t1 + 1e-6; t += 1 / 120) { const v = fn(t); if (v - lastV >= step && t - last >= 1 / 12) { play(t); last = t; lastV = v; } }
609
+ };
610
+
611
+ // ---- beds + music: tape hiss and the VCR motor; pad per scene, each one stopped by the tape cut
612
+ k.bed('hiss', 0, END, { gain: 1.6 });
613
+ k.bed('hum', 0, END, { gain: 0.35 });
614
+ const pad = (t0, t1, chords) => k.music('pad', t0, t1, { bpm: 60, gain: 0.55, chords });
615
+ pad(0, CUT1 - 0.25, [['F3', 'A3', 'C4', 'E4'], ['E3', 'G3', 'B3', 'D4']]);
616
+ pad(CUT1 + 0.12, CUT2 - 0.25, [['D3', 'F3', 'A3', 'C4'], ['G3', 'B3', 'D4', 'F#4']]);
617
+ pad(CUT2 + 0.12, CUT3 - 0.25, [['Bb3', 'D4', 'F4', 'A4']]);
618
+ pad(CUT3 + 0.12, END, [['F3', 'A3', 'C4', 'E4']]);
619
+
620
+ // ---- scene 0: question glitches in letter by letter (0.3 + k*0.035), subtitle lands, header snaps to the VCR OSD
621
+ for (let i = 0; i < 18; i += 3) crackle(0.3 + i * 0.035, 0.05, 0.8);
622
+ ep(0.3 + 18 * 0.035, 'C5', 0.9); ep(0.3 + 18 * 0.035 + 0.09, 'E5', 0.6);
623
+ crackle(1.75, 0.35, 1.4, false); // title shakes (jitter 1.75..2.1)
624
+ k.tone(1.75, { f: [[0, 220], [0.17, 180], [0.35, 230]], type: 'triangle', dur: 0.36, gain: 0.05, a: 0.02 }); // tape wobble
625
+ k.noise(2.1, { dur: 0.012, filter: 'bandpass', f: 1600, q: 2, gain: 0.2, a: 0.0005 }); // OSD header clicks on
626
+ // the bust rises through the floor (2.0..2.8, ease-out), heavily glitched, and its caption lands at 2.8
627
+ k.noise(2.0, { dur: 0.75, color: 'brown', filter: 'lowpass', f: [[0, 120], [0.6, 600]], gain: 0.3, a: 0.05 });
628
+ crackle(2.0, 0.6, 1.2, false);
629
+ k.play('thud', 2.62, { f: 70, gain: 0.5 });
630
+ ep(2.8, 'A4', 1); ep(2.8, 'E5', 0.6);
631
+ // polaroids (k = 0..11, t0 = 2.6 + 0.3k, 1.1 s each) fade into the bust: one soft bell per card, a pentatonic walk
632
+ const PENT = ['F4', 'A4', 'C5', 'G4', 'D5', 'A4', 'C5', 'E5', 'G4'];
633
+ for (let i = 0; i < 12; i++) { const t = 2.6 + i * 0.3 + 0.95; if (t < CUT1 - 0.3) ep(t, PENT[i % PENT.length], 0.55, 0.3, true); }
634
+ // tape-counter digits 0 -> 1,000,000 (2.7..5.6, ease-in-out): mechanical counter clicks
635
+ counter(2.7, 5.6, (t) => EZ.inOut(cl((t - 2.7) / 2.9)), 1 / 40, (t) => k.noise(t, { dur: 0.012, filter: 'bandpass', f: 1900, q: 3, gain: 0.16, a: 0.0005, pan: -0.5, minor: true }));
636
+ k.noise(5.6, { dur: 0.03, color: 'brown', filter: 'lowpass', f: 600, gain: 0.3, a: 0.001, pan: -0.5 }); ep(5.6, 'C5', 0.7, -0.4);
637
+
638
+ // ---- CUT1, scene 1: guess.exe
639
+ cut(CUT1, 'F3');
640
+ neonDraw(CUT1 + 0.4, 0.3, 1, -0.3); // arrow out of the test polaroid
641
+ neonDraw(6.7, 0.25, 1, 0.2); // arrow to the pop-up
642
+ ep(6.9, 'G4', 0.8, 0.4); ep(6.98, 'D5', 0.7, 0.4); // guess.exe opens: "dog?"
643
+ k.noise(7.0, { dur: 0.012, filter: 'bandpass', f: 1500, q: 2, gain: 0.16, a: 0.0005, minor: true }); // GUESS pill lights
644
+ // CHECK (7.9): wrong. the tape warps down, a sour low chord, then the glitch burst (8.0..8.25)
645
+ const WRONG = 7.9;
646
+ [['E4', 0], ['Bb3', 0], ['F3', 0]].forEach(([n]) => k.tone(WRONG, { f: [[0, n], [0.5, k.hz(n) * 0.82]], type: 'sawtooth', dur: 0.6, gain: 0.06, a: 0.01, hold: 0.3, filter: 'lowpass', f2: 900, pan: 0.3 }));
647
+ k.play('thud', WRONG, { f: 90, gain: 0.6, pan: 0.3 });
648
+ crackle(8.0, 0.25, 2, false);
649
+ // ADJUST (8.5): pink error pulse flows back into the bust (right -> left), then the sliders glide (8.9..9.5)
650
+ k.noise(8.5, { dur: 0.012, filter: 'bandpass', f: 1500, q: 2, gain: 0.16, a: 0.0005, minor: true });
651
+ k.tone(8.5, { f: [[0, 620], [0.4, 260]], type: 'triangle', dur: 0.45, gain: 0.08, a: 0.03, hold: 0.3, pan: 0.4, verb: 0.3 });
652
+ neonDraw(8.5, 0.4, 0.9, -0.1, false);
653
+ k.withMinor(() => [0, 1, 2].forEach((i) => k.tone(8.9 + i * 0.04, { f: [[0, 300 + i * 40], [0.6, 420 + i * 60]], type: 'sine', dur: 0.62, gain: 0.04, a: 0.2, hold: 0.2, pan: 0.2 + i * 0.2 })));
654
+ // GUESS again -> "cat ✓ correct!" (9.6): dreamy maj7 arpeggio
655
+ ['F4', 'A4', 'C5', 'E5'].forEach((n, i) => ep(9.6 + i * 0.07, n, 1, 0.3));
656
+ k.noise(10.0, { dur: 0.012, filter: 'bandpass', f: 1500, q: 2, gain: 0.16, a: 0.0005, minor: true }); // CHECK pill
657
+
658
+ // ---- CUT2, scene 2: neon chart across the sky. Line draws 10.85..12.05; dots at every 4th of 20 points
659
+ cut(CUT2, 'D3');
660
+ const L2 = CUT2 + 0.45;
661
+ neonDraw(L2, 1.2, 0.6, -0.2);
662
+ for (let i = 0; i <= 20; i += 4) ep(L2 + 1.2 * i / 20, ['C4', 'E4', 'G4', 'A4', 'C5', 'D5'][i / 4], 0.5, -0.4 + i * 0.03, true);
663
+ const val = (u) => 61 + 36 * (1 - Math.pow(1 - u, 2.2));
664
+ counter(L2, L2 + 1.2, (t) => Math.round(val(cl((t - L2) / 1.2))), 1, (t) => k.noise(t, { dur: 0.012, filter: 'bandpass', f: 1900, q: 3, gain: 0.14, a: 0.0005, pan: 0.4, minor: true }));
665
+ // 97% (lt > 1.7): the biggest moment. deep tape-thump, lush chord swell, chroma shimmer as a fast tremolo shimmer
666
+ const BEST = CUT2 + 1.7 + 1e-3;
667
+ k.play('impact', BEST, { f: 80, gain: 0.9 });
668
+ ['F3', 'C4', 'E4', 'G4', 'A4'].forEach((n, i) => {
669
+ k.tone(BEST, { f: n, type: 'sawtooth', dur: 1.6, gain: 0.05, a: 0.03, hold: 0.5, filter: 'lowpass', f2: [[0, 2200], [1.4, 700]], verb: 0.5, pan: (i - 2) * 0.3 });
670
+ k.tone(BEST, { f: n, type: 'sawtooth', detune: 12, dur: 1.6, gain: 0.04, a: 0.03, hold: 0.5, filter: 'lowpass', f2: 1500, verb: 0.5, pan: (2 - i) * 0.3 });
671
+ });
672
+ ep(BEST + 0.05, 'C5', 1.1); ep(BEST + 0.2, 'G5', 0.7);
673
+
674
+ // ---- CUT3, scene 3: takeaway glitches in (line 1 from 12.85, line 2 from 13.3), Japanese subtitle lands at 13.8
675
+ cut(CUT3, 'Bb3');
676
+ for (let i = 0; i < 13; i += 3) crackle(CUT3 + 0.15 + i * 0.03, 0.05, 0.8);
677
+ for (let i = 0; i < 14; i += 3) crackle(CUT3 + 0.6 + i * 0.03, 0.05, 0.8);
678
+ const TAKE = CUT3 + 1.1 + 1e-3;
679
+ ['F4', 'C5', 'E5', 'A5'].forEach((n, i) => ep(TAKE + i * 0.1, n, 0.9 - i * 0.1));
680
+ k.tone(TAKE, { f: 'F2', type: 'triangle', dur: 1.8, gain: 0.16, a: 0.05, hold: 0.6 });
681
+ }
682
+
683
+ registerStyle({
684
+ id: 'vaporwave',
685
+ name: 'Vaporwave',
686
+ category: 'Retro UI & Neon',
687
+ description: 'Pastel-neon dreamscape: sliced sunset sun, glowing grid floor, a glitching marble bust on a checkerboard plinth, palm silhouettes, katakana, and a chewed-up VHS tape finish.',
688
+ motion: 'Tape starts on a blue VCR screen and rolls into lock, the striped sun rises, palms slide in, the bust glitches into existence slice by slice, katakana stamps on with chroma split and a tracking band crawls down.',
689
+ fonts: ['900 74px "Noto Serif JP"', '700 20px "Noto Serif JP"', '400 30px "VT323"', '600 22px "Inter"', '900 130px "Noto Serif JP"', '700 30px "Noto Serif JP"', '600 58px "Inter"'],
690
+ frameT: 4.6,
691
+ duration: 5.5,
692
+ explainerDuration: 15,
693
+ explainerNote: 'Told on a worn VHS tape: scenes change with a tape cut (VCR blue screen, fast-forward, vertical roll), the header lives on as the VCR on-screen menu, the counter is tape-counter digits, labelled polaroids slide along the neon floor into the marble-bust "model", a pastel guess.exe pop-up does the guessing and the chart is drawn in neon across the sky.',
694
+ explainer(ctx, W, H_, t) { explainVW(ctx, W, H_, t); },
695
+ explainerSound(k) { vwSound(k); },
696
+ readsPixels: true,
697
+ draw(ctx, W, H_, t) {
698
+ const r = H.rng(77);
699
+ sky(ctx, W);
700
+ // faint stars
701
+ for (let i = 0; i < 90; i++) { ctx.fillStyle = `rgba(255,230,255,${r.range(0.2, 0.8) * (0.6 + 0.4 * Math.sin(t * 3 + i))})`; ctx.fillRect(r.range(0, W), r.range(0, HZ * 0.55), 1.5, 1.5); }
702
+ sun(ctx, 470, HZ - 70 + (1 - H.ease.out(H.clamp((t - 0.5) / 1.6))) * 260, 210, t);
703
+ floor(ctx, W, H_, t);
704
+
705
+ // palms
706
+ const slA = (1 - H.ease.out(H.clamp((t - 0.9) / 1.0))) * 360, slB = (1 - H.ease.out(H.clamp((t - 1.05) / 1.0))) * 260;
707
+ palm(ctx, 175 - slB, HZ + 40, 260, -0.12, 8, '#2a0b4c');
708
+ palm(ctx, 1120 + slB, HZ + 50, 250, 0.16, 11, '#2a0b4c');
709
+ palm(ctx, 70 - slA, HZ + 120, 400, 0.28, 3, '#1a0634');
710
+ palm(ctx, 1225 + slA, HZ + 140, 430, -0.3, 5, '#1a0634');
711
+
712
+ // bust on plinth, rendered to layer for glitching
713
+ const L = H.layer(W, H_), lc = L.getContext('2d');
714
+ const bx = 840, by = 112, bs = 0.96;
715
+ blitBust(lc, bx, by, bs);
716
+ const plY = by + 428 * bs - 4;
717
+ pedestal(lc, bx - 175 * bs - 8, plY, 336 * bs + 16, 150, t);
718
+
719
+ // glitch composite: chroma ghosts + horizontal slice displacement (bust glitches into existence at T_B)
720
+ const T_B = 1.8;
721
+ const shock = t >= T_B ? Math.exp(-(t - T_B) * 3.2) : 0;
722
+ const burst = t > 4.0 && t < 4.17 ? 1.2 : 0;
723
+ const gl = 0.6 + 0.4 * Math.sin(t * 5.3) + shock * 3.5 + burst;
724
+ if (t >= T_B) {
725
+ ctx.save();
726
+ ctx.globalAlpha = 0.42; ctx.globalCompositeOperation = 'lighter';
727
+ const tint = (col, dx) => { const T = H.layer(W, H_), tc = T.getContext('2d'); tc.drawImage(L, 0, 0); tc.globalCompositeOperation = 'source-in'; tc.fillStyle = col; tc.fillRect(0, 0, W, H_); ctx.drawImage(T, dx, 0); };
728
+ tint('#00e5ff', -14 * gl); tint('#ff2bd6', 12 * gl);
729
+ ctx.restore();
730
+ const gr = H.rng(Math.floor(t * 8) + 5);
731
+ let y = 0;
732
+ while (y < H_) {
733
+ const hgt = gr.int(4, 40);
734
+ const shift = gr.chance(0.18 + shock * 0.5 + burst * 0.2) ? gr.range(-46, 46) * gl : 0;
735
+ if (gr.chance(H.clamp((T_B + 0.35 - t) / 0.35) * 0.7)) { y += hgt; continue; } // slices still missing while it materialises
736
+ ctx.drawImage(L, 0, y, W, hgt, shift, y, W, hgt);
737
+ if (shift && gr.chance(0.5)) { ctx.fillStyle = gr.chance(0.5) ? 'rgba(0,255,255,0.35)' : 'rgba(255,40,200,0.35)'; ctx.globalCompositeOperation = 'source-atop'; ctx.globalCompositeOperation = 'source-over'; }
738
+ y += hgt;
739
+ }
740
+ }
741
+
742
+ // Title: katakana + spaced latin
743
+ ctx.save();
744
+ ctx.font = '900 74px "Noto Serif JP"'; ctx.textBaseline = 'alphabetic';
745
+ const KT = 'ニューラル夢';
746
+ const nk = H.clamp(Math.floor((t - 2.55) / 0.11) + 1, 0, KT.length);
747
+ const kt = KT.slice(0, nk);
748
+ const kAge = t - (2.55 + (nk - 1) * 0.11), co = 4 + 14 * Math.exp(-Math.max(0, kAge) * 8);
749
+ ctx.fillStyle = '#00f0ff'; ctx.fillText(kt, 66 + co, 120 + 3);
750
+ ctx.fillStyle = '#ff2bd6'; ctx.fillText(kt, 66 - co, 120 - 2);
751
+ ctx.fillStyle = '#fff4fb'; ctx.fillText(kt, 66, 120);
752
+ ctx.font = '600 22px "Inter"'; ctx.letterSpacing = '17px'; ctx.fillStyle = '#ffe9f6';
753
+ ctx.shadowColor = '#ff4fc0'; ctx.shadowBlur = 12;
754
+ ctx.fillText('NEURAL DREAMS'.slice(0, H.clamp(Math.floor((t - 3.15) / 0.045), 0, 13)), 70, 162);
755
+ ctx.shadowBlur = 0; ctx.letterSpacing = '0px';
756
+ ctx.font = '700 20px "Noto Serif JP"'; ctx.fillStyle = 'rgba(255,220,240,0.85)';
757
+ if (t > 3.8) ctx.fillText('エーアイ・アエステティック', 72, 196);
758
+ ctx.restore();
759
+
760
+ // ---------- VHS finish (pixel pass) ----------
761
+ const bandY = ((t * 0.2 + 0.806) % 1) * (H_ + 160) - 80;
762
+ const rollP = H.clamp((t - 0.45) / 0.6), roll = Math.round((1 - H.ease.out(rollP)) * H_ * 0.9) * (t >= 0.45 ? 1 : 0);
763
+ vhsPass(ctx, W, H_, t, { roll, bandY, bandH: 64, wobK: 28, streakP: 0.35, streakL: 420, speck: 0.06, desat: true });
764
+ if (t < 0.45) { // VCR blue screen before the tape locks
765
+ ctx.fillStyle = '#1f33d6'; ctx.fillRect(0, 0, W, H_);
766
+ const nr = H.rng(Math.floor(t * 24) + 40);
767
+ for (let i = 0; i < 50; i++) { ctx.fillStyle = `rgba(255,255,255,${nr.range(0.05, 0.4)})`; ctx.fillRect(0, nr.range(0, H_), W, nr.range(1, 4)); }
768
+ }
769
+ H.grain(ctx, W, H_, t < 1.0 ? 0.16 : 0.07, Math.floor(t * 24) + 9, true);
770
+
771
+ // OSD
772
+ ctx.save();
773
+ ctx.font = '400 34px "VT323"'; ctx.fillStyle = '#ffffff'; ctx.shadowColor = 'rgba(0,0,0,0.6)'; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2;
774
+ ctx.fillText('PLAY ▶', W - 170, 58);
775
+ const secs = 754 + Math.floor(t);
776
+ ctx.fillText(`SP 0:${String(Math.floor(secs / 60)).padStart(2, '0')}:${String(secs % 60).padStart(2, '0')}`, W - 230, H_ - 34);
777
+ ctx.fillText('JUN. 14 1996', 40, H_ - 34);
778
+ ctx.restore();
779
+ H.vignette(ctx, W, H_, 0.45, '20,0,40');
780
+ },
781
+ });
782
+ }