@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,793 @@
1
+ {
2
+ // ---------- SNES-era 16-bit pixel art: 320x180 internal buffer, scaled x4 nearest-neighbour ----------
3
+ const LW = 320, LH = 180;
4
+ const BAYER = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5].map((v) => (v + 0.5) / 16);
5
+ const bay = (x, y) => BAYER[(y & 3) * 4 + (x & 3)];
6
+ const C = (h) => H.hex(h);
7
+
8
+ // master palette (~40 colours, like a few SNES CGRAM rows)
9
+ const P = {
10
+ sky: ['#140c34', '#1e1146', '#2c1658', '#43206a', '#632a78', '#8c357c', '#b8457a', '#e05c72', '#f6806a', '#ffaa6e', '#ffd28a'].map(C),
11
+ star: C('#fff4d8'), starDim: C('#a88ac8'),
12
+ sunHi: C('#fff6c2'), sunMid: C('#ffd27a'), sunLo: C('#ff9a5c'),
13
+ cloud: C('#7a3478'), cloudLit: C('#e8687a'), cloudHot: C('#ffb08a'),
14
+ mtFar: C('#6a3478'), mtFarLit: C('#b0507e'), mtNear: C('#3e1f5e'), mtNearLit: C('#7a3670'),
15
+ bldBack: C('#2a1850'), bldBackLit: C('#4a2a6a'), bldFront: C('#170d30'), bldFrontLit: C('#38205a'),
16
+ winY: C('#ffd27a'), winC: C('#6ef0ff'), winP: C('#ff6ab0'), winDim: C('#5a3a7a'),
17
+ core: C('#c8fcff'), coreMid: C('#4ad8ff'), coreDeep: C('#1a78c8'),
18
+ red: C('#ff3a4a'),
19
+ roof: C('#0c0820'), roofTop: C('#2a1a44'), roofRim: C('#d0607a'), roofMid: C('#1a1032'),
20
+ K: C('#0a0618'),
21
+ };
22
+
23
+ // hero sprite (faces right, looking at the city)
24
+ const HERO = [
25
+ '.....KKKK......',
26
+ '....KHHHHK.....',
27
+ '...KHHhHHHK....',
28
+ '...KHHHHSSHK...',
29
+ '...KHHHSSKSK...',
30
+ '...KHhSSSSSK...',
31
+ '....KHSSSSK....',
32
+ '.....KKSSKK....',
33
+ '....KJJJJJJK...',
34
+ '...KJJjJJJJK...',
35
+ '..KJJjJJJJJSK..',
36
+ '..KJJjJJJJKSK..',
37
+ '..KJjjJJJJKKK..',
38
+ '...KJJJJJJK....',
39
+ '...KcccccccK...',
40
+ '...KPPPKPPPK...',
41
+ '...KPPpKPPPK...',
42
+ '...KPPpKKPPK...',
43
+ '...KPPK.KPPK...',
44
+ '..KBBBK.KBBBK..',
45
+ '..KBBBBKKBBBBK.',
46
+ '...KKKK..KKKK..',
47
+ ];
48
+ const HERO_WALK = HERO.slice(0, 18).concat(['..KPPK..KPPK...', '.KBBBK...KBBK..', '.KBBBBK..KBBBBK', '..KKKK....KKKK.']);
49
+ const HERO_PAL = { K: '#0a0618', H: '#ff5a8a', h: '#b02a6a', S: '#ffc8a0', J: '#3a6ad8', j: '#22408a', c: '#ffd27a', P: '#3a2a5a', p: '#22183a', B: '#b8456a' };
50
+ // little drone companion
51
+ const DRONE = [
52
+ '...KKKKK...',
53
+ '..KWWWWWK..',
54
+ '.KWwwwwwWK.',
55
+ 'KWwKKKKKwWK',
56
+ 'KWKEEeEEKWK',
57
+ 'KWKEeeeEKWK',
58
+ 'KwwKKKKKwwK',
59
+ '.KwwwwwwwK.',
60
+ '..KKKKKKK..',
61
+ '...K...K...',
62
+ ];
63
+ const DRONE_PAL = { K: '#0a0618', W: '#e8eef8', w: '#9aa8c8', E: '#4ad8ff', e: '#c8fcff' };
64
+
65
+ // ================= EXPLAINER: "How does AI learn?" as a 16-bit RPG cutscene + battle =================
66
+ const PX_CAT = ['K.........K', 'KK.......KK', 'KOK.....KOK', 'KOOKKKKKOOK', 'KOOOOOOOOOK', 'KOOKOOOKOOK', 'KOOOOPOOOOK', 'KWOOKOKOOWK', '.KOOOOOOOK.', '..KKKKKKK..'];
67
+ const PX_DOG = ['..KKKKKKK..', '.KTTTTTTTK.', 'KBKTTTTTKBK', 'KBKTKTKTKBK', 'KBKTTTTTKBK', 'KBKTTKTTKBK', '.KKTTTTTKK.', '..KTTPTTK..', '...KTTTK...', '....KKK....'];
68
+ const PX_PET_PAL = { K: '#0a0618', O: '#ff9a4a', W: '#fff4d8', P: '#ff6ab0', T: '#e8c890', B: '#8a5a3a' };
69
+ let PX_NZ = null; // explainer backdrop noise (same seed as the hero's mountains), built once
70
+ const PX_ARROW = ['KK.....','KYK....', 'KYYK...', 'KYYYK..', 'KYYYYK.', 'KYYYK..', 'KYYK...', 'KYK....', 'KK.....'];
71
+
72
+ function pixExplainer(ctx, W, H_, t) {
73
+ const T = Math.floor(t * 12) / 12; // 12 fps game tick for logic
74
+ const prog = (a, b) => H.clamp((T - a) / (b - a));
75
+ const buf = new Uint8ClampedArray(LW * LH * 4);
76
+ const put = (x, y, c) => { x |= 0; y |= 0; if (x < 0 || y < 0 || x >= LW || y >= LH) return; const i = (y * LW + x) * 4; buf[i] = c[0]; buf[i + 1] = c[1]; buf[i + 2] = c[2]; buf[i + 3] = 255; };
77
+ const rect = (x, y, w, h, c) => { for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) put(x + i, y + j, c); };
78
+ const dput = (x, y, c1, c2, a) => put(x, y, a > bay(x, y) ? c2 : c1);
79
+ const sprite = (rows, pal, ox, oy, flip, scale = 1) => {
80
+ const cp = {}; for (const k in pal) cp[k] = C(pal[k]);
81
+ rows.forEach((row, j) => [...row].forEach((ch, i) => { if (ch === '.') return; const xx = flip ? row.length - 1 - i : i; for (let a = 0; a < scale; a++) for (let b = 0; b < scale; b++) put(ox + xx * scale + a, oy + j * scale + b, cp[ch]); }));
82
+ };
83
+ const tc = H.layer(LW, 24), tx = tc.getContext('2d', { willReadFrequently: true });
84
+ const text = (str, x, y, col, shadow, size = 8, outline = null) => {
85
+ tx.clearRect(0, 0, LW, 24); tx.font = size + 'px "Press Start 2P"'; tx.textBaseline = 'top'; tx.fillStyle = '#fff'; tx.fillText(str, 0, 0);
86
+ const w = Math.min(LW, Math.ceil(tx.measureText(str).width) + 2), hh = size + 4, d = tx.getImageData(0, 0, w, hh).data;
87
+ if (outline) for (let j = 0; j < hh; j++) for (let i = 0; i < w; i++) if (d[(j * w + i) * 4 + 3] > 110) for (let oy = -1; oy <= 1; oy++) for (let ox = -1; ox <= 1; ox++) put(x + i + ox, y + j + oy, outline);
88
+ if (shadow) for (let j = 0; j < hh; j++) for (let i = 0; i < w; i++) if (d[(j * w + i) * 4 + 3] > 110) put(x + i + 1, y + j + 1, shadow);
89
+ for (let j = 0; j < hh; j++) for (let i = 0; i < w; i++) if (d[(j * w + i) * 4 + 3] > 110) put(x + i, y + j, col);
90
+ return w - 2;
91
+ };
92
+ const white = C('#f8f8ff'), gold = C('#ffd27a'), ink = C('#0a0618'), grey = C('#9aa0c8'), red = C('#ff3a4a'), green = C('#6aff8a'), cyan = C('#6ef0ff');
93
+ const WB = ['#4060d0', '#3450bc', '#2a42a6', '#22358e', '#1a2a78', '#142064'].map(C);
94
+ const win = (x0, y0, w, h) => { // SNES RPG window
95
+ for (let y = y0; y < y0 + h; y++) { const f = ((y - y0) / h) * (WB.length - 1), i = Math.min(WB.length - 2, Math.floor(f)), a = H.smooth(0.7, 1, f - i); for (let x = x0; x < x0 + w; x++) { if ((x === x0 || x === x0 + w - 1) && (y === y0 || y === y0 + h - 1)) continue; dput(x, y, WB[i], WB[i + 1], a); } }
96
+ for (let x = x0 + 1; x < x0 + w - 1; x++) { put(x, y0, white); put(x, y0 + h - 1, white); put(x, y0 + 1, grey); put(x, y0 + h - 2, grey); put(x, y0 - 1, ink); put(x, y0 + h, ink); }
97
+ for (let y = y0 + 1; y < y0 + h - 1; y++) { put(x0, y, white); put(x0 + w - 1, y, white); put(x0 + 1, y, grey); put(x0 + w - 2, y, grey); put(x0 - 1, y, ink); put(x0 + w, y, ink); }
98
+ };
99
+ const bubble = (x0, y0, w, h, tailX) => { rect(x0, y0, w, h, white); for (let x = x0; x < x0 + w; x++) { put(x, y0 - 1, ink); put(x, y0 + h, ink); } for (let y = y0; y < y0 + h; y++) { put(x0 - 1, y, ink); put(x0 + w, y, ink); } for (let k = 0; k < 4; k++) { for (let i = -k; i <= 0; i++) put(tailX + i + k, y0 + h + k, k === 3 ? ink : white); put(tailX + k + 1, y0 + h + k, ink); put(tailX - 1, y0 + h + k, ink); } };
100
+ const hand = (x, y) => sprite(['.KKKK...', 'KWWWWKKK', 'KWWWWWWK', 'KWWWWKKK', '.KKKK...'], { K: '#0a0618', W: '#f8f8ff' }, x, y);
101
+ const fmt = (n) => String(Math.floor(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
102
+ const sky = (c0, c1, hz) => { for (let y = 0; y < LH; y++) { const f = Math.pow(H.clamp(y / hz), 1.2) * 5, i = Math.min(4, Math.floor(f)); const cA = H.mix(c0, c1, i / 5).map(Math.round), cB = H.mix(c0, c1, (i + 1) / 5).map(Math.round); for (let x = 0; x < LW; x++) dput(x, y, cA, cB, H.smooth(0.6, 1, f - i)); } };
103
+ const stars = (n, seed) => { const r = H.rng(seed); for (let s = 0; s < n; s++) { const x = r.int(0, LW - 1), y = r.int(0, 90), ph = r() * 6.28; if (Math.sin(T * 3 + ph) > -0.3) put(x, y, y < 40 ? P.star : P.starDim); } };
104
+ const mixc = (a, b, k) => H.mix(a, b, k).map(Math.round);
105
+ // ---- backdrop kit shared with the hero still: rim-lit fbm mountains, lit-from-below cloud streaks, striped sun, lit skyline
106
+ const nz = PX_NZ || (PX_NZ = H.makeNoise(77));
107
+ const mountains = (base, amp, freq, seed, scroll, col, lit, sunX, yMax = LH) => {
108
+ const hgt = [];
109
+ for (let x = -1; x <= LW + 1; x++) { const u = (x + scroll) * freq; hgt.push(Math.round(base - amp * (Math.abs(nz.fbm2(u, seed, 3)) * 1.6 + 0.35 * nz.n2(u * 3.1, seed + 9)))); }
110
+ for (let x = 0; x < LW; x++) {
111
+ const top = hgt[x + 1], facing = hgt[x + 2] < hgt[x + 1] ? 0 : 1;
112
+ for (let y = Math.max(0, top); y < yMax; y++) { const d = y - top; if (d < 2 && (facing || Math.abs(x - sunX) < 40)) put(x, y, lit); else if (d < 5 && facing) dput(x, y, col, lit, 0.5 - d * 0.1); else put(x, y, col); }
113
+ }
114
+ };
115
+ const cloud = (cx, cy, w, h, cDark = P.cloud, cLit = P.cloudLit, cHot = P.cloudHot) => {
116
+ for (let y = cy - h; y <= cy + h; y++) for (let x = Math.round(cx - w); x <= cx + w; x++) {
117
+ const dx = (x - cx) / w, dy = (y - cy) / h, lump = 1 + 0.25 * Math.sin(x * 0.35 + cx);
118
+ if (dx * dx + (dy * dy) / lump > 1) continue;
119
+ put(x, y, dy > 0.55 ? cHot : dy > 0.15 ? cLit : cDark);
120
+ }
121
+ };
122
+ const sun = (sx, sy, sr) => {
123
+ for (let y = sy - sr; y <= sy + sr; y++) {
124
+ const ry = (y - (sy - sr)) / (2 * sr);
125
+ if (y > sy - 4) { const k = y - (sy - 4), gap = 1 + Math.floor(k / 9); if ((k % 7) < gap) continue; }
126
+ for (let x = sx - sr; x <= sx + sr; x++) {
127
+ if ((x - sx) ** 2 + (y - sy) ** 2 > sr * sr) continue;
128
+ put(x, y, ry < 0.45 ? (ry < 0.3 ? P.sunHi : (0.3 + 0.15 * bay(x, y) > ry ? P.sunHi : P.sunMid)) : (ry < 0.55 ? P.sunMid : (ry - 0.55) * 3 > bay(x, y) ? P.sunLo : P.sunMid));
129
+ }
130
+ }
131
+ };
132
+ const skyline = (seed, base, minH, maxH, col, lit, winA, scroll, winOn = 0.3) => {
133
+ const r = H.rng(seed); let bx = -24 - Math.round(scroll);
134
+ while (bx < LW) {
135
+ const w = r.int(9, 18), h = r.int(minH, maxH), top = base - h, rr = H.rng(seed * 31 + w * 7 + h);
136
+ rect(bx, top, w, h, col); rect(bx, top, w, 1, lit); for (let y = top; y < base; y++) put(bx + w - 1, y, lit);
137
+ if (rr.chance(0.35)) { const ah = rr.int(2, 6); for (let k = 1; k <= ah; k++) put(bx + (w >> 1), top - k, col); if (Math.sin(T * 4 + w) > 0) put(bx + (w >> 1), top - ah - 1, P.red); }
138
+ const wc = rr.pick(winA);
139
+ for (let wy = top + 2; wy < base - 1; wy += 2) for (let wx = bx + 1; wx < bx + w - 2; wx += 2) if (rr() < winOn) put(wx, wy, wc);
140
+ bx += w + r.int(0, 3);
141
+ }
142
+ };
143
+
144
+ // ---------------- scenes ----------------
145
+ const sceneRoof = () => {
146
+ sky(H.hex('#140c34'), H.hex('#e05c72'), 130); stars(50, 3);
147
+ // dusk backdrop from the hero still: striped sun on the horizon, lit cloud streaks, two rim-lit mountain layers
148
+ sun(266, 112, 24);
149
+ const cd = T * 2;
150
+ cloud(((40 + cd) % 380) - 30, 36, 30, 2); cloud(((250 + cd * 1.1) % 380) - 30, 26, 22, 2); cloud(((150 + cd * 0.7) % 380) - 30, 70, 38, 2); cloud(((300 + cd * 0.9) % 380) - 30, 84, 26, 2);
151
+ mountains(112, 26, 0.018, 3.3, T * 2, P.mtFar, P.mtFarLit, 266, 128);
152
+ mountains(122, 16, 0.03, 8.1, T * 4, P.mtNear, P.mtNearLit, 266, 128);
153
+ // horizon haze band where the city meets the mountains
154
+ for (let y = 106; y < 122; y++) { const a = 0.4 * (1 - Math.abs(y - 114) / 8); for (let x = 0; x < LW; x++) if (a > bay(x, y)) { const i = (y * LW + x) * 4; put(x, y, mixc([buf[i], buf[i + 1], buf[i + 2]], P.cloudLit, 0.35)); } }
155
+ // skyline + rooftop
156
+ const r = H.rng(9); let bx = 0; while (bx < LW) { const w = r.int(10, 22), h = r.int(16, 50); rect(bx, 140 - h, w, h + 40, P.bldBack); rect(bx, 140 - h, w, 1, P.bldBackLit); for (let y = 140 - h; y < 128; y++) put(bx + w - 1, y, P.bldBackLit); for (let wy = 140 - h + 3; wy < 136; wy += 3) for (let wx = bx + 2; wx < bx + w - 2; wx += 2) if (r() < 0.35) put(wx, wy, r() < 0.5 ? P.winY : P.winC); bx += w + 1; }
157
+ rect(0, 128, LW, 52, P.roof); rect(0, 128, LW, 1, P.roofRim); rect(0, 129, LW, 1, P.roofTop);
158
+ for (let y = 130; y < LH; y++) for (let x = 0; x < LW; x++) if (((x + y) & 7) === 0 && y % 2) put(x, y, P.roofMid);
159
+ for (let x = 0; x < LW; x++) { put(x, 119, P.K); put(x, 123, P.K); if (x % 12 === 4) for (let y = 119; y < 128; y++) put(x, y, P.K); if (x % 3 === 0) put(x, 118, P.roofRim); }
160
+ for (let x = 113; x < 140; x++) put(x, 129, P.K); // hero contact shadow
161
+ sprite(HERO, HERO_PAL, 112, 128 - HERO.length * 2, false, 2);
162
+ sprite(DRONE, DRONE_PAL, 160, 58 + Math.round(Math.sin(T * 3) * 2), false, 3);
163
+ if (T > 0.3) { const k = Math.floor(T * 6) % 3; for (let i = 0; i < 3; i++) if (i <= k) { put(196 + i * 6, 54 - i * 4, gold); put(197 + i * 6, 53 - i * 4, gold); } }
164
+ };
165
+ const sceneHall = () => { // training hall: data conveyor into the MODEL
166
+ sky(H.hex('#0c0a2a'), H.hex('#3a1e6a'), 110);
167
+ // tall hall windows looking out on the dusk city (dimmed hero backdrop, slow parallax)
168
+ const WIN = [0, 1, 2, 3].map((i) => [10 + i * 78, 64]), WY0 = 22, WY1 = 94;
169
+ for (let y = WY0; y < WY1; y++) { const f = Math.pow((y - WY0) / (WY1 - WY0), 1.1) * 7 + 1, i = Math.min(8, Math.floor(f)); for (let x = 0; x < LW; x++) dput(x, y, P.sky[i], P.sky[i + 1], H.smooth(0.55, 1, f - i)); }
170
+ cloud(((60 + T * 2) % 380) - 30, 40, 30, 2); cloud(((230 + T * 1.5) % 380) - 30, 56, 36, 2);
171
+ mountains(88, 22, 0.02, 3.3, T * 2, P.mtFar, P.mtFarLit, 300, WY1);
172
+ mountains(93, 10, 0.034, 8.1, T * 3, P.mtNear, P.mtNearLit, 300, WY1);
173
+ skyline(77, 96, 6, 24, P.bldBack, P.bldBackLit, [P.winY, P.winY, P.winC, P.winP], T * 4);
174
+ // wall, window frames, mullions, sills
175
+ const wallA = C('#1c1244'), wallB = C('#120a30'), frame = C('#2e1e5e'), frameLit = C('#6a4aa8');
176
+ const winCol = new Uint8Array(LW); WIN.forEach(([x0, w]) => winCol.fill(1, x0, x0 + w));
177
+ for (let y = 0; y < 110; y++) for (let x = 0; x < LW; x++) {
178
+ const inWin = y >= WY0 && y < WY1 && winCol[x];
179
+ if (!inWin) dput(x, y, wallA, wallB, y / 110 * 0.8 + (((x + 39) % 78) < 4 ? 0.3 : 0));
180
+ }
181
+ WIN.forEach(([x0, w]) => {
182
+ for (let x = x0 - 2; x < x0 + w + 2; x++) { put(x, WY0 - 2, frameLit); put(x, WY0 - 1, frame); put(x, WY1, frameLit); put(x, WY1 + 1, frame); put(x, WY1 + 2, P.K); }
183
+ for (let y = WY0 - 1; y < WY1; y++) { put(x0 - 1, y, frame); put(x0 + w, y, frameLit); put(x0 + (w >> 1), y, frame); put(x0 + (w >> 1) + 1, y, P.K); }
184
+ for (let x = x0; x < x0 + w; x++) { put(x, 40, frame); put(x, 41, P.K); }
185
+ // glass glint: two diagonal light streaks
186
+ for (let k = 0; k < 10; k++) { if (0.45 > bay(x0 + 4 + k, WY0 + 14 - k)) put(x0 + 4 + k, WY0 + 14 - k, C('#c8a8e8')); if (0.3 > bay(x0 + 8 + k, WY0 + 16 - k)) put(x0 + 8 + k, WY0 + 16 - k, C('#a888d0')); }
187
+ });
188
+ // baseboard
189
+ rect(0, 104, LW, 6, C('#0e0826')); rect(0, 104, LW, 1, frame);
190
+ // floor: perspective grid (spacing grows toward the viewer), window light pooled on the gloss, fade into the horizon
191
+ const fl = C('#22145a'), flLine = C('#5a3a9a'), glow = C('#4a2a8a'), pool = C('#34206e');
192
+ for (let y = 110; y < LH; y++) {
193
+ const dz = 48 / (y - 104), dz1 = 48 / (y + 1 - 104), hLine = Math.floor(dz) !== Math.floor(dz1), lineA = H.clamp((y - 112) / 22), hz = H.clamp(1 - (y - 110) / 16);
194
+ const spread = 1 + (y - 110) * 0.012, poolA = (1 - (y - 110) / 40) * 0.75;
195
+ const pools = y < 150 ? WIN.map(([x0, w]) => [160 + (x0 + w / 2 - 160) * spread, w * 0.42 * spread]) : [];
196
+ for (let x = 0; x < LW; x++) {
197
+ let c = hz > bay(x, y) ? glow : fl;
198
+ if (poolA > bay(x, y) && pools.some(([cx, hw]) => Math.abs(x - cx) < hw)) c = pool;
199
+ if (hLine && lineA > bay(x, y)) c = flLine;
200
+ put(x, y, c);
201
+ }
202
+ }
203
+ for (let k = -12; k <= 12; k++) { for (let y = 110; y < LH; y++) { const x = Math.round(160 + k * (y - 104) * 0.9); if (H.clamp((y - 108) / 18) > bay(x, y)) put(x, y, flLine); } }
204
+ for (let x = 0; x < 236; x++) put(x, 110, P.K); // belt shadow on the floor
205
+ // conveyor belt
206
+ rect(0, 100, 236, 8, C('#2a2a4a')); for (let x = 0; x < 236; x++) { put(x, 100, C('#8a8ab8')); put(x, 107, P.K); if ((x + Math.floor(T * 24)) % 8 === 0) for (let y = 101; y < 107; y++) put(x, y, C('#4a4a7a')); }
207
+ // MODEL: the drone, 3x, on a pedestal, label landing under it
208
+ const absorb = Math.floor((T - 2.95) / 0.33) >= 0 && ((T - 2.95) % 0.33) < 0.09 && T < 5.9;
209
+ rect(238, 108, 64, 6, C('#5a3a9a')); rect(238, 108, 64, 1, C('#a88ac8'));
210
+ sprite(DRONE, absorb ? { K: '#0a0618', W: '#ffffff', w: '#ffffff', E: '#ffffff', e: '#ffffff' } : DRONE_PAL, 254, 70 + Math.round(Math.sin(T * 3) * 1), false, 3);
211
+ win(244, 118, 52, 14); text('MODEL', 251, 121, white, ink);
212
+ // training-data label + chevrons flowing along the belt
213
+ win(54, 48, 112, 14); text('TRAINING DATA', 59, 51, gold, ink);
214
+ for (let k = 0; k < 6; k++) { const x = ((k * 40 + Math.floor(T * 48)) % 240); if (x < 228) sprite(PX_ARROW, { K: '#0a0618', Y: '#ffd27a' }, x, 110, false); }
215
+ // labelled example cards riding the belt (pixel-stepped)
216
+ for (let i = 0; i < 12; i++) {
217
+ const t0 = 2.6 + i * 0.27, k = (T - t0) / 1.1; if (k < 0 || k >= 1) continue;
218
+ const x = Math.round(-30 + k * 268), isCat = i % 3 !== 1, y = 74;
219
+ rect(x, y, 26, 24, white); rect(x, y, 26, 1, ink); rect(x, y + 23, 26, 1, ink); rect(x, y, 1, 24, ink); rect(x + 25, y, 1, 24, ink);
220
+ sprite(isCat ? PX_CAT : PX_DOG, PX_PET_PAL, x + 2, y + 2, false, 2);
221
+ rect(x, y + 25, 26, 10, isCat ? C('#ffd27a') : C('#ff6ab0')); text(isCat ? 'CAT' : 'DOG', x + 1, y + 26, ink, null);
222
+ }
223
+ // examples counter window
224
+ win(8, 134, 196, 42); text('EXAMPLES SEEN', 16, 139, cyan, ink);
225
+ text(fmt(1e6 * Math.pow(prog(2.9, 5.6), 2.2)), 16, 153, white, ink, 16);
226
+ };
227
+ const sceneBattle = () => {
228
+ // backdrop
229
+ sky(H.hex('#1a0c3a'), H.hex('#b8457a'), 100); stars(24, 5);
230
+ // JRPG battle background: lit cloud streaks, far + near rim-lit ranges, rolling dithered hills, banded field
231
+ cloud(((20 + T * 3) % 380) - 30, 30, 34, 2); cloud(((200 + T * 2) % 380) - 30, 58, 28, 2); cloud(((120 + T * 2.5) % 380) - 30, 74, 40, 2);
232
+ mountains(92, 30, 0.016, 5.7, T * 1.5, P.mtFar, P.mtFarLit, 160, 120);
233
+ mountains(102, 16, 0.028, 2.2, T * 3, P.mtNear, P.mtNearLit, 160, 120);
234
+ const hillA = C('#2e1650'), hillB = C('#43206a'), hillLit = C('#9a4a86'), field = [C('#3e1f5e'), C('#4c2468')];
235
+ const hTop = []; for (let x = 0; x < LW; x++) hTop.push(Math.round(106 + 4 * nz.n2((x + T * 6) * 0.025, 4.4) + 2 * Math.sin((x + T * 6) * 0.07)));
236
+ for (let x = 0; x < LW; x++) for (let y = hTop[x]; y < 114; y++) { const d = y - hTop[x]; put(x, y, d < 1 ? hillLit : d < 3 ? (0.5 > bay(x, y) ? hillLit : hillB) : (((y + 1) >> 1) % 3 === 0 && 0.5 > bay(x, y)) ? hillB : hillA); }
237
+ for (let y = 114; y < 136; y++) {
238
+ const dz = 30 / (y - 100) + T * 1.2, band = Math.floor(dz) & 1, fr = dz - Math.floor(dz);
239
+ for (let x = 0; x < LW; x++) { const c0 = field[band], c1 = field[band ^ 1]; put(x, y, fr > 0.82 && (fr - 0.82) * 5.5 > bay(x, y) ? c1 : c0); }
240
+ }
241
+ for (let x = 0; x < LW; x++) put(x, 114, C('#24103e'));
242
+ const phase = T < 7.4 ? 0 : T < 8.3 ? 1 : T < 9.4 ? 2 : 0;
243
+ // enemy: the new picture (a cat), framed; it shakes when hit
244
+ const shake = (T > 7.45 && T < 7.8) ? (Math.floor(T * 24) % 2 ? 2 : -2) : 0;
245
+ const ex = 40 + shake, ey = 40;
246
+ rect(ex - 4, ey - 4, 52, 48, white); rect(ex - 5, ey - 5, 54, 1, ink); rect(ex - 5, ey + 44, 54, 1, ink); rect(ex - 5, ey - 5, 1, 50, ink); rect(ex + 48, ey - 5, 1, 50, ink);
247
+ sprite(PX_CAT, PX_PET_PAL, ex + 2, ey + 1, false, 4);
248
+ win(ex - 8, ey + 48, 66, 14); text('NEW PIC', ex - 3, ey + 51, white, ink);
249
+ // party: the model (drone) with its name tag
250
+ const dx = 228, dy = 52 + Math.round(Math.sin(T * 3) * 2);
251
+ sprite(DRONE, DRONE_PAL, dx, dy, true, 3);
252
+ win(dx - 4, 88, 52, 14); text('MODEL', dx + 3, 91, white, ink);
253
+ // command window (the loop) + cursor
254
+ win(4, 120, 96, 56);
255
+ ['GUESS', 'CHECK', 'ADJUST'].forEach((nm, i) => text(nm, 26, 128 + i * 15, i === phase ? gold : white, ink));
256
+ hand(10 + (Math.floor(T * 6) % 2), 129 + phase * 15);
257
+ // weights window: three stat bars that change during ADJUST
258
+ win(104, 120, 212, 56); text('WEIGHTS', 112, 125, cyan, ink);
259
+ const adj = Math.floor(prog(8.6, 9.2) * 4) / 4;
260
+ [[0.3, 0.75], [0.8, 0.45], [0.55, 0.9]].forEach(([a, b], i) => {
261
+ const v = H.lerp(a, b, adj), y = 138 + i * 12, flashBar = adj > 0 && adj < 1 && Math.floor(T * 12) % 2 === 0;
262
+ text('W' + (i + 1), 112, y - 1, white, ink);
263
+ rect(136, y, 120, 6, ink); rect(137, y + 1, Math.round(118 * v), 4, flashBar ? white : H.mix(C('#4ad8ff'), C('#6aff8a'), v).map(Math.round));
264
+ });
265
+ // GUESS 1: "DOG?" bubble from the model
266
+ if (T > 6.5 && T < 9.4) { bubble(dx - 76, 22, 74, 24, dx - 8); text('DOG?', dx - 71, 26, ink, null, 16); }
267
+ // CHECK: MISS! damage pop + message
268
+ if (T > 7.4 && T < 8.4) {
269
+ const k = T - 7.4, by = Math.round(20 - Math.max(0, 8 - k * 40) + (k < 0.2 ? -Math.sin(k * 15) * 6 : 0));
270
+ text('MISS!', ex + 2, ey + by - 10, red, C('#5a0814'), 16, ink);
271
+ win(102, 100, 116, 16); text("IT'S A CAT!", 116, 104, white, ink);
272
+ }
273
+ // ADJUST: error travels back from the picture to the model (dotted red pixels)
274
+ if (T > 8.3 && T < 9.4) {
275
+ const k = prog(8.3, 8.8), x0 = ex + 50, x1 = dx - 4;
276
+ for (let x = x0; x < x0 + (x1 - x0) * k; x += 3) { const y = Math.round(70 + Math.sin((x - x0) / (x1 - x0) * Math.PI) * -18); rect(x, y, 2, 2, red); }
277
+ if (k > 0.3) text('ERROR', 132, 44, red, C('#5a0814'), 8, ink);
278
+ }
279
+ // GUESS 2: correct
280
+ if (T > 9.5) {
281
+ bubble(dx - 76, 22, 74, 24, dx - 8); text('CAT!', dx - 71, 26, C('#1a7a3a'), null, 16);
282
+ if (T > 9.75) { const k = T - 9.75; text('HIT!', ex + 6, ey + Math.round(10 - Math.min(8, k * 40)), green, C('#0a4a2a'), 16, ink); }
283
+ }
284
+ // round counter (loop is repeated)
285
+ text('ROUND ' + String(1 + Math.floor(Math.max(0, T - 6.2) * 2.5)).padStart(3, '0'), 232, 108, gold, ink);
286
+ };
287
+ const sceneLevel = () => {
288
+ // night falls over the city: hero sky gradient with a dithered horizon glow, far ranges and a lit skyline
289
+ sky(H.hex('#0c0a2a'), H.hex('#e05c72'), 180); stars(40, 8);
290
+ cloud(((80 + T * 2) % 380) - 30, 136, 40, 2); cloud(((260 + T * 1.6) % 380) - 30, 128, 30, 2);
291
+ mountains(152, 22, 0.018, 3.3, T * 2, P.mtFar, P.mtFarLit, 240);
292
+ mountains(162, 14, 0.03, 8.1, T * 3, P.mtNear, P.mtNearLit, 240);
293
+ skyline(404, LH, 10, 34, P.bldBack, P.bldBackLit, [P.winY, P.winY, P.winC, P.winP], T * 5, 0.35);
294
+ for (let y = 146; y < 162; y++) { const a = 0.36 * (1 - Math.abs(y - 154) / 8); for (let x = 0; x < LW; x++) if (a > bay(x, y)) { const i = (y * LW + x) * 4; if (buf[i] + buf[i + 1] + buf[i + 2] < 260) put(x, y, mixc([buf[i], buf[i + 1], buf[i + 2]], P.cloudLit, 0.35)); } }
295
+ win(10, 20, 300, 106);
296
+ text('ACCURACY', 22, 32, cyan, ink);
297
+ const p = prog(10.95, 12.4), val = Math.round(61 + 36 * H.ease.out(p));
298
+ text(val + '%', 22, 46, white, ink, 16);
299
+ text('61% > ' + val + '%', 22, 68, gold, ink);
300
+ // EXP-style bar
301
+ text('EXP', 22, 84, white, ink); rect(52, 84, 104, 8, ink); rect(53, 85, Math.round(102 * H.ease.out(p)), 6, H.mix(C('#4ad8ff'), C('#6aff8a'), p).map(Math.round));
302
+ // bar chart of rounds (stepped growth)
303
+ const ox = 176, oy = 112;
304
+ for (let y = 30; y <= oy; y++) put(ox, y, white); for (let x = ox; x < ox + 124; x++) put(x, oy, white);
305
+ text('ROUNDS', 238, 114, grey, ink);
306
+ for (let i = 0; i < 10; i++) {
307
+ const g = H.clamp(p * 10 - i); if (g <= 0) break;
308
+ const h = Math.round(((97 - 36 * Math.exp(-i / 3)) / 100) * 78 * g);
309
+ rect(ox + 4 + i * 12, oy - h, 9, h, H.mix(C('#4a2a8a'), C('#6aff8a'), i / 9).map(Math.round)); rect(ox + 4 + i * 12, oy - h, 9, 1, white);
310
+ }
311
+ // emphasis: LEVEL UP! + sparkles around 97%
312
+ if (p >= 1) {
313
+ if (Math.floor(T * 6) % 2 === 0 || T > 13.0) text('LEVEL UP!', 76, 50, gold, ink);
314
+ for (let k = 0; k < 6; k++) { const a = k * 1.05 + T * 3, rr = 22 + (Math.floor(T * 12) % 3) * 2, sx = 44 + Math.cos(a) * rr * 1.4, sy = 52 + Math.sin(a) * rr * 0.4; put(sx, sy, white); put(sx - 1, sy, gold); put(sx + 1, sy, gold); put(sx, sy - 1, gold); put(sx, sy + 1, gold); }
315
+ }
316
+ // takeaway dialog with portrait
317
+ if (T > 13.0) {
318
+ const op = Math.floor(prog(13.0, 13.25) * 4) / 4, bh = Math.max(4, Math.round(48 * op));
319
+ win(6, 153 - Math.round(bh / 2), 308, bh);
320
+ if (op >= 1) {
321
+ sprite(DRONE, DRONE_PAL, 12, 142, false, 2);
322
+ text('MORE EXAMPLES', 42, 135, white, ink, 16); sprite(PX_ARROW, { K: '#0a0618', Y: '#ffd27a' }, 256, 138, false);
323
+ text('BETTER GUESSES!', 42, 155, gold, ink, 16);
324
+ }
325
+ }
326
+ };
327
+
328
+ // ---------------- timeline with SNES transitions ----------------
329
+ let scene = T < 2.3 ? sceneRoof : T < 6.2 ? sceneHall : T < 10.6 ? sceneBattle : sceneLevel;
330
+ scene();
331
+ // BEAT 1 dialog: question typed, then the window slides up (stepped) into the quest header
332
+ if (T < 2.3) {
333
+ const up = prog(1.7, 2.1), y0 = Math.round(H.lerp(128, 2, Math.floor(up * 6) / 6)), h0 = Math.round(H.lerp(48, 14, Math.floor(up * 6) / 6));
334
+ win(6, y0, up > 0 ? Math.round(H.lerp(308, 160, Math.floor(up * 6) / 6)) : 308, h0);
335
+ if (up === 0) {
336
+ sprite(DRONE, DRONE_PAL, 14, 141, false, 2);
337
+ const q1 = 'HOW DOES AI', q2 = 'LEARN?';
338
+ text(q1.slice(0, Math.floor(prog(0.2, 0.9) * q1.length)), 44, y0 + 7, white, ink, 16);
339
+ text(q2.slice(0, Math.floor(prog(0.9, 1.25) * q2.length)), 44, y0 + 27, gold, ink, 16);
340
+ } else text('HOW DOES AI LEARN?', 12, y0 + 3, white, ink);
341
+ } else { win(6, 2, 160, 14); text('HOW DOES AI LEARN?', 12, 5, white, ink); }
342
+
343
+ // mosaic (roof -> hall), wavy battle-swirl (hall -> battle), iris (battle -> level)
344
+ const mosaic = (n) => { if (n <= 1) return; for (let by = 0; by < LH; by += n) for (let bx = 0; bx < LW; bx += n) { const sx = Math.min(LW - 1, bx + (n >> 1)), sy = Math.min(LH - 1, by + (n >> 1)), si = (sy * LW + sx) * 4; for (let y = by; y < Math.min(LH, by + n); y++) for (let x = bx; x < Math.min(LW, bx + n); x++) { const i = (y * LW + x) * 4; buf[i] = buf[si]; buf[i + 1] = buf[si + 1]; buf[i + 2] = buf[si + 2]; } } };
345
+ if (T >= 2.0 && T < 2.6) mosaic(Math.round(16 - Math.abs(T - 2.3) / 0.3 * 15));
346
+ if (T >= 5.9 && T < 6.5) {
347
+ const k = 1 - Math.abs(T - 6.2) / 0.3, src = buf.slice();
348
+ for (let y = 0; y < LH; y++) { const off = Math.round(Math.sin(y * 0.25 + T * 30) * k * 40); for (let x = 0; x < LW; x++) { const sx = ((x + off) % LW + LW) % LW, si = (y * LW + sx) * 4, i = (y * LW + x) * 4; for (let c = 0; c < 3; c++) buf[i + c] = src[si + c] + (255 - src[si + c]) * k * 0.6; } }
349
+ }
350
+ if (T >= 10.3 && T < 10.9) {
351
+ const k = Math.abs(T - 10.6) / 0.3, R = Math.round(k * 200), cx = T < 10.6 ? 244 : 160, cy = T < 10.6 ? 66 : 80;
352
+ for (let y = 0; y < LH; y++) for (let x = 0; x < LW; x++) if ((x - cx) ** 2 + (y - cy) ** 2 > R * R) { const i = (y * LW + x) * 4; buf[i] = buf[i + 1] = buf[i + 2] = 0; }
353
+ }
354
+ // screen flash on the MISS (emphasis)
355
+ if (T >= 7.4 && T < 7.5) for (let i = 0; i < buf.length; i += 4) { buf[i] = Math.min(255, buf[i] + 120); buf[i + 1] = Math.min(255, buf[i + 1] + 60); buf[i + 2] = Math.min(255, buf[i + 2] + 60); }
356
+
357
+ const lc = H.layer(LW, LH), lx = lc.getContext('2d');
358
+ lx.putImageData(new ImageData(buf, LW, LH), 0, 0);
359
+ ctx.save(); ctx.imageSmoothingEnabled = false; ctx.drawImage(lc, 0, 0, W, H_); ctx.restore();
360
+ }
361
+
362
+ // ================= EXPLAINER SOUND: SNES chip palette =================
363
+ // The picture runs on a 12 fps game tick (T = floor(t*12)/12), so the cues below walk the same ticks and
364
+ // re-evaluate the same expressions as pixExplainer: every sound starts on the tick its event first shows.
365
+ // Voiceover mode (k.vo): music drops out (kit), and minor plays plus the custom chip details inside k.withMinor play
366
+ // quieter, so the transitions, MISS!, HIT!, LEVEL UP and the takeaway cadence stay on top of the narration.
367
+ function pixSound(k) {
368
+ const FPS = 12, END = 14.5, V = k.vo;
369
+ const prog = (T, a, b) => H.clamp((T - a) / (b - a));
370
+ const sq = (t, f, dur, gain, pan) => k.tone(t, { f, type: 'square', dur, gain, a: 0.001, hold: dur * 0.6, pan });
371
+ const tri = (t, f, dur, gain, pan) => k.tone(t, { f, type: 'triangle', dur, gain, a: 0.002, hold: dur * 0.6, pan });
372
+ const crash = (t, dur, gain) => k.noise(t, { dur, filter: 'lowpass', f: 3200, gain, rate: 0.25, a: 0.001 }); // NES-style noise channel
373
+
374
+ // ---- music: field theme / battle theme / level screen; each one stops for the transition that ends it
375
+ k.music('chip', 0, 2.3, { bpm: 240 / 2.3, gain: 0.3, chords: [['C4', 'E4', 'G4', 'B4']] }); // softer under the typed question
376
+ k.music('chip', 2.3, 5.9, { bpm: 240 / 2.3, gain: 0.5, chords: [['A3', 'C4', 'E4', 'G4'], ['F3', 'A3', 'C4', 'E4']] }); // bar = mosaic cut at 2.3
377
+ k.music('chip', 6.2, 10.3, { bpm: 150, gain: 0.5, chords: [['A3', 'C4', 'E4', 'A4'], ['F3', 'A3', 'C4', 'F4'], ['C4', 'E4', 'G4', 'C5']] }); // bar 3 = back to GUESS at 9.4
378
+ k.music('chip', 10.917, 12.4, { bpm: 160, gain: 0.45, chords: [['F3', 'A3', 'C4', 'E4']] });
379
+ k.music('chip', 13.25, END, { bpm: 160, gain: 0.4, chords: [['C4', 'E4', 'G4', 'C5']] });
380
+
381
+ // ---- per-tick state machine (mirrors pixExplainer)
382
+ const s = { q1: 0, q2: 0, up: 0, absorb: false, absorbN: 0, cnt: 0, lastTick: -1, phase: 0, adj: 0, acc: 61, op: -1 };
383
+ const Q1 = 'HOW DOES AI', Q2 = 'LEARN?';
384
+ const NOTES_UP = ['C6', 'E6', 'G6', 'C7'];
385
+ for (let f = 0; f <= END * FPS; f++) {
386
+ const T = f / FPS;
387
+ // beat 1: dialog types out (kit chip text blips, one per printed glyph)
388
+ const q1 = Math.floor(prog(T, 0.2, 0.9) * Q1.length), q2 = Math.floor(prog(T, 0.9, 1.25) * Q2.length);
389
+ if (q1 > s.q1) k.play('text', T, { n: q1 - s.q1, rate: 0.035, gain: 1.1, minor: true });
390
+ if (q2 > s.q2) k.play('text', T, { n: q2 - s.q2, rate: 0.035, gain: 1.1, minor: true });
391
+ s.q1 = q1; s.q2 = q2;
392
+ // window slides up into the quest header in 6 steps
393
+ const up = Math.floor(prog(T, 1.7, 2.1) * 6);
394
+ if (up > s.up) k.withMinor(() => tri(T, ['G4', 'A4', 'C5', 'D5', 'E5', 'G5'][up - 1], 0.05, 0.12));
395
+ s.up = up;
396
+ // mosaic (2.0-2.6): one square step per tick, pitch falls as the blocks grow and rises as they shrink
397
+ if (T >= 2.0 && T < 2.6) {
398
+ const n = Math.round(16 - Math.abs(T - 2.3) / 0.3 * 15);
399
+ if (n > 1) sq(T, 1500 / Math.sqrt(n), 0.075, 0.038);
400
+ }
401
+ // hall: the MODEL flashes as it absorbs a card -> rising chip gulp, a little higher each time
402
+ const absorb = Math.floor((T - 2.95) / 0.33) >= 0 && ((T - 2.95) % 0.33) < 0.09 && T < 5.9;
403
+ if (absorb && !s.absorb) { const b = 500 * Math.pow(2, s.absorbN / 12); k.tone(T, { f: [[0, b], [0.07, b * 2]], type: 'square', dur: 0.09, gain: 0.045, a: 0.001, hold: 0.05, pan: 0.5, minor: true }); s.absorbN += 2; }
404
+ s.absorb = absorb;
405
+ // EXAMPLES SEEN counter (accelerating): tally ticks get denser as it speeds up, coin when it lands on 1,000,000
406
+ if (T >= 2.9 && T <= 5.6 + 1e-6) {
407
+ const v = Math.floor(1e6 * Math.pow(prog(T, 2.9, 5.6), 2.2)), gap = T < 3.8 ? 3 : T < 4.7 ? 2 : 1;
408
+ if (v > s.cnt && f - s.lastTick >= gap) { k.withMinor(() => sq(T, 1100 + 1300 * prog(T, 2.9, 5.6), 0.022, 0.03, -0.4)); s.lastTick = f; }
409
+ s.cnt = v;
410
+ } else if (T > 5.6 && s.cnt < 1e6) { k.play('coin', T, { gain: 0.9, pan: -0.3 }); s.cnt = 1e6; }
411
+ // battle command cursor
412
+ const phase = T < 6.2 ? -1 : T < 7.4 ? 0 : T < 8.3 ? 1 : T < 9.4 ? 2 : 0;
413
+ if (phase !== s.phase && (phase === 2 || (phase === 0 && T > 9))) k.play('blip', T, { f: 'A5', dur: 0.04, gain: 0.9, pan: -0.5, minor: true }); // CHECK move is covered by the MISS hit
414
+ s.phase = phase;
415
+ // ADJUST: weight bars change in 4 stepped levels -> stat-up blips
416
+ const adj = Math.floor(prog(T, 8.6, 9.2) * 4);
417
+ if (adj > s.adj) k.withMinor(() => sq(T, NOTES_UP[adj - 1], adj === 4 ? 0.14 : 0.06, 0.05, 0.1));
418
+ s.adj = adj;
419
+ // LEVEL screen: accuracy 61 -> 97 (ease-out), one tick per tick-of-change so it slows as it settles
420
+ if (T >= 10.95) {
421
+ const acc = Math.round(61 + 36 * H.ease.out(prog(T, 10.95, 12.4)));
422
+ if (acc > s.acc) k.withMinor(() => sq(T, 520 + (acc - 61) * 22, 0.03, 0.04));
423
+ s.acc = acc;
424
+ }
425
+ // takeaway window opens in steps, then the text lands
426
+ if (T >= 13.0) {
427
+ const op = Math.floor(prog(T, 13.0, 13.25) * 4);
428
+ if (op > s.op && op < 4) k.withMinor(() => tri(T, ['C5', 'E5', 'G5'][Math.min(2, op)], 0.05, 0.12));
429
+ s.op = op;
430
+ }
431
+ }
432
+
433
+ // ---- one-off cues (first tick where the condition is true)
434
+ k.play('select', 0.02, { gain: 0.7, minor: true }); // dialog window on screen
435
+ // wavy battle swirl 5.9-6.5 (peak 6.2): warbling square + grainy noise sweep, crash on the cut
436
+ for (let f = Math.ceil(5.9 * FPS); f < 6.5 * FPS; f++) { const T = f / FPS, kk = 1 - Math.abs(T - 6.2) / 0.3; sq(T, 300 + kk * 900 * (f % 2 ? 1 : 1.26), 0.07, 0.045 + kk * 0.02); }
437
+ k.noise(5.9, { dur: 0.6, filter: 'bandpass', f: [[0, 400], [0.3, 3000], [0.6, 500]], q: 1.4, gain: 0.14, rate: 0.3, a: 0.28 });
438
+ crash(6.25, 0.3, 0.12);
439
+ k.play('text', 79 / FPS, { n: 4, rate: 0.045, gain: 1.1, pan: 0.4 }); // T > 6.5: "DOG?" bubble from the model
440
+ // CHECK: MISS! + screen flash + shake, then the "wrong" buzz under IT'S A CAT!
441
+ const MISS = 89 / FPS; // T >= 7.4
442
+ k.play('hurt', MISS, { gain: 1.4, pan: -0.4 }); crash(MISS, 0.35, 0.22);
443
+ k.play('error', MISS + 0.3, { gain: 0.9 });
444
+ // ADJUST: red error trail travels picture -> model (8.3-8.8), panned left to right, falling
445
+ k.withMinor(() => { for (let f = 100; f <= 105; f++) { const T = f / FPS, kk = prog(T, 8.3, 8.8); sq(T, 880 * Math.pow(2, -kk), 0.05, 0.04, -0.5 + kk); } });
446
+ k.withMinor(() => sq(102 / FPS, 'A2', 0.14, 0.06, 0)); // "ERROR" label (k > 0.3)
447
+ // GUESS 2: "CAT!" bubble, then HIT! lands (the correction)
448
+ k.play('text', 115 / FPS, { n: 4, rate: 0.045, gain: 1.1, pan: 0.4 }); // T > 9.5
449
+ const HIT = 118 / FPS; // T > 9.75
450
+ crash(HIT, 0.18, 0.16);
451
+ ['G5', 'C6', 'E6'].forEach((n, i) => sq(HIT + i * 0.05, n, 0.06, 0.06, -0.3));
452
+ sq(HIT + 0.15, 'G6', 0.25, 0.05, -0.3); tri(HIT, 'C4', 0.3, 0.12);
453
+ // iris: closes on the model (square sweep down), opens on the level screen (sweep up)
454
+ k.tone(124 / FPS, { f: [[0, 1400], [0.27, 160]], type: 'square', dur: 0.28, gain: 0.045, a: 0.002, hold: 0.2, pan: 0.4 });
455
+ k.tone(128 / FPS, { f: [[0, 160], [0.24, 1400]], type: 'square', dur: 0.25, gain: 0.045, a: 0.002, hold: 0.18 });
456
+ // LEVEL UP! at 97% (the biggest moment): power-up run, held major chord, sparkles
457
+ const LVL = 149 / FPS; // p >= 1 at T >= 12.4
458
+ k.play('powerup', LVL, { gain: 1.4 });
459
+ ['C6', 'E6', 'G6'].forEach((n) => sq(LVL + 0.32, n, 0.6, 0.035)); tri(LVL + 0.32, 'C4', 0.6, 0.22);
460
+ k.play('sparkle', LVL + 0.12, { n: 7, gain: 0.8 });
461
+ // takeaway lands: resolving chip cadence (B -> C over a C bass)
462
+ const TAKE = 159 / FPS; // window fully open at T >= 13.25
463
+ sq(TAKE, 'B5', 0.08, 0.055); sq(TAKE + 0.08, 'C6', 0.7, 0.055); sq(TAKE + 0.08, 'G5', 0.7, 0.03); tri(TAKE, 'C4', 0.8, 0.22);
464
+ }
465
+
466
+ registerStyle({
467
+ id: 'pixel-16bit',
468
+ name: '16-Bit Pixel Odyssey',
469
+ category: 'Retro Digital',
470
+ description: 'A SNES-era cutscene: 320x180 hand-placed pixels blown up 4x, a strict ~40-colour palette, banded sky with checkerboard dither transitions, layered parallax and an RPG dialog box.',
471
+ motion: 'A SNES cutscene opening: 16-step brightness fade-in, parallax layers rise into place in whole-pixel steps, the hero walks on with a 2-frame cycle, the drone flies in, hearts pop, the window snaps open and dialog types out.',
472
+ fonts: ['8px "Press Start 2P"', '16px "Press Start 2P"'],
473
+ duration: 6,
474
+ frameT: 5.8,
475
+ readsPixels: true,
476
+ explainerDuration: 14.5,
477
+ explainerNote: 'Tells it as a SNES RPG: the question is a dialog line whose window snaps up into a quest header, a mosaic transition opens a training hall where labelled CAT/DOG cards ride a chevron conveyor into the MODEL while an EXAMPLES window counts, a wavy battle-swirl starts a turn-based fight (GUESS/CHECK/ADJUST command menu, MISS! damage pop and screen flash, red error trail, weight stat bars), and an iris wipe lands on a LEVEL UP accuracy screen.',
478
+ explainer(ctx, W, H_, t) { pixExplainer(ctx, W, H_, t); },
479
+ explainerSound(k) { pixSound(k); },
480
+ draw(ctx, W, H_, t) {
481
+ // game timing: build/sprite logic runs on a 12 fps tick, positions snap to the 320x180 grid
482
+ const T = Math.floor(t * 12) / 12;
483
+ const prog = (a, b) => H.clamp((T - a) / (b - a));
484
+ const rise = (a, b, dist) => Math.round((1 - H.ease.out(prog(a, b))) * dist);
485
+ const buf = new Uint8ClampedArray(LW * LH * 4);
486
+ let OY = 0; // per-layer vertical offset (layers rise into place)
487
+ const put = (x, y, c) => {
488
+ x |= 0; y = (y + OY) | 0;
489
+ if (x < 0 || y < 0 || x >= LW || y >= LH) return;
490
+ const i = (y * LW + x) * 4; buf[i] = c[0]; buf[i + 1] = c[1]; buf[i + 2] = c[2]; buf[i + 3] = 255;
491
+ };
492
+ const get = (x, y) => { y += OY; if (y < 0 || y >= LH) return [0, 0, 0]; const i = (y * LW + x) * 4; return [buf[i], buf[i + 1], buf[i + 2]]; };
493
+ const rect = (x, y, w, h, c) => { for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) put(x + i, y + j, c); };
494
+ // dithered blend between two palette colours (a = amount of c2)
495
+ const dput = (x, y, c1, c2, a) => put(x, y, a > bay(x, y) ? c2 : c1);
496
+ const sprite = (rows, pal, ox, oy, flip, scale = 1) => {
497
+ const cp = {}; for (const k in pal) cp[k] = C(pal[k]);
498
+ rows.forEach((row, j) => [...row].forEach((ch, i) => {
499
+ if (ch === '.') return;
500
+ const xx = flip ? row.length - 1 - i : i;
501
+ for (let a = 0; a < scale; a++) for (let b = 0; b < scale; b++) put(ox + xx * scale + a, oy + j * scale + b, cp[ch]);
502
+ }));
503
+ };
504
+ const r = H.rng(1601), nz = H.makeNoise(77);
505
+
506
+ // ---------- 1. SKY: solid bands with narrow checker-dithered seams ----------
507
+ const horizon = 118;
508
+ for (let y = 0; y < LH; y++) {
509
+ const f = Math.pow(H.clamp(y / horizon), 1.25) * (P.sky.length - 1);
510
+ const i = Math.min(P.sky.length - 2, Math.floor(f)), fr = f - i;
511
+ const a = H.smooth(0.55, 1.0, fr);
512
+ for (let x = 0; x < LW; x++) dput(x, y, P.sky[i], P.sky[i + 1], a);
513
+ }
514
+ // stars (only in top bands), some twinkle with t
515
+ for (let s = 0; s < 70; s++) {
516
+ const x = r.int(0, LW - 1), y = r.int(0, 46), ph = r() * 6.28, big = r.chance(0.12);
517
+ const tw = Math.sin(t * 3 + ph) > -0.2;
518
+ if (!tw) continue;
519
+ put(x, y, y < 24 ? P.star : P.starDim);
520
+ if (big) { put(x - 1, y, P.starDim); put(x + 1, y, P.starDim); put(x, y - 1, P.starDim); put(x, y + 1, P.starDim); }
521
+ }
522
+
523
+ // ---------- 2. SETTING SUN with retro stripe cut-outs ----------
524
+ const sx = 236, sy = 92 + rise(0, 1.8, 34), sr = 30;
525
+ for (let y = sy - sr; y <= sy + sr; y++) {
526
+ const ry = (y - (sy - sr)) / (2 * sr);
527
+ // stripe gaps grow towards the bottom
528
+ if (y > sy - 4) { const k = y - (sy - 4); const period = 7, gap = 1 + Math.floor(k / 9); if ((k % period) < gap) continue; }
529
+ for (let x = sx - sr; x <= sx + sr; x++) {
530
+ if ((x - sx) ** 2 + (y - sy) ** 2 > sr * sr) continue;
531
+ const c = ry < 0.45 ? (ry < 0.3 ? P.sunHi : (0.3 + 0.15 * bay(x, y) > ry ? P.sunHi : P.sunMid)) : (ry < 0.55 ? P.sunMid : (ry - 0.55) * 3 > bay(x, y) ? P.sunLo : P.sunMid);
532
+ put(x, y, c);
533
+ }
534
+ }
535
+
536
+ // ---------- 3. CLOUD STREAKS (lit from below by the sun) ----------
537
+ const cloud = (cx, cy, w, h) => {
538
+ for (let y = cy - h; y <= cy + h; y++) for (let x = cx - w; x <= cx + w; x++) {
539
+ const dx = (x - cx) / w, dy = (y - cy) / h;
540
+ const lump = 1 + 0.25 * Math.sin(x * 0.35 + cx);
541
+ if (dx * dx + (dy * dy) / lump > 1) continue;
542
+ const below = dy > 0.15, rim = dy > 0.55;
543
+ put(x, y, rim ? P.cloudHot : below ? P.cloudLit : P.cloud);
544
+ }
545
+ };
546
+ const cdrift = t * 2;
547
+ cloud(((60 + cdrift) % 380) - 30, 40, 34, 3);
548
+ cloud(((140 + cdrift * 0.8) % 380) - 30, 54, 22, 2);
549
+ cloud(((300 + cdrift * 1.1) % 380) - 30, 30, 26, 2);
550
+ cloud(((210 + cdrift * 0.6) % 380) - 30, 66, 40, 2);
551
+
552
+ // ---------- 4. MOUNTAINS (two parallax layers, rim-lit toward the sun) ----------
553
+ const mountains = (base, amp, freq, seed, scroll, col, lit) => {
554
+ const hgt = [];
555
+ for (let x = -1; x <= LW; x++) {
556
+ const u = (x + scroll) * freq;
557
+ const v = Math.abs(nz.fbm2(u, seed, 3)) * 1.6 + 0.35 * nz.n2(u * 3.1, seed + 9);
558
+ hgt.push(Math.round(base - amp * v));
559
+ }
560
+ for (let x = 0; x < LW; x++) {
561
+ const top = hgt[x + 1], facing = hgt[x + 2] < hgt[x + 1] ? 0 : 1; // slopes rising rightwards face the sun
562
+ for (let y = top; y < LH; y++) {
563
+ const d = y - top;
564
+ if (d < 2 && (facing || x > sx - 40)) put(x, y, lit);
565
+ else if (d < 5 && facing) dput(x, y, col, lit, 0.5 - d * 0.1);
566
+ else put(x, y, col);
567
+ }
568
+ }
569
+ };
570
+ OY = rise(0.5, 1.2, 50); mountains(108, 30, 0.018, 3.3, 0, P.mtFar, P.mtFarLit);
571
+ OY = rise(0.7, 1.4, 50); mountains(118, 20, 0.03, 8.1, 0, P.mtNear, P.mtNearLit);
572
+ OY = rise(1.0, 1.6, 64);
573
+
574
+ // ---------- 5. FUTURE CITY: back row, mega-tower AI core, front row ----------
575
+ const cityBase = 150;
576
+ const building = (x, w, h, col, colLit, winChance, layerSeed, front) => {
577
+ const top = cityBase - h;
578
+ rect(x, top, w, h, col);
579
+ for (let y = top; y < cityBase; y++) put(x + w - 1, y, colLit); // sun-side edge
580
+ rect(x, top, w, 1, colLit);
581
+ const rr = H.rng(layerSeed);
582
+ if (rr.chance(0.4)) { // antenna with blinking light
583
+ const ah = rr.int(3, 9); for (let k = 1; k <= ah; k++) put(x + (w >> 1), top - k, col);
584
+ if (Math.sin(t * 4 + layerSeed) > 0) put(x + (w >> 1), top - ah - 1, P.red);
585
+ }
586
+ const wc = rr.pick([P.winY, P.winY, P.winC, P.winP]);
587
+ const sp = front ? 3 : 2;
588
+ for (let wy = top + 2; wy < cityBase - 2; wy += sp) for (let wx = x + 1; wx < x + w - 2; wx += 2) {
589
+ const on = rr() < winChance && Math.sin(t * 0.7 + wx * 13.1 + wy * 7.7) > -0.85;
590
+ if (on) put(wx, wy, wc); else if (front && rr.chance(0.3)) put(wx, wy, P.winDim);
591
+ }
592
+ };
593
+ // back row
594
+ const sc1 = 0;
595
+ let bx = -((sc1) % 23) - 10;
596
+ const rb = H.rng(404);
597
+ while (bx < LW) { const w = rb.int(9, 18), h = rb.int(18, 44); building(bx, w, h, P.bldBack, P.bldBackLit, 0.35, bx * 7 + 11 + sc1 * 0, false); bx += w + rb.int(0, 3); }
598
+
599
+ // mega-tower: the AI core (focal point)
600
+ OY = rise(1.3, 2.0, 132);
601
+ const coreOn = T >= 2.08 && !(T >= 2.17 && T < 2.25) && !(T >= 2.33 && T < 2.38);
602
+ const tx = 168, tTop = 34;
603
+ for (let y = tTop; y < cityBase; y++) {
604
+ const k = (y - tTop) / (cityBase - tTop);
605
+ const hw = Math.round(3 + 12 * Math.pow(k, 1.7));
606
+ for (let x = tx - hw; x <= tx + hw; x++) {
607
+ const edge = x >= tx + hw - 1;
608
+ put(x, y, edge ? P.bldFrontLit : (x === tx ? P.bldBackLit : P.bldFront));
609
+ }
610
+ if (y > 60 && y % 4 === 0) for (let x = tx - hw + 2; x < tx + hw - 1; x += 3) if (Math.sin(x * 3.1 + y) > 0.1) put(x, y, P.winC);
611
+ }
612
+ for (let k = 1; k < 14; k++) put(tx, tTop - k, P.bldFrontLit);
613
+ put(tx, tTop - 14, Math.sin(t * 5) > 0 ? P.red : P.bldFrontLit);
614
+ // core ring + glowing orb with dithered halo
615
+ const cy0 = 52, pulse = 0.5 + 0.5 * Math.sin(t * 2.4);
616
+ if (coreOn) {
617
+ const haloR = 20 + pulse * 3;
618
+ for (let y = cy0 - 26; y <= cy0 + 26; y++) for (let x = tx - 26; x <= tx + 26; x++) {
619
+ const d = Math.hypot(x - tx, y - cy0);
620
+ if (d > haloR || d < 7) continue;
621
+ const a = d < 12 ? 0.5 : d < 16 ? 0.25 : 0.0625;
622
+ if (a > bay(x, y)) { const c = get(x, y); put(x, y, H.mix(c, P.coreMid, 0.5).map(Math.round)); }
623
+ }
624
+ for (let y = cy0 - 7; y <= cy0 + 7; y++) for (let x = tx - 7; x <= tx + 7; x++) {
625
+ const d = Math.hypot(x - tx + 1.5, y - cy0 + 1.5);
626
+ if (Math.hypot(x - tx, y - cy0) > 7.2) continue;
627
+ put(x, y, d < 3 ? P.core : d < 6 ? (d - 3) / 3 > bay(x, y) ? P.coreMid : P.core : P.coreMid);
628
+ }
629
+ for (let y = cy0 - 7; y <= cy0 + 7; y++) for (let x = tx - 7; x <= tx + 7; x++) {
630
+ const d = Math.hypot(x - tx, y - cy0); if (d > 6.2 && d <= 7.2) put(x, y, P.coreDeep);
631
+ }
632
+ // orbit ring (ellipse) - back half then front half
633
+ const ringA = t * 1.2;
634
+ for (let s = 0; s < 220; s++) {
635
+ const a = (s / 220) * Math.PI * 2;
636
+ const x = tx + Math.cos(a) * 15, y = cy0 + Math.sin(a) * 4;
637
+ const front = Math.sin(a) > 0;
638
+ if (!front && Math.hypot(x - tx, y - cy0) < 7.2) continue;
639
+ const lit = Math.cos(a - ringA) > 0.6;
640
+ put(x, y, lit ? P.core : front ? P.coreMid : P.coreDeep);
641
+ }
642
+ } else { // unlit core shell
643
+ for (let y = cy0 - 7; y <= cy0 + 7; y++) for (let x = tx - 7; x <= tx + 7; x++) { const d = Math.hypot(x - tx, y - cy0); if (d <= 7.2) put(x, y, d > 6.2 ? P.K : P.coreDeep); }
644
+ }
645
+ // front row
646
+ OY = rise(1.6, 2.1, 44);
647
+ const sc2 = 0;
648
+ let fx = -(sc2 % 31) - 12;
649
+ const rf = H.rng(808);
650
+ while (fx < LW) {
651
+ const w = rf.int(12, 24), h = rf.int(10, 30);
652
+ if (Math.abs(fx + w / 2 - tx) > 22) building(fx, w, h, P.bldFront, P.bldFrontLit, 0.45, fx * 3 + 5, true);
653
+ fx += w + rf.int(1, 4);
654
+ }
655
+ OY = 0;
656
+ // street-level glow below the skyline
657
+ const streetY = cityBase + rise(1.0, 1.6, 40);
658
+ for (let y = streetY; y < LH; y++) for (let x = 0; x < LW; x++) dput(x, y, P.bldFront, C('#3a1e50'), 0.5 - (y - streetY) * 0.04);
659
+ // horizon haze band where city meets mountains (atmospheric depth)
660
+ for (let y = 112; y < 128; y++) for (let x = 0; x < LW; x++) { const a = 0.32 * (1 - Math.abs(y - 120) / 8); if (a > bay(x, y)) { const c = get(x, y); if (c[0] + c[1] + c[2] < 260) put(x, y, H.mix(c, P.cloudLit, 0.35).map(Math.round)); } }
661
+ // flying cars with light trails
662
+ for (let k = 0; k < (T > 2.1 ? 6 : 0); k++) {
663
+ const lane = 74 + k * 9 + (k % 2) * 3, dir = k % 2 ? -1 : 1, sp = 18 + k * 7;
664
+ let x = ((k * 97 + t * sp * dir) % 360 + 360) % 360 - 20;
665
+ const y = lane;
666
+ const head = k % 3 === 0 ? P.winP : P.winY;
667
+ for (let q = 1; q < 10; q++) if ((10 - q) / 10 > bay(Math.round(x - dir * q), y) + 0.1) put(x - dir * q, y, q < 4 ? head : P.cloudLit);
668
+ put(x, y, P.star); put(x + dir, y, head);
669
+ }
670
+
671
+ // ---------- 6. FOREGROUND ROOFTOP (fastest parallax: static frame of reference) ----------
672
+ const roofY = 128;
673
+ rect(0, roofY, 178, LH - roofY, P.roof);
674
+ rect(0, roofY, 177, 1, P.roofRim);
675
+ rect(0, roofY + 1, 177, 1, P.roofTop);
676
+ for (let y = roofY; y < LH; y++) put(177, y, P.roofTop);
677
+ for (let y = roofY + 2; y < LH; y++) for (let x = 0; x < 176; x++) if (((x + y) & 7) === 0 && y % 2) put(x, y, P.roofMid);
678
+ // railing
679
+ for (let x = 0; x < 176; x++) put(x, roofY - 9, P.K);
680
+ for (let x = 0; x < 176; x++) if ((x % 12) === 4 || x === 175) for (let y = roofY - 9; y < roofY; y++) put(x, y, P.K);
681
+ for (let x = 0; x < 176; x++) put(x, roofY - 5, P.K);
682
+ for (let x = 0; x < 176; x++) if (x % 3 === 0) put(x, roofY - 10, P.roofRim);
683
+ // rooftop HVAC unit + satellite dish (silhouettes w/ rim light)
684
+ rect(14, roofY - 18, 26, 18, P.roof);
685
+ rect(14, roofY - 18, 26, 1, P.roofRim);
686
+ for (let y = roofY - 17; y < roofY; y++) put(39, y, P.roofTop);
687
+ for (let x = 17; x < 37; x += 3) for (let y = roofY - 15; y < roofY - 4; y++) put(x, y, P.roofMid);
688
+ // radio mast with guy wires and a blinking beacon
689
+ for (let y = roofY - 46; y < roofY; y++) { put(56, y, P.roof); if (y % 4 === 0) { put(55, y, P.roof); put(57, y, P.roof); } }
690
+ for (let k = 0; k < 30; k++) { put(56 - k * 0.5, roofY - 40 + k * 1.33, P.roof); put(56 + k * 0.5, roofY - 40 + k * 1.33, P.roof); }
691
+ put(57, roofY - 46, P.roofRim);
692
+ if (Math.sin(t * 3) > 0) { put(56, roofY - 47, P.red); put(55, roofY - 47, C('#ff8a8a')); }
693
+ // water tank on stilts
694
+ const TK = 66; rect(TK, roofY - 30, 18, 16, P.roof);
695
+ for (let y = roofY - 30; y < roofY - 14; y++) { put(TK + 17, y, P.roofTop); if (y % 4 === 0) for (let x = TK; x < TK + 17; x++) put(x, y, P.roofMid); }
696
+ for (let x = TK - 1; x < TK + 19; x++) put(x, roofY - 31, P.roofRim);
697
+ for (let x = TK + 1; x < TK + 17; x++) { const top = roofY - 32 - Math.round(3 * Math.sin(((x - TK - 1) / 16) * Math.PI)); for (let y = top; y < roofY - 31; y++) put(x, y, P.roof); put(x, top, P.roofTop); }
698
+ for (let y = roofY - 14; y < roofY; y++) { put(TK + 2, y, P.roof); put(TK + 15, y, P.roof); put(TK + 2 + (y - roofY + 14), y, P.roof); }
699
+
700
+ // ---------- 7. CHARACTERS ----------
701
+ const walkP = prog(2.2, 3.0), walking = walkP > 0 && walkP < 1;
702
+ const heroX = Math.round(-16 + 114 * walkP), heroY = roofY - HERO.length - (walking && Math.floor(T * 8) % 2 ? 1 : 0);
703
+ // scarf: 2-frame flap
704
+ const flap = Math.floor(T * 6) % 2;
705
+ const scarf = flap ? [[-1, 0], [-2, 0], [-3, 1], [-4, 1], [-5, 1], [-6, 2], [-7, 2]] : [[-1, 0], [-2, 1], [-3, 1], [-4, 0], [-5, 0], [-6, 1], [-7, 0]];
706
+ scarf.forEach(([dx, dy], i) => { put(heroX + 5 + dx, heroY + 8 + dy, P.winP); put(heroX + 5 + dx, heroY + 9 + dy, i > 3 ? P.cloudLit : P.winP); put(heroX + 5 + dx, heroY + 10 + dy, P.K); });
707
+ sprite(walking && Math.floor(T * 8) % 2 ? HERO_WALK : HERO, HERO_PAL, heroX, heroY, false);
708
+ // rim light on hero's sun-facing side
709
+ for (let j = 0; j < HERO.length; j++) { const row = HERO[j]; const last = row.search(/[^.][.]*$/); if (last > 0 && row[last - 1] && row[last - 1] !== '.' && row[last - 1] !== 'K') put(heroX + last - 1, heroY + j, C('#ffb08a')); }
710
+ // drone bobbing
711
+ const dY = Math.round(roofY - 40 + Math.sin(T * 3) * 2) - rise(2.5, 3.1, 30);
712
+ const dX = 120 + rise(2.5, 3.1, 210);
713
+ sprite(DRONE, DRONE_PAL, dX, dY, false);
714
+ // drone thruster
715
+ if (Math.floor(T * 10) % 2) { put(dX + 2, dY + 10, P.winC); put(dX + 8, dY + 10, P.winC); } else { put(dX + 2, dY + 11, P.coreMid); put(dX + 8, dY + 11, P.coreMid); }
716
+ // shadow under hero
717
+ for (let x = Math.max(0, heroX + 2); x < heroX + 13; x++) put(x, roofY + 1, P.K);
718
+
719
+ // ---------- 8. TEXT RASTERISER (threshold so glyphs are pure pixels) ----------
720
+ const tc = H.layer(LW, LH); const tx2 = tc.getContext('2d', { willReadFrequently: true });
721
+ const text = (str, x, y, col, shadow, font = '8px "Press Start 2P"') => {
722
+ tx2.clearRect(0, 0, LW, LH); tx2.font = font; tx2.textBaseline = 'top'; tx2.fillStyle = '#fff'; tx2.fillText(str, 0, 0);
723
+ const w = Math.ceil(tx2.measureText(str).width) + 2, d = tx2.getImageData(0, 0, w, 12).data;
724
+ for (let j = 0; j < 12; j++) for (let i = 0; i < w; i++) if (d[(j * w + i) * 4 + 3] > 110) {
725
+ if (shadow) put(x + i + 1, y + j + 1, shadow);
726
+ }
727
+ for (let j = 0; j < 12; j++) for (let i = 0; i < w; i++) if (d[(j * w + i) * 4 + 3] > 110) put(x + i, y + j, col);
728
+ return w;
729
+ };
730
+
731
+ // ---------- 9. HUD ----------
732
+ const white = C('#f8f8ff'), gold = C('#ffd27a'), ink = C('#0a0618');
733
+ // hearts
734
+ const HEART = ['.KK.KK.', 'KRRKRRK', 'KRWRRRK', 'KRRRRRK', '.KRRRK.', '..KRK..', '...K...'];
735
+ const nHearts = H.clamp(Math.floor((T - 2.9) / 0.12) + 1, 0, 4);
736
+ for (let h = 0; h < nHearts; h++) sprite(HEART, { K: '#0a0618', R: h < 3 ? '#ff3a5a' : '#4a2a5a', W: h < 3 ? '#ffd0e0' : '#6a4a7a' }, 8 + h * 9, 7, false);
737
+ if (T >= 2.9) {
738
+ text('SCORE', 236, 6, white, ink);
739
+ text(String(Math.floor(4096 * H.ease.out(prog(2.9, 3.6)) + Math.max(0, T - 3.6) * 64)).padStart(6, '0'), 236, 15, gold, ink);
740
+ }
741
+
742
+ // ---------- 10. DIALOG BOX (SNES RPG window) ----------
743
+ const openP = Math.floor(prog(3.1, 3.4) * 4) / 4;
744
+ if (openP > 0) {
745
+ const bx0 = 6, bh = openP < 1 ? Math.max(4, Math.round(38 * openP)) : 38, by0 = 155 - (bh >> 1), bw = 308;
746
+ const WB = ['#4060d0', '#3450bc', '#2a42a6', '#22358e', '#1a2a78', '#142064'].map(C);
747
+ for (let y = by0; y < by0 + bh; y++) {
748
+ const f = ((y - by0) / bh) * (WB.length - 1), i = Math.min(WB.length - 2, Math.floor(f)), a = H.smooth(0.7, 1, f - i);
749
+ for (let x = bx0; x < bx0 + bw; x++) {
750
+ const inCorner = (x === bx0 || x === bx0 + bw - 1) && (y === by0 || y === by0 + bh - 1);
751
+ if (inCorner) continue;
752
+ dput(x, y, WB[i], WB[i + 1], a);
753
+ }
754
+ }
755
+ // borders: dark outer, white, grey inner
756
+ const grey = C('#9aa0c8');
757
+ for (let x = bx0 + 1; x < bx0 + bw - 1; x++) { put(x, by0, white); put(x, by0 + bh - 1, white); put(x, by0 + 1, grey); put(x, by0 + bh - 2, grey); put(x, by0 - 1, ink); put(x, by0 + bh, ink); }
758
+ for (let y = by0 + 1; y < by0 + bh - 1; y++) { put(bx0, y, white); put(bx0 + bw - 1, y, white); put(bx0 + 1, y, grey); put(bx0 + bw - 2, y, grey); put(bx0 - 1, y, ink); put(bx0 + bw, y, ink); }
759
+ put(bx0 + 1, by0 + 1, white); put(bx0 + bw - 2, by0 + 1, white); put(bx0 + 1, by0 + bh - 2, white); put(bx0 + bw - 2, by0 + bh - 2, white);
760
+ if (openP >= 1) {
761
+ // portrait frame with drone at 2x
762
+ const px0 = bx0 + 6, py0 = by0 + 5;
763
+ rect(px0, py0, 28, 28, C('#0a1040'));
764
+ for (let i = 0; i < 28; i++) { put(px0 + i, py0, grey); put(px0 + i, py0 + 27, white); put(px0, py0 + i, grey); put(px0 + 27, py0 + i, white); }
765
+ sprite(DRONE, DRONE_PAL, px0 + 3, py0 + 4, false, 2);
766
+ // name + typed line
767
+ text('NOVA', px0 + 34, by0 + 6, gold, ink);
768
+ const line = 'THE CITY AI IS AWAKE.';
769
+ const shown = Math.min(line.length, Math.floor(prog(3.45, 4.3) * line.length));
770
+ text(line.slice(0, shown), px0 + 34, by0 + 20, white, ink);
771
+ // bouncing next-arrow
772
+ if (shown >= line.length) {
773
+ const ay = by0 + bh - 9 + (Math.floor(t * 4) % 2);
774
+ const ARW = ['KKKKKKK', 'KWWWWWK', '.KWWWK.', '..KWK..', '...K...'];
775
+ sprite(ARW, { K: '#0a0618', W: '#ffd27a' }, bx0 + bw - 14, ay, false);
776
+ }
777
+ } // window fully open
778
+ } // window visible
779
+
780
+ // SNES master-brightness fade-in (16 discrete levels)
781
+ const lvl = Math.min(15, Math.floor((T / 0.8) * 15));
782
+ if (lvl < 15) for (let i = 0; i < buf.length; i += 4) { buf[i] = buf[i] * lvl / 15; buf[i + 1] = buf[i + 1] * lvl / 15; buf[i + 2] = buf[i + 2] * lvl / 15; }
783
+
784
+ // ---------- 11. blit low-res buffer, scale x4 with nearest neighbour ----------
785
+ const lc = H.layer(LW, LH), lx = lc.getContext('2d');
786
+ lx.putImageData(new ImageData(buf, LW, LH), 0, 0);
787
+ ctx.save();
788
+ ctx.imageSmoothingEnabled = false;
789
+ ctx.drawImage(lc, 0, 0, W, H_);
790
+ ctx.restore();
791
+ },
792
+ });
793
+ }