@autoaiai/aicut 0.1.7 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/MANUAL.md +94 -84
  2. package/package.json +1 -1
  3. package/product.json +2 -1
  4. package/skills/aicut/SKILL.md +4 -4
  5. package/skills/aicut/references/commands.md +15 -15
  6. package/skills/aicut/references/film-format.md +1 -1
  7. package/skills/aicut/references/recipes.md +4 -4
  8. package/skills/aicut/references/troubleshooting.md +2 -2
  9. package/skills/aicut-motion/SKILL.md +75 -9
  10. package/skills/aicut-motion/THIRD_PARTY_NOTICES.md +14 -0
  11. package/skills/aicut-motion/assets/atlas/lib/harness.js +116 -0
  12. package/skills/aicut-motion/assets/atlas/lib/sound.js +274 -0
  13. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/code.js +937 -0
  14. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/poster.jpg +0 -0
  15. package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/style.json +13 -0
  16. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/code.js +909 -0
  17. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/poster.jpg +0 -0
  18. package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/style.json +13 -0
  19. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/code.js +881 -0
  20. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/poster.jpg +0 -0
  21. package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/style.json +13 -0
  22. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/code.js +897 -0
  23. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/poster.jpg +0 -0
  24. package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/style.json +13 -0
  25. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/code.js +814 -0
  26. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/poster.jpg +0 -0
  27. package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/style.json +13 -0
  28. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/code.js +990 -0
  29. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/poster.jpg +0 -0
  30. package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/style.json +13 -0
  31. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/code.js +994 -0
  32. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/poster.jpg +0 -0
  33. package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/style.json +13 -0
  34. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/code.js +792 -0
  35. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/poster.jpg +0 -0
  36. package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/style.json +13 -0
  37. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/code.js +840 -0
  38. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/poster.jpg +0 -0
  39. package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/style.json +13 -0
  40. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/code.js +1126 -0
  41. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/poster.jpg +0 -0
  42. package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/style.json +13 -0
  43. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/code.js +854 -0
  44. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/poster.jpg +0 -0
  45. package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/style.json +13 -0
  46. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/code.js +1124 -0
  47. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/poster.jpg +0 -0
  48. package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/style.json +13 -0
  49. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/code.js +728 -0
  50. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/poster.jpg +0 -0
  51. package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/style.json +13 -0
  52. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/code.js +664 -0
  53. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/poster.jpg +0 -0
  54. package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/style.json +13 -0
  55. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/code.js +991 -0
  56. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/poster.jpg +0 -0
  57. package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/style.json +13 -0
  58. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/code.js +801 -0
  59. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/poster.jpg +0 -0
  60. package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/style.json +13 -0
  61. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/code.js +692 -0
  62. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/poster.jpg +0 -0
  63. package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/style.json +13 -0
  64. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/code.js +756 -0
  65. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/poster.jpg +0 -0
  66. package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/style.json +13 -0
  67. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/code.js +922 -0
  68. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/poster.jpg +0 -0
  69. package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/style.json +13 -0
  70. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/code.js +852 -0
  71. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/poster.jpg +0 -0
  72. package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/style.json +13 -0
  73. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/code.js +724 -0
  74. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/poster.jpg +0 -0
  75. package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/style.json +13 -0
  76. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/code.js +793 -0
  77. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/poster.jpg +0 -0
  78. package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/style.json +13 -0
  79. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/code.js +846 -0
  80. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/poster.jpg +0 -0
  81. package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/style.json +13 -0
  82. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/code.js +1008 -0
  83. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/poster.jpg +0 -0
  84. package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/style.json +13 -0
  85. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/code.js +680 -0
  86. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/poster.jpg +0 -0
  87. package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/style.json +13 -0
  88. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/code.js +633 -0
  89. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/poster.jpg +0 -0
  90. package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/style.json +13 -0
  91. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/code.js +757 -0
  92. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/poster.jpg +0 -0
  93. package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/style.json +13 -0
  94. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/code.js +828 -0
  95. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/poster.jpg +0 -0
  96. package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/style.json +13 -0
  97. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/code.js +697 -0
  98. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/poster.jpg +0 -0
  99. package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/style.json +13 -0
  100. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/code.js +687 -0
  101. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/poster.jpg +0 -0
  102. package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/style.json +13 -0
  103. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/code.js +727 -0
  104. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/poster.jpg +0 -0
  105. package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/style.json +13 -0
  106. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/code.js +903 -0
  107. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/poster.jpg +0 -0
  108. package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/style.json +13 -0
  109. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/code.js +662 -0
  110. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/poster.jpg +0 -0
  111. package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/style.json +13 -0
  112. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/code.js +951 -0
  113. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/poster.jpg +0 -0
  114. package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/style.json +13 -0
  115. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/code.js +759 -0
  116. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/poster.jpg +0 -0
  117. package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/style.json +13 -0
  118. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/code.js +738 -0
  119. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/poster.jpg +0 -0
  120. package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/style.json +13 -0
  121. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/code.js +793 -0
  122. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/poster.jpg +0 -0
  123. package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/style.json +13 -0
  124. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/code.js +673 -0
  125. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/poster.jpg +0 -0
  126. package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/style.json +13 -0
  127. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/code.js +907 -0
  128. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/poster.jpg +0 -0
  129. package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/style.json +13 -0
  130. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/code.js +833 -0
  131. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/poster.jpg +0 -0
  132. package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/style.json +13 -0
  133. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/code.js +845 -0
  134. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/poster.jpg +0 -0
  135. package/skills/aicut-motion/assets/atlas/styles/041-manga-page/style.json +13 -0
  136. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/code.js +496 -0
  137. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/poster.jpg +0 -0
  138. package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/style.json +13 -0
  139. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/code.js +635 -0
  140. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/poster.jpg +0 -0
  141. package/skills/aicut-motion/assets/atlas/styles/043-light-painting/style.json +13 -0
  142. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/code.js +799 -0
  143. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/poster.jpg +0 -0
  144. package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/style.json +13 -0
  145. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/code.js +547 -0
  146. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/poster.jpg +0 -0
  147. package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/style.json +13 -0
  148. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/code.js +592 -0
  149. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/poster.jpg +0 -0
  150. package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/style.json +13 -0
  151. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/code.js +768 -0
  152. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/poster.jpg +0 -0
  153. package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/style.json +13 -0
  154. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/code.js +666 -0
  155. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/poster.jpg +0 -0
  156. package/skills/aicut-motion/assets/atlas/styles/048-transit-map/style.json +13 -0
  157. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/code.js +559 -0
  158. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/poster.jpg +0 -0
  159. package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/style.json +13 -0
  160. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/code.js +943 -0
  161. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/poster.jpg +0 -0
  162. package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/style.json +13 -0
  163. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/code.js +606 -0
  164. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/poster.jpg +0 -0
  165. package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/style.json +13 -0
  166. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/code.js +730 -0
  167. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/poster.jpg +0 -0
  168. package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/style.json +13 -0
  169. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/code.js +782 -0
  170. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/poster.jpg +0 -0
  171. package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/style.json +13 -0
  172. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/code.js +504 -0
  173. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/poster.jpg +0 -0
  174. package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/style.json +13 -0
  175. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/code.js +517 -0
  176. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/poster.jpg +0 -0
  177. package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/style.json +13 -0
  178. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/code.js +713 -0
  179. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/poster.jpg +0 -0
  180. package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/style.json +13 -0
  181. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/code.js +717 -0
  182. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/poster.jpg +0 -0
  183. package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/style.json +13 -0
  184. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/code.js +540 -0
  185. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/poster.jpg +0 -0
  186. package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/style.json +13 -0
  187. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/code.js +524 -0
  188. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/poster.jpg +0 -0
  189. package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/style.json +13 -0
  190. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/code.js +934 -0
  191. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/poster.jpg +0 -0
  192. package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/style.json +13 -0
  193. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/code.js +690 -0
  194. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/poster.jpg +0 -0
  195. package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/style.json +13 -0
  196. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/code.js +612 -0
  197. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/poster.jpg +0 -0
  198. package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/style.json +13 -0
  199. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/code.js +666 -0
  200. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/poster.jpg +0 -0
  201. package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/style.json +13 -0
  202. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/code.js +651 -0
  203. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/poster.jpg +0 -0
  204. package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/style.json +13 -0
  205. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/code.js +656 -0
  206. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/poster.jpg +0 -0
  207. package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/style.json +13 -0
  208. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/code.js +672 -0
  209. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/poster.jpg +0 -0
  210. package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/style.json +13 -0
  211. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/code.js +548 -0
  212. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/poster.jpg +0 -0
  213. package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/style.json +13 -0
  214. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/code.js +989 -0
  215. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/poster.jpg +0 -0
  216. package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/style.json +13 -0
  217. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/code.js +440 -0
  218. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/poster.jpg +0 -0
  219. package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/style.json +13 -0
  220. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/code.js +895 -0
  221. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/poster.jpg +0 -0
  222. package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/style.json +13 -0
  223. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/code.js +563 -0
  224. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/poster.jpg +0 -0
  225. package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/style.json +13 -0
  226. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/code.js +627 -0
  227. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/poster.jpg +0 -0
  228. package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/style.json +13 -0
  229. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/code.js +1096 -0
  230. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/poster.jpg +0 -0
  231. package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/style.json +13 -0
  232. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/code.js +595 -0
  233. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/poster.jpg +0 -0
  234. package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/style.json +13 -0
  235. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/code.js +665 -0
  236. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/poster.jpg +0 -0
  237. package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/style.json +13 -0
  238. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/code.js +645 -0
  239. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/poster.jpg +0 -0
  240. package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/style.json +13 -0
  241. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/code.js +436 -0
  242. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/poster.jpg +0 -0
  243. package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/style.json +13 -0
  244. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/code.js +1237 -0
  245. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/poster.jpg +0 -0
  246. package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/style.json +13 -0
  247. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/code.js +622 -0
  248. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/poster.jpg +0 -0
  249. package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/style.json +13 -0
  250. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/code.js +524 -0
  251. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/poster.jpg +0 -0
  252. package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/style.json +13 -0
  253. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/code.js +689 -0
  254. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/poster.jpg +0 -0
  255. package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/style.json +13 -0
  256. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/code.js +574 -0
  257. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/poster.jpg +0 -0
  258. package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/style.json +13 -0
  259. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/code.js +469 -0
  260. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/poster.jpg +0 -0
  261. package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/style.json +13 -0
  262. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/code.js +531 -0
  263. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/poster.jpg +0 -0
  264. package/skills/aicut-motion/assets/atlas/styles/084-flow-field/style.json +13 -0
  265. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/code.js +675 -0
  266. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/poster.jpg +0 -0
  267. package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/style.json +13 -0
  268. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/code.js +609 -0
  269. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/poster.jpg +0 -0
  270. package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/style.json +13 -0
  271. package/skills/aicut-motion/references/design-systems/README.md +1 -1
  272. package/skills/aicut-motion/references/motion-delivery.md +55 -0
  273. package/skills/aicut-motion/references/pages.md +1 -1
  274. package/skills/aicut-motion/references/style-index.json +690 -0
  275. package/skills/aicut-motion/references/style-research.md +73 -0
  276. package/skills/aicut-motion/references/style-selection.md +71 -0
  277. package/skills/aicut-motion/scripts/atlas-viewer.html +195 -0
  278. package/skills/aicut-motion/scripts/atlas.mjs +109 -0
  279. package/studio/client.mjs +3 -3
  280. package/studio/server/fetch-github.mjs +14 -3
  281. package/studio/server/server.mjs +71 -6
  282. package/studio/server/skill-summaries.mjs +60 -0
  283. package/studio/server/skills.mjs +121 -7
  284. package/studio/server/styles-store.mjs +235 -0
  285. package/studio/server/template-posters.mjs +2 -2
  286. package/studio/server/templates-page.mjs +93 -93
  287. package/studio/server/templates.mjs +32 -7
  288. package/studio/ui/dist/assets/ExportCenter-DasulhAv.js +1 -0
  289. package/studio/ui/dist/assets/SettingsModal-BZw0Hz2_.js +2 -0
  290. package/studio/ui/dist/assets/apple-touch-icon-ClxQ7j-O.png +0 -0
  291. package/studio/ui/dist/assets/es-C8XPZARI.js +1 -0
  292. package/studio/ui/dist/assets/favicon-Blwx8Trs.png +0 -0
  293. package/studio/ui/dist/assets/index-BjJVmBzB.js +1623 -0
  294. package/studio/ui/dist/assets/index-C3BfKZFO.css +1 -0
  295. package/studio/ui/dist/assets/ja-BeNkdaEj.js +1 -0
  296. package/studio/ui/dist/assets/ko-DcTVhdzK.js +1 -0
  297. package/studio/ui/dist/assets/zh-CN-Cg51ctbL.js +1 -0
  298. package/studio/ui/dist/index.html +4 -3
  299. package/studio/ui/dist/assets/ExportCenter-F-2vF-lQ.js +0 -1
  300. package/studio/ui/dist/assets/SettingsModal-BgVYguNu.js +0 -2
  301. package/studio/ui/dist/assets/es-BRUzvuoM.js +0 -1
  302. package/studio/ui/dist/assets/index-Bhg-9dA3.css +0 -1
  303. package/studio/ui/dist/assets/index-CWur1Iw9.js +0 -1625
  304. package/studio/ui/dist/assets/ja-Cth-uDcW.js +0 -1
  305. package/studio/ui/dist/assets/ko-upvw591D.js +0 -1
  306. package/studio/ui/dist/assets/zh-CN-BCfJFazB.js +0 -1
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": 78,
3
+ "name": "Sketchbook Graphite",
4
+ "nameZh": "石墨素描本",
5
+ "family": "Hand-Drawn",
6
+ "familyZh": "手绘",
7
+ "description": "A graphite study in a spiral sketchbook: faint construction circles and axes under confident darker contours, directional hatching that breaks on the paper tooth, smudged tones, kneaded-eraser highlights and a margin note.",
8
+ "motionNotes": "Motion: Construction lines ghost in first, contours firm up over them, then hatching sweeps across the shadow side while the eraser lifts the highlight.",
9
+ "explainerNotes": "Explainer: A pencil works across a wide sketchbook spread (faint construction pass first, then a firm contour, crescent hatching for form) while the camera follows, then the page physically turns to a fresh sheet for the progress chart; numbers are pencilled and re-pencilled as they count.",
10
+ "code": "code.js",
11
+ "registeredAs": "sketchbook-pencil",
12
+ "source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
13
+ }
@@ -0,0 +1,622 @@
1
+ {
2
+ const COLS = 40, ROWS = 25;
3
+ const PAL = { k: '#000000', r: '#ff0000', g: '#00ff00', y: '#ffff00', b: '#0000ff', m: '#ff00ff', c: '#00ffff', w: '#ffffff' };
4
+ const PKEYS = Object.keys(PAL);
5
+ const PRGB = PKEYS.map((k) => H.hex(PAL[k]));
6
+
7
+ function newGrid() {
8
+ const g = [];
9
+ for (let r = 0; r < ROWS; r++) { const row = []; for (let c = 0; c < COLS; c++) row.push({ ch: ' ', fg: 'w', bg: 'k', mos: 0, dh: 0 }); g.push(row); }
10
+ return g;
11
+ }
12
+ // put text: string, starting col, fg/bg; dh = 1 means double height (occupies row and row+1)
13
+ function put(g, row, col, str, fg = 'w', bg = null, dh = 0) {
14
+ for (let i = 0; i < str.length && col + i < COLS; i++) {
15
+ const cell = g[row][col + i];
16
+ cell.ch = str[i]; cell.fg = fg; cell.mos = 0; cell.dh = dh; if (bg) cell.bg = bg;
17
+ if (dh && row + 1 < ROWS) { const b = g[row + 1][col + i]; b.ch = ' '; b.dh = 2; b.mos = 0; if (bg) b.bg = bg; }
18
+ }
19
+ }
20
+ function band(g, row, c0, c1, bg) { for (let c = c0; c <= c1; c++) g[row][c].bg = bg; }
21
+
22
+ // Rasterize a canvas drawing into sextant mosaic cells with one foreground colour per cell (like the real thing).
23
+ function mosaic(g, col0, row0, cols, rows, bg, drawFn) {
24
+ const S = 4, sw = cols * 2, sh = rows * 3;
25
+ const L = H.layer(sw * S, sh * S), c = L.getContext('2d', { willReadFrequently: true });
26
+ c.scale(S, S); drawFn(c, sw, sh);
27
+ const d = c.getImageData(0, 0, sw * S, sh * S).data;
28
+ // per-subpixel: dominant palette index (or -1)
29
+ const sub = new Int8Array(sw * sh).fill(-1);
30
+ for (let y = 0; y < sh; y++) for (let x = 0; x < sw; x++) {
31
+ const counts = new Array(8).fill(0); let cov = 0;
32
+ for (let j = 0; j < S; j++) for (let i = 0; i < S; i++) {
33
+ const p = ((y * S + j) * sw * S + (x * S + i)) * 4;
34
+ if (d[p + 3] < 128) continue;
35
+ cov++;
36
+ let best = 0, bd = 1e9;
37
+ for (let k = 0; k < 8; k++) { const q = PRGB[k]; const dd = (q[0] - d[p]) ** 2 + (q[1] - d[p + 1]) ** 2 + (q[2] - d[p + 2]) ** 2; if (dd < bd) { bd = dd; best = k; } }
38
+ counts[best]++;
39
+ }
40
+ if (cov >= S * S * 0.5) { let m = 0; for (let k = 1; k < 8; k++) if (counts[k] > counts[m]) m = k; sub[y * sw + x] = m; }
41
+ }
42
+ for (let r = 0; r < rows; r++) for (let cc = 0; cc < cols; cc++) {
43
+ const counts = new Array(8).fill(0);
44
+ for (let j = 0; j < 3; j++) for (let i = 0; i < 2; i++) { const v = sub[(r * 3 + j) * sw + cc * 2 + i]; if (v >= 0 && PKEYS[v] !== bg) counts[v]++; }
45
+ let m = -1, mc = 0; for (let k = 0; k < 8; k++) if (counts[k] > mc) { mc = counts[k]; m = k; }
46
+ const cell = g[row0 + r][col0 + cc];
47
+ cell.bg = bg; cell.ch = ' '; cell.dh = 0;
48
+ if (m < 0) { cell.mos = 0; continue; }
49
+ let bits = 0;
50
+ for (let j = 0; j < 3; j++) for (let i = 0; i < 2; i++) if (sub[(r * 3 + j) * sw + cc * 2 + i] === m) bits |= 1 << (j * 2 + i);
51
+ cell.mos = bits; cell.fg = PKEYS[m];
52
+ }
53
+ }
54
+
55
+ function buildPage(t) {
56
+ const g = newGrid();
57
+ // Row 0 header
58
+ const sec = (45 + Math.floor(t)) % 60, mn = 23 + Math.floor((45 + t) / 60);
59
+ const searching = t < 0.9 ? (Math.floor(t * 24) * 37 % 899 + 100).toString(16).toUpperCase().slice(-3) : '100';
60
+ put(g, 0, 0, 'P100', 'w');
61
+ put(g, 0, 6, 'AITEXT ' + searching, t < 0.9 ? 'g' : 'w');
62
+ put(g, 0, 18, 'Thu 09 Oct', 'w');
63
+ put(g, 0, 31, `10:${String(mn).padStart(2, '0')}/${String(sec).padStart(2, '0')}`, 'y');
64
+
65
+ // Rows 1-3: blue band with mosaic masthead
66
+ for (let r = 1; r <= 3; r++) band(g, r, 0, 39, 'b');
67
+ mosaic(g, 1, 1, 28, 3, 'b', (c) => {
68
+ const GL = {
69
+ A: ['..XXXX..', '.XXXXXX.', 'XX....XX', 'XX....XX', 'XXXXXXXX', 'XXXXXXXX', 'XX....XX', 'XX....XX', 'XX....XX'],
70
+ I: ['XX', 'XX', 'XX', 'XX', 'XX', 'XX', 'XX', 'XX', 'XX'],
71
+ N: ['XX....XX', 'XXX...XX', 'XXXX..XX', 'XXXX..XX', 'XX.XX.XX', 'XX..XXXX', 'XX..XXXX', 'XX...XXX', 'XX....XX'],
72
+ E: ['XXXXXXX', 'XXXXXXX', 'XX.....', 'XX.....', 'XXXXXX.', 'XXXXXX.', 'XX.....', 'XXXXXXX', 'XXXXXXX'],
73
+ W: ['XX......XX', 'XX......XX', 'XX......XX', 'XX..XX..XX', 'XX..XX..XX', 'XX.XXXX.XX', 'XXXX..XXXX', 'XXX....XXX', 'XX......XX'],
74
+ S: ['.XXXXXX', 'XXXXXXX', 'XX.....', 'XXXXXX.', '.XXXXXX', '.....XX', '.....XX', 'XXXXXXX', 'XXXXXX.'],
75
+ };
76
+ let x = 0;
77
+ for (const ch of 'AI NEWS') {
78
+ if (ch === ' ') { x += 4; continue; }
79
+ const gl = GL[ch];
80
+ c.fillStyle = PAL.y;
81
+ gl.forEach((row, j) => { for (let i = 0; i < row.length; i++) if (row[i] === 'X') c.fillRect(x + i, j, 1, 1); });
82
+ x += gl[0].length + 2;
83
+ }
84
+ });
85
+ // red box on the right of the masthead
86
+ for (let r = 1; r <= 3; r++) band(g, r, 30, 39, 'r');
87
+ put(g, 1, 31, 'TOP', 'w', 'r', 0);
88
+ put(g, 2, 31, 'STORIES', 'y', 'r', 1);
89
+
90
+ // Double-height headline
91
+ put(g, 5, 1, 'AI LEARNS TO DREAM', 'y', null, 1);
92
+ // body (cyan / white)
93
+ const body = ['Researchers say the new', 'model now writes poems,', 'plans holidays and', 'tells jokes nobody', 'laughs at.'];
94
+ body.forEach((l, i) => put(g, 8 + i, 1, l, i < 2 ? 'w' : 'w'));
95
+ put(g, 14, 1, 'Experts urge calm', 'c');
96
+ put(g, 14, 21, '102', 'w');
97
+
98
+ // robot illustration (mosaic) on the right
99
+ mosaic(g, 25, 5, 14, 12, 'k', (c) => {
100
+ const R = (x, y, w, h, col) => { c.fillStyle = col; c.fillRect(x, y, w, h); };
101
+ // antenna
102
+ R(13, 2, 2, 5, PAL.w); c.fillStyle = PAL.r; c.beginPath(); c.arc(14, 2.2, 2.2, 0, 7); c.fill();
103
+ // head
104
+ c.fillStyle = PAL.w; c.beginPath(); c.roundRect(4, 7, 20, 16, 3); c.fill();
105
+ // ears
106
+ R(1, 11, 3, 7, PAL.m); R(24, 11, 3, 7, PAL.m);
107
+ // eyes
108
+ R(8, 11, 4, 4, PAL.c); R(16, 11, 4, 4, PAL.c);
109
+ // mouth
110
+ R(8, 18, 12, 2, PAL.r);
111
+ // neck
112
+ R(11, 23, 6, 3, PAL.c);
113
+ // body
114
+ c.fillStyle = PAL.g; c.beginPath(); c.roundRect(2, 26, 24, 10, 3); c.fill();
115
+ R(10, 28, 8, 5, PAL.y);
116
+ });
117
+
118
+ // robot eyes use the teletext Flash attribute
119
+ for (let r = 8; r <= 9; r++) for (let cc = 25; cc < 39; cc++) if (g[r][cc].mos && g[r][cc].fg === 'c') g[r][cc].flash = true;
120
+
121
+ // Index list with page numbers
122
+ const idx = [['Chip demand soars', '104'], ['Robots join union', '105'], ['Cloud forecast', '106'], ['Quiz: human or bot?', '107']];
123
+ idx.forEach(([a, n], i) => { put(g, 17 + i, 1, a, 'c'); put(g, 17 + i, 21, n, 'w'); });
124
+ // little mosaic bar chart (growth) at right, bars separated by a spacing column
125
+ const bars = [3, 5, 4, 7, 9];
126
+ const bc = ['g', 'g', 'y', 'g', 'y'];
127
+ bars.forEach((hgt, i) => {
128
+ const grow = H.clamp((t - 2.0) * 1.5 - i * 0.14, 0, 1);
129
+ const hh = Math.round(hgt * grow * 1.1);
130
+ for (let k = 0; k < 12; k++) {
131
+ const row = 20 - Math.floor(k / 3), j = 2 - (k % 3);
132
+ if (k < hh) for (let cc = 0; cc < 1; cc++) {
133
+ const cell = g[row][27 + i * 2]; cell.fg = bc[i]; cell.mos |= 3 << (j * 2); cell.ch = ' ';
134
+ }
135
+ }
136
+ });
137
+ put(g, 21, 26, 'GPU INDEX', 'w');
138
+
139
+ // Ticker band
140
+ band(g, 22, 0, 39, 'c');
141
+ if (Math.floor(t * 1.5) % 3 !== 2) put(g, 22, 1, 'LATEST', 'r', 'c');
142
+ const tick = ' Talking toaster seeks agent * Chatbot passes driving test * ';
143
+ const off = Math.floor(t * 6) % tick.length;
144
+ const vis = (tick + tick).substr(off, 31);
145
+ put(g, 22, 8, vis, 'b', 'c');
146
+
147
+ // Fastext row
148
+ put(g, 24, 0, 'Headlines', 'r'); put(g, 24, 11, 'Sport', 'g'); put(g, 24, 19, 'Weather', 'y'); put(g, 24, 29, 'AI Index', 'c');
149
+ return g;
150
+ }
151
+
152
+ // =====================================================================================
153
+ // EXPLAINER: "How does AI learn?" told as a run of teletext pages (P100 -> P104).
154
+ // Transition = real page change: header searches in green while the old page stays, then the new page
155
+ // overwrites it top-down two rows at a time.
156
+ // =====================================================================================
157
+
158
+ // =====================================================================================
159
+ // Shared rendering: SAA5050-style rounded glyphs (cached bitmaps) and the CRT presentation.
160
+ // =====================================================================================
161
+ const TTC = { key: '', glyph: new Map(), mask: null, face: null, scratch: {} };
162
+ const ttCache = (W, H_) => { const k = W + 'x' + H_; if (TTC.key !== k) { TTC.key = k; TTC.glyph = new Map(); TTC.mask = null; TTC.face = null; TTC.scratch = {}; } };
163
+ const scratch = (name, W, H_) => { let L = TTC.scratch[name]; if (!L) L = TTC.scratch[name] = H.layer(W, H_); const c = L.getContext('2d'); c.setTransform(1, 0, 0, 1, 0, 0); c.globalAlpha = 1; c.globalCompositeOperation = 'source-over'; c.filter = 'none'; c.clearRect(0, 0, W, H_); return L; };
164
+ // glyph metrics (same fit as before: the VT323 'H' fills 80% of the cell width and 74% of the row height)
165
+ const ttMetrics = (cw, rh) => {
166
+ if (TTC.met) { const m = TTC.met; if (m.cw === cw && m.rh === rh) return m; }
167
+ const c = H.layer(8, 8).getContext('2d'); c.font = '400 33px "VT323"';
168
+ const mH = c.measureText('H'), gw = mH.actualBoundingBoxRight + mH.actualBoundingBoxLeft;
169
+ const sx = (cw * 0.8) / gw;
170
+ return (TTC.met = { cw, rh, sx, gx0: cw * 0.09 + mH.actualBoundingBoxLeft * sx, sy: (rh * 0.74) / mH.actualBoundingBoxAscent });
171
+ };
172
+ // One glyph rasterised once at 2x, blurred and re-thresholded: the even, rounded strokes of the SAA5050's
173
+ // character-rounding instead of the font's lumpy pixel steps. Coloured copies are cached too.
174
+ function glyphImg(ch, fg, dh, dw, cw, rh) {
175
+ const key = ch + '|' + fg + '|' + dh + '|' + dw; let gI = TTC.glyph.get(key); if (gI) return gI;
176
+ const mkey = ch + '|' + dh + '|' + dw; let mI = TTC.glyph.get(mkey);
177
+ if (!mI) {
178
+ const { sx, sy, gx0 } = ttMetrics(cw, rh), SC = 2, wc = dw ? 2 : 1, hc = dh ? 2 : 1;
179
+ const px = Math.ceil(cw * 0.35), py = Math.ceil(rh * 0.55), bw = Math.ceil(cw * wc + px * 2), bh = Math.ceil(rh * hc + py * 2);
180
+ const A = H.layer(bw * SC, bh * SC), a = A.getContext('2d');
181
+ a.scale(SC, SC); a.translate(px + gx0 * wc, py + rh * (dh ? 1.6 : 0.8)); a.scale(sx * wc, sy * hc);
182
+ a.font = '400 33px "VT323"'; a.textBaseline = 'alphabetic'; a.fillStyle = '#fff';
183
+ a.fillText(ch, 0, 0); a.fillText(ch, 1.1 / sx, 0);
184
+ const B = H.layer(bw * SC, bh * SC), b = B.getContext('2d', { willReadFrequently: true });
185
+ b.filter = 'blur(2.4px)'; b.drawImage(A, 0, 0); b.filter = 'none';
186
+ const id = b.getImageData(0, 0, B.width, B.height), d = id.data;
187
+ for (let i = 0; i < d.length; i += 4) { const v = H.clamp((d[i + 3] / 255 - 0.27) / 0.22, 0, 1); d[i] = d[i + 1] = d[i + 2] = 255; d[i + 3] = Math.round(v * v * (3 - 2 * v) * 255); }
188
+ b.putImageData(id, 0, 0);
189
+ mI = { img: B, px, py, bw, bh }; TTC.glyph.set(mkey, mI);
190
+ }
191
+ const C = H.layer(mI.img.width, mI.img.height), cc = C.getContext('2d');
192
+ cc.drawImage(mI.img, 0, 0); cc.globalCompositeOperation = 'source-in'; cc.fillStyle = PAL[fg]; cc.fillRect(0, 0, C.width, C.height);
193
+ gI = { img: C, px: mI.px, py: mI.py, bw: mI.bw, bh: mI.bh }; TTC.glyph.set(key, gI); return gI;
194
+ }
195
+ function drawGlyph(c, cell, x, y, cw, rh) {
196
+ const gI = glyphImg(cell.ch, cell.fg, cell.dh === 1 ? 1 : 0, cell.dw ? 1 : 0, cw, rh);
197
+ c.drawImage(gI.img, x - gI.px, y - gI.py, gI.bw, gI.bh);
198
+ }
199
+ // Static screen overlays, cached per size:
200
+ // - shade: one multiply layer = phosphor shadow mask (aperture-grille RGB triads with staggered slot breaks)
201
+ // x scanlines x vignette x the curved tube's inner edge shadow and darker corners
202
+ // - glass: a faint diagonal reflection and dome highlight on the tube face
203
+ const rrPath = (f, x, y, w, h, r) => { f.moveTo(x + r, y); f.arcTo(x + w, y, x + w, y + h, r); f.arcTo(x + w, y + h, x, y + h, r); f.arcTo(x, y + h, x, y, r); f.arcTo(x, y, x + w, y, r); f.closePath(); };
204
+ const screenOverlays = (W, H_) => {
205
+ if (TTC.face) return TTC.face;
206
+ const SH = H.layer(W, H_), f = SH.getContext('2d');
207
+ f.fillStyle = '#fff'; f.fillRect(0, 0, W, H_);
208
+ const T = H.layer(6, 6), tc = T.getContext('2d');
209
+ const cols = ['#ff7a7a', '#7aff7a', '#7a7aff'];
210
+ for (let x = 0; x < 6; x++) { tc.fillStyle = cols[x % 3]; tc.fillRect(x, 0, 1, 6); }
211
+ tc.fillStyle = 'rgba(0,0,0,0.35)'; tc.fillRect(0, 2, 3, 1); tc.fillRect(3, 5, 3, 1);
212
+ f.save(); f.globalCompositeOperation = 'multiply'; f.globalAlpha = 0.22; f.fillStyle = f.createPattern(T, 'repeat'); f.fillRect(0, 0, W, H_); f.restore();
213
+ f.fillStyle = 'rgba(0,0,0,0.28)'; for (let y = 0; y < H_; y += 3) f.fillRect(0, y + 2, W, 1);
214
+ H.vignette(f, W, H_, 0.42);
215
+ f.save(); f.beginPath(); f.rect(-200, -200, W + 400, H_ + 400); rrPath(f, 10, 8, W - 20, H_ - 16, 54);
216
+ f.shadowColor = 'rgba(0,0,0,0.85)'; f.shadowBlur = 36; f.fillStyle = '#000'; f.fill('evenodd'); f.restore();
217
+ f.beginPath(); f.rect(-10, -10, W + 20, H_ + 20); rrPath(f, 3, 2, W - 6, H_ - 4, 60); f.fillStyle = '#000'; f.fill('evenodd');
218
+ const GL = H.layer(W, H_), g = GL.getContext('2d');
219
+ const gl = g.createLinearGradient(W * 0.05, 0, W * 0.42, H_ * 0.75);
220
+ gl.addColorStop(0, 'rgba(255,255,255,0)'); gl.addColorStop(0.42, 'rgba(225,235,255,0.045)'); gl.addColorStop(0.55, 'rgba(225,235,255,0.012)'); gl.addColorStop(1, 'rgba(255,255,255,0)');
221
+ g.fillStyle = gl; g.beginPath(); rrPath(g, 10, 8, W - 20, H_ - 16, 54); g.fill();
222
+ const dome = g.createRadialGradient(W * 0.3, H_ * 0.18, 0, W * 0.3, H_ * 0.18, W * 0.45);
223
+ dome.addColorStop(0, 'rgba(210,225,255,0.035)'); dome.addColorStop(1, 'rgba(210,225,255,0)');
224
+ g.fillStyle = dome; g.beginPath(); rrPath(g, 10, 8, W - 20, H_ - 16, 54); g.fill();
225
+ return (TTC.face = { shade: SH, glass: GL });
226
+ };
227
+ // CRT presentation of a rendered page layer L (shared by the still and every explainer page)
228
+ function crtPresent(ctx, W, H_, L, t) {
229
+ const MX = 30, MY = 16, PW = W - MX * 2, PH = H_ - MY * 2; // overscan margin
230
+ const OV = screenOverlays(W, H_);
231
+ // soft-focus picture
232
+ const S = scratch('S', W, H_), sc = S.getContext('2d');
233
+ sc.fillStyle = '#000'; sc.fillRect(0, 0, W, H_);
234
+ sc.filter = 'blur(0.6px)'; sc.drawImage(L, MX, MY, PW, PH); sc.filter = 'none';
235
+ // red gun lands off-convergence: lift the red channel out and put it back 1.4px to the right
236
+ // (real red/cyan colour fringes instead of the old white ghost)
237
+ const Rg = scratch('R', W, H_), rc = Rg.getContext('2d');
238
+ rc.drawImage(S, 0, 0); rc.globalCompositeOperation = 'multiply'; rc.fillStyle = '#ff0000'; rc.fillRect(0, 0, W, H_);
239
+ ctx.drawImage(S, 0, 0);
240
+ ctx.save(); ctx.globalCompositeOperation = 'difference'; ctx.drawImage(Rg, 0, 0);
241
+ ctx.globalCompositeOperation = 'lighter'; ctx.drawImage(Rg, 1.4, 0);
242
+ // tube glass: blacks sit slightly above #000
243
+ ctx.fillStyle = 'rgb(9,10,12)'; ctx.fillRect(0, 0, W, H_);
244
+ ctx.restore();
245
+ // phosphor bloom
246
+ const B = scratch('bloom', W / 4, H_ / 4), bc = B.getContext('2d');
247
+ bc.filter = 'blur(3px)'; bc.drawImage(L, MX / 4, MY / 4, PW / 4, PH / 4); bc.filter = 'none';
248
+ ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.globalAlpha = 0.5; ctx.drawImage(B, 0, 0, W, H_); ctx.restore();
249
+ // shadow mask, scanlines, vignette and the tube's edge shadow in one multiply
250
+ ctx.save(); ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(OV.shade, 0, 0); ctx.restore();
251
+ // slow CRT refresh roll
252
+ const ry = ((t * 0.35) % 1) * (H_ + 200) - 100;
253
+ const rg = ctx.createLinearGradient(0, ry - 100, 0, ry + 100);
254
+ rg.addColorStop(0, 'rgba(255,255,255,0)'); rg.addColorStop(0.5, 'rgba(255,255,255,0.035)'); rg.addColorStop(1, 'rgba(255,255,255,0)');
255
+ ctx.fillStyle = rg; ctx.fillRect(0, ry - 100, W, 200);
256
+ ctx.drawImage(OV.glass, 0, 0);
257
+ H.grain(ctx, W, H_, 0.035, 5 + Math.floor(t * 25), true);
258
+ }
259
+
260
+ function renderTT(ctx, W, H_, g, t) {
261
+ ttCache(W, H_);
262
+ const cw = W / COLS, rh = H_ / ROWS;
263
+ const X = (c) => Math.round(c * cw), Y = (r) => Math.round(r * rh);
264
+ const L = scratch('page', W, H_), c = L.getContext('2d');
265
+ c.fillStyle = '#000'; c.fillRect(0, 0, W, H_);
266
+ const flashOff = Math.floor(t * 3) % 3 === 2;
267
+ for (let r = 0; r < ROWS; r++) for (let col = 0; col < COLS; col++) {
268
+ const cell = g[r][col];
269
+ const x0 = X(col), x1 = X(col + 1), y0 = Y(r), y1 = Y(r + 1);
270
+ c.fillStyle = PAL[cell.bg]; c.fillRect(x0, y0, x1 - x0, y1 - y0);
271
+ if (cell.mos && !(cell.flash && flashOff)) {
272
+ c.fillStyle = PAL[cell.fg];
273
+ for (let j = 0; j < 3; j++) for (let i = 0; i < 2; i++) {
274
+ if (!(cell.mos & (1 << (j * 2 + i)))) continue;
275
+ const sx0 = Math.round(x0 + (i * (x1 - x0)) / 2), sx1 = Math.round(x0 + ((i + 1) * (x1 - x0)) / 2);
276
+ const sy0 = Math.round(r * rh + (j * rh) / 3), sy1 = Math.round(r * rh + ((j + 1) * rh) / 3);
277
+ c.fillRect(sx0, sy0, sx1 - sx0, sy1 - sy0);
278
+ }
279
+ }
280
+ }
281
+ for (let r = 0; r < ROWS; r++) for (let col = 0; col < COLS; col++) {
282
+ const cell = g[r][col];
283
+ if (cell.mos || cell.ch === ' ' || cell.dh === 2 || (cell.flash && flashOff)) continue;
284
+ drawGlyph(c, cell, X(col), Y(r), cw, rh);
285
+ }
286
+ crtPresent(ctx, W, H_, L, t);
287
+ }
288
+
289
+ // double-size (Level 2.5 style): each character spans 2 columns x 2 rows
290
+ function put2(g, row, col, str, fg = 'w', bg = null) {
291
+ for (let i = 0; i < str.length && col + i * 2 + 1 < COLS; i++) {
292
+ const c0 = col + i * 2;
293
+ for (const [r, c, k] of [[row, c0, 1], [row, c0 + 1, 2], [row + 1, c0, 2], [row + 1, c0 + 1, 2]]) {
294
+ const cell = g[r][c]; cell.mos = 0; if (bg) cell.bg = bg;
295
+ if (k === 1) { cell.ch = str[i]; cell.fg = fg; cell.dh = 1; cell.dw = 1; } else { cell.ch = ' '; cell.dh = 2; cell.dw = 0; }
296
+ }
297
+ }
298
+ }
299
+ const putC = (g, row, str, fg, bg, dh) => put(g, row, Math.max(0, Math.floor((COLS - str.length) / 2)), str, fg, bg, dh);
300
+ const flashCells = (g, r0, r1, c0, c1) => { for (let r = r0; r <= r1; r++) for (let c = c0; c <= c1; c++) g[r][c].flash = true; };
301
+ const fmtN = (n) => Math.round(n).toLocaleString('en-US');
302
+ // mosaic pictures (drawn in sub-pixel units: 2 per cell across, 3 per cell down)
303
+ const catPic = (col) => (c, w, h) => {
304
+ c.fillStyle = PAL[col];
305
+ c.beginPath(); c.moveTo(w * 0.14, h * 0.05); c.lineTo(w * 0.42, h * 0.32); c.lineTo(w * 0.1, h * 0.45); c.closePath(); c.fill();
306
+ c.beginPath(); c.moveTo(w * 0.86, h * 0.05); c.lineTo(w * 0.58, h * 0.32); c.lineTo(w * 0.9, h * 0.45); c.closePath(); c.fill();
307
+ c.beginPath(); c.ellipse(w * 0.5, h * 0.58, w * 0.42, h * 0.4, 0, 0, 7); c.fill();
308
+ c.fillStyle = '#000'; c.fillRect(w * 0.27, h * 0.48, w * 0.13, h * 0.14); c.fillRect(w * 0.6, h * 0.48, w * 0.13, h * 0.14);
309
+ c.fillStyle = PAL.r; c.fillRect(w * 0.43, h * 0.68, w * 0.14, h * 0.1);
310
+ };
311
+ const dogPic = (col) => (c, w, h) => {
312
+ c.fillStyle = PAL[col];
313
+ c.beginPath(); c.ellipse(w * 0.5, h * 0.5, w * 0.32, h * 0.44, 0, 0, 7); c.fill();
314
+ c.beginPath(); c.ellipse(w * 0.14, h * 0.46, w * 0.12, h * 0.34, 0.25, 0, 7); c.fill();
315
+ c.beginPath(); c.ellipse(w * 0.86, h * 0.46, w * 0.12, h * 0.34, -0.25, 0, 7); c.fill();
316
+ c.fillStyle = '#000'; c.fillRect(w * 0.33, h * 0.36, w * 0.11, h * 0.13); c.fillRect(w * 0.56, h * 0.36, w * 0.11, h * 0.13);
317
+ c.fillRect(w * 0.42, h * 0.62, w * 0.16, h * 0.13);
318
+ };
319
+ const robotPic = (c) => {
320
+ const R = (x, y, w, h, col) => { c.fillStyle = col; c.fillRect(x, y, w, h); };
321
+ R(11, 0, 2, 4, PAL.w); c.fillStyle = PAL.r; c.beginPath(); c.arc(12, 1.2, 1.8, 0, 7); c.fill();
322
+ c.fillStyle = PAL.w; c.beginPath(); c.roundRect(3, 4, 18, 14, 3); c.fill();
323
+ R(0, 8, 3, 6, PAL.m); R(21, 8, 3, 6, PAL.m);
324
+ R(6, 7, 4, 4, PAL.c); R(14, 7, 4, 4, PAL.c);
325
+ R(7, 13, 10, 2, PAL.r);
326
+ };
327
+ const arrowPic = (col, len, dir = 1) => (c, w, h) => { // horizontal arrow; len 0..1 draws on from the tail
328
+ c.fillStyle = PAL[col];
329
+ const L = w * len;
330
+ if (dir > 0) { c.fillRect(0, h * 0.34, Math.max(0, L - h * 0.6), h * 0.32); if (len > 0.6) { c.beginPath(); c.moveTo(L - h * 0.9, 0); c.lineTo(L, h / 2); c.lineTo(L - h * 0.9, h); c.closePath(); c.fill(); } }
331
+ else { c.fillRect(w - L + h * 0.6, h * 0.34, Math.max(0, L - h * 0.6), h * 0.32); if (len > 0.6) { c.beginPath(); c.moveTo(w - L + h * 0.9, 0); c.lineTo(w - L, h / 2); c.lineTo(w - L + h * 0.9, h); c.closePath(); c.fill(); } }
332
+ };
333
+ const tickPic = (c, w, h) => { c.strokeStyle = PAL.g; c.lineWidth = 2.4; c.lineCap = 'square'; c.beginPath(); c.moveTo(w * 0.1, h * 0.55); c.lineTo(w * 0.4, h * 0.85); c.lineTo(w * 0.9, h * 0.12); c.stroke(); };
334
+ const crossPic = (c, w, h) => { c.strokeStyle = PAL.r; c.lineWidth = 2.6; c.beginPath(); c.moveTo(w * 0.12, h * 0.1); c.lineTo(w * 0.88, h * 0.9); c.moveTo(w * 0.88, h * 0.1); c.lineTo(w * 0.12, h * 0.9); c.stroke(); };
335
+
336
+ function exHeader(g, num, t, searching) {
337
+ const sec = (12 + Math.floor(t)) % 60;
338
+ put(g, 0, 0, 'P' + num, 'w');
339
+ const roll = searching ? (Math.floor(t * 24) * 37 % 899 + 100).toString(16).toUpperCase().slice(-3) : num;
340
+ put(g, 0, 6, 'AITEXT ' + roll, searching ? 'g' : 'w');
341
+ put(g, 0, 18, 'Thu 09 Oct', 'w');
342
+ put(g, 0, 31, `10:24/${String(sec).padStart(2, '0')}`, 'y');
343
+ }
344
+ function stepBand(g, step) {
345
+ band(g, 1, 0, 39, 'b');
346
+ put(g, 1, 1, 'HOW DOES AI LEARN?', 'y', 'b');
347
+ put(g, 1, 30, 'STEP ' + step + '/4', 'c', 'b');
348
+ }
349
+
350
+ // ---- pages (tl = time since this page was requested) ----
351
+ function page100(g, tl) {
352
+ for (let r = 1; r <= 3; r++) band(g, r, 0, 39, 'b');
353
+ put(g, 2, 2, 'AITEXT SCIENCE EXPLAINED', 'w', 'b');
354
+ put(g, 6, 3, 'HOW DOES', 'y', null, 1);
355
+ put(g, 9, 3, 'AI LEARN?', 'y', null, 1);
356
+ put(g, 13, 3, 'A guide in four', 'c');
357
+ put(g, 14, 3, 'short pages.', 'c');
358
+ mosaic(g, 26, 5, 12, 12, 'k', (c, w, h) => {
359
+ c.fillStyle = PAL.m; c.lineWidth = 6; c.strokeStyle = PAL.m; c.lineCap = 'butt';
360
+ c.beginPath(); c.arc(w / 2, h * 0.3, w * 0.28, Math.PI * 1.05, Math.PI * 2.25); c.stroke();
361
+ c.fillRect(w / 2 - 3, h * 0.42, 6, h * 0.22); c.fillRect(w / 2 - 3, h * 0.78, 6, h * 0.14);
362
+ });
363
+ flashCells(g, 15, 16, 26, 37);
364
+ put(g, 20, 3, 'Page through: 101 102 103 104', 'w');
365
+ put(g, 24, 0, 'Next page', 'r'); put(g, 24, 13, 'Index', 'g'); put(g, 24, 22, 'Quiz', 'y'); put(g, 24, 31, 'Science', 'c');
366
+ }
367
+ function page101(g, tl) {
368
+ stepBand(g, 1);
369
+ put(g, 3, 1, 'TRAINING DATA', 'c', null, 1);
370
+ put(g, 3, 24, 'LABELLED', 'w', null, 1);
371
+ // four labelled examples appear one by one
372
+ const ex = [[1, 6, 'cat', 'y'], [9, 6, 'dog', 'w'], [1, 13, 'dog', 'w'], [9, 13, 'cat', 'y']];
373
+ ex.forEach(([c0, r0, lab, col], i) => {
374
+ if (tl < 0.25 + i * 0.22) return;
375
+ mosaic(g, c0, r0, 6, 5, 'k', (lab === 'cat' ? catPic : dogPic)(col));
376
+ put(g, r0 + 5, c0 + 1, ' ' + lab + ' ', 'b', lab === 'cat' ? 'y' : 'c'); // label tag under its picture
377
+ });
378
+ // arrow draws on, then examples stream along it into the model
379
+ const al = H.clamp((tl - 1.0) / 0.5);
380
+ if (al > 0) mosaic(g, 16, 10, 10, 3, 'k', arrowPic('w', Math.ceil(al * 10) / 10));
381
+ put(g, 9, 16, 'feed in', 'w');
382
+ mosaic(g, 27, 6, 12, 7, 'k', robotPic);
383
+ put(g, 13, 29, 'THE MODEL', 'g');
384
+ if (tl > 1.5) {
385
+ const ph = ((tl - 1.5) / 0.45) % 1, k = Math.floor((tl - 1.5) / 0.45);
386
+ const col = 16 + Math.floor(ph * 8);
387
+ mosaic(g, col, 14, 3, 2, 'k', (k % 2 ? dogPic : catPic)(k % 2 ? 'w' : 'y'));
388
+ put(g, 16, col, k % 2 ? 'dog' : 'cat', k % 2 ? 'w' : 'y');
389
+ }
390
+ // counter
391
+ const n = 1e6 * H.ease.inOut(H.clamp((tl - 1.4) / 2.0));
392
+ if (tl > 1.3) { put2(g, 20, 0, fmtN(n).padStart(9, ' '), 'y'); put(g, 20, 19, ' EXAMPLES', 'w', null, 1); }
393
+ if (tl > 1.3) put(g, 23, 1, 'Every picture comes with its answer.', 'c');
394
+ put(g, 24, 0, 'Back', 'r'); put(g, 24, 13, 'Index', 'g'); put(g, 24, 22, 'Next', 'y'); put(g, 24, 31, 'Science', 'c');
395
+ }
396
+ function page102(g, tl) {
397
+ stepBand(g, 2);
398
+ // the loop strip at the bottom: GUESS -> CHECK -> ADJUST, return arrow
399
+ const step = tl < 0.5 ? -1 : tl < 1.7 ? 0 : tl < 2.5 ? 1 : tl < 3.6 ? 2 : tl < 4.1 ? 0 : 1;
400
+ const boxes = [['GUESS', 'y', 2], ['CHECK', 'c', 15], ['ADJUST', 'm', 28]];
401
+ boxes.forEach(([lab, col, c0], i) => {
402
+ const act = i === step;
403
+ band(g, 20, c0, c0 + 9, act ? col : 'k'); band(g, 21, c0, c0 + 9, act ? col : 'k');
404
+ put(g, 20, c0 + 1, lab, act ? 'b' : col, act ? col : 'k', 1);
405
+ });
406
+ put(g, 20, 12, '>', 'w', null, 1); put(g, 20, 25, '>', 'w', null, 1);
407
+ mosaic(g, 3, 22, 32, 1, 'k', (c, w, h) => { c.fillStyle = PAL.w; c.fillRect(1, 0, 2, 2); c.fillRect(1, 1, w - 4, 1.2); c.fillRect(w - 3, 0, 2, 2); c.beginPath(); c.moveTo(0, 0); c.lineTo(4, 0); c.lineTo(2, -2); c.fill(); });
408
+ put(g, 23, 12, 'repeat, repeat, repeat', 'w');
409
+ // new picture -> model
410
+ mosaic(g, 1, 4, 8, 6, 'k', catPic('y'));
411
+ put(g, 10, 1, 'NEW PIC', 'c');
412
+ const a1 = H.clamp((tl - 0.3) / 0.4);
413
+ if (a1 > 0) mosaic(g, 9, 6, 5, 2, 'k', arrowPic('w', a1));
414
+ mosaic(g, 14, 4, 12, 7, 'k', robotPic);
415
+ // weights: three mosaic sliders under the robot whose knobs step when adjusting
416
+ const adj = H.clamp((tl - 2.8) / 0.7);
417
+ const knobs = [[0.2, 0.75], [0.7, 0.3], [0.45, 0.85]];
418
+ put(g, 11, 14, 'weights', 'm');
419
+ knobs.forEach(([a, b], i) => {
420
+ const v = H.lerp(a, b, Math.floor(adj * 5) / 5);
421
+ mosaic(g, 14, 12 + i, 12, 1, 'k', (c, w, h) => { c.fillStyle = PAL.b; c.fillRect(0, 1, w, 1); c.fillStyle = adj > 0 && adj < 1 ? PAL.y : PAL.m; c.fillRect(Math.round(v * (w - 3)), 0, 3, 3); });
422
+ });
423
+ // guess area
424
+ const a2 = H.clamp((tl - 0.9) / 0.3);
425
+ if (a2 > 0) mosaic(g, 26, 6, 3, 2, 'k', arrowPic('w', a2));
426
+ if (tl > 1.1 && tl < 3.6) {
427
+ put(g, 4, 30, 'DOG?', 'r', null, 1);
428
+ if (tl > 1.7) { mosaic(g, 35, 4, 4, 3, 'k', crossPic); flashCells(g, 4, 6, 35, 38); band(g, 4, 29, 34, 'w'); band(g, 5, 29, 34, 'w'); }
429
+ if (tl > 1.9) { put(g, 8, 29, 'NO - IT IS', 'c'); put(g, 9, 29, 'A CAT', 'c'); }
430
+ }
431
+ if (tl > 2.5 && tl < 3.6) {
432
+ // error signal travels back from the guess to the weights (right to left), a block per step
433
+ const e = H.clamp((tl - 2.5) / 0.5);
434
+ mosaic(g, 26, 14, 13, 2, 'k', arrowPic('r', Math.ceil(e * 8) / 8, -1));
435
+ put(g, 16, 30, 'error', 'r');
436
+ }
437
+ if (tl >= 3.6) {
438
+ put(g, 4, 30, 'CAT', 'g', null, 1);
439
+ mosaic(g, 35, 4, 4, 3, 'k', tickPic);
440
+ put(g, 8, 29, 'CORRECT!', 'g');
441
+ }
442
+ }
443
+ function page103(g, tl) {
444
+ stepBand(g, 3);
445
+ put(g, 3, 1, 'ACCURACY', 'w', null, 1);
446
+ const pc = Math.round(H.lerp(61, 97, H.ease.inOut(H.clamp((tl - 0.9) / 1.2))));
447
+ const done = tl > 2.15;
448
+ put2(g, 3, 11, ' ' + pc + '% ', done ? 'b' : 'y', done ? 'y' : null);
449
+ if (done) flashCells(g, 3, 4, 11, 20);
450
+ put(g, 3, 23, 'after', 'c'); put(g, 4, 23, 'each round', 'c');
451
+ // bar chart: one bar per training round, stepped in
452
+ const nb = Math.min(12, Math.floor(H.clamp((tl - 0.85) / 1.3) * 12) + (tl > 0.85 ? 1 : 0));
453
+ for (let i = 0; i < nb; i++) {
454
+ const v = H.lerp(61, 97, 1 - Math.pow(1 - i / 11, 2));
455
+ const hh = Math.round(((v - 50) / 50) * 36); // sub-rows (12 rows * 3)
456
+ for (let k = 0; k < hh; k++) {
457
+ const row = 18 - Math.floor(k / 3), j = 2 - (k % 3);
458
+ const cell = g[row][3 + i * 2]; cell.fg = i === nb - 1 ? 'y' : 'g'; cell.mos |= 3 << (j * 2); cell.ch = ' ';
459
+ }
460
+ }
461
+ mosaic(g, 2, 19, 25, 1, 'k', (c, w) => { c.fillStyle = PAL.w; c.fillRect(0, 0, w, 1.2); });
462
+ put(g, 20, 3, 'TRAINING ROUNDS >>>', 'c');
463
+ if (tl > 0.9) { put(g, 16, 28, '61% at', 'w'); put(g, 17, 28, 'round 1', 'w'); }
464
+ if (done) { put(g, 12, 28, '97% at', 'y'); put(g, 13, 28, 'round 12', 'y'); }
465
+ }
466
+ function page104(g, tl) {
467
+ stepBand(g, 4);
468
+ putC(g, 5, 'THE TAKEAWAY', 'c');
469
+ put(g, 8, 3, 'MORE EXAMPLES', 'w', null, 1);
470
+ mosaic(g, 18, 10, 6, 3, 'k', (c, w, h) => { c.fillStyle = PAL.y; c.fillRect(w * 0.42, 0, w * 0.16, h * 0.55); c.beginPath(); c.moveTo(0, h * 0.45); c.lineTo(w, h * 0.45); c.lineTo(w / 2, h); c.fill(); });
471
+ put(g, 14, 13, 'BETTER GUESSES', 'y', null, 1);
472
+ // rising staircase of blocks
473
+ for (let i = 0; i < 6; i++) { if (tl < 0.4 + i * 0.08) continue; for (let k = 0; k <= i; k++) for (let cc = 0; cc < 4; cc++) { const cell = g[22 - k][3 + i * 6 + cc]; cell.mos = 63; cell.fg = i === 5 ? 'y' : 'g'; } }
474
+ put(g, 24, 0, 'Index', 'r'); put(g, 24, 13, 'Quiz', 'g'); put(g, 24, 22, 'Again', 'y'); put(g, 24, 31, 'Science', 'c');
475
+ }
476
+ const EX_PAGES = [[0, '100', page100], [1.85, '101', page101], [5.55, '102', page102], [10.3, '103', page103], [12.75, '104', page104]];
477
+ function buildEx(i, t) {
478
+ const g = newGrid();
479
+ const [t0, num, fn] = EX_PAGES[i];
480
+ fn(g, t - t0);
481
+ exHeader(g, num, t, false);
482
+ return g;
483
+ }
484
+
485
+ // ---- SOUND: a TV set showing teletext. Mains hum + room, a soft library-music pad (like the pages-from-teletext
486
+ // filler music), a remote-control button press on every page request, a quiet seek chatter while the green header
487
+ // counter rolls, a "found" bleep, then a fast stepped data wipe as rows land two at a time. Everything on the page is
488
+ // a soft lowpassed square bleep (a small TV speaker). Times copy EX_PAGES / SEARCH = 0.35 / 22 row-pairs per second.
489
+ function ttSound(k) {
490
+ const END = 14.8, SEARCH = 0.35;
491
+ const P = EX_PAGES.map((p) => p[0]); // [0, 1.85, 5.55, 10.3, 12.75]
492
+ const bleep = (t, f, d = 0.07, g = 1, pan = 0, minor = false) => k.tone(t, { f, type: 'square', dur: d, gain: 0.075 * g, a: 0.002, hold: d * 0.6, filter: 'lowpass', f2: 1800, pan, minor });
493
+ const remote = (t) => { k.noise(t, { dur: 0.02, filter: 'bandpass', f: 1400, q: 1.5, gain: 0.32, a: 0.001 }); k.tone(t, { f: [[0, 190], [0.03, 120]], dur: 0.045, gain: 0.18 }); };
494
+ // row-pair wipe: one soft data click per two rows landing, top to bottom (pitch steps down the page)
495
+ const wipe = (t0) => { for (let i = 0; i < 12; i++) k.noise(t0 + i / 22, { dur: 0.03, filter: 'bandpass', f: 2000 - i * 90, q: 2.5, gain: 0.16 * (1 - i / 20), a: 0.002 }); k.noise(t0, { dur: 0.56, color: 'pink', filter: 'bandpass', f: [[0, 1600], [0.55, 700]], q: 1, gain: 0.035, a: 0.05, hold: 0.4 }); };
496
+ const page = (t0, first) => {
497
+ remote(t0);
498
+ for (let i = 0; i < 4; i++) bleep(t0 + 0.04 + i * 0.075, [330, 294, 370, 262][i], 0.03, 0.55, -0.3, true); // header counter seeks
499
+ bleep(t0 + SEARCH, first ? 'A5' : 'E5', 0.09, 1.3); // page found
500
+ wipe(t0 + SEARCH);
501
+ };
502
+ const counter = (t0, t1, fn, step, play) => { let last = -1, lastV = fn(t0); 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, v); last = t; lastV = v; } } };
503
+
504
+ // ---- bed + music
505
+ k.bed('hum', 0, END, { gain: 0.4 });
506
+ k.bed('room', 0, END, { gain: 0.3 });
507
+ k.music('pad', 0.4, END, { bpm: 96, gain: 0.32, chords: [['C4', 'E4', 'G4', 'B4'], ['A3', 'C4', 'E4', 'G4'], ['D4', 'F4', 'A4', 'C5'], ['G3', 'B3', 'D4', 'F4']] });
508
+
509
+ // ---- P100: title page
510
+ page(P[0], true);
511
+ // ---- P101: training data
512
+ page(P[1]);
513
+ const L1 = P[1];
514
+ bleep(L1 + 0.69, 'C5', 0.06, 0.8, -0.5, true); bleep(L1 + 0.91, 'E5', 0.06, 0.8, -0.3, true); // last two example pictures appear
515
+ for (let s = 2; s <= 10; s += 2) bleep(L1 + 1.0 + (s / 10) * 0.5 - 0.05, 262 + s * 25, 0.04, 0.6, 0, true); // arrow steps on (10 steps / 0.5 s)
516
+ for (let t = L1 + 1.5; t < P[2] - 0.05; t += 0.45) bleep(t, 'G4', 0.05, 0.6, 0.2, true); // a picture streams out along the arrow
517
+ counter(L1 + 1.4, L1 + 3.4, (t) => H.ease.inOut(H.clamp((t - L1 - 1.4) / 2.0)), 1 / 30, (t) => bleep(t, 220, 0.025, 0.7, -0.4, true)); // 0 -> 1,000,000
518
+ bleep(L1 + 3.4, 'C5', 0.06, 1, -0.4); bleep(L1 + 3.46, 'G5', 0.12, 1, -0.4); // counter lands on 1,000,000
519
+
520
+ // ---- P102: guess / check / adjust
521
+ page(P[2]);
522
+ const L2 = P[2];
523
+ bleep(L2 + 1.1, 'A4', 0.08, 1, 0.5); bleep(L2 + 1.18, 'D5', 0.12, 1, 0.5); // DOG? (rising, a question)
524
+ // CHECK (tl 1.7): wrong. cross + flash + white band: harsh low two-note buzz
525
+ const WRONG = L2 + 1.7;
526
+ k.tone(WRONG, { f: 'Bb2', type: 'square', dur: 0.16, gain: 0.09, hold: 0.12, filter: 'lowpass', f2: 1300, pan: 0.5 });
527
+ k.tone(WRONG + 0.17, { f: 'E2', type: 'square', dur: 0.32, gain: 0.1, hold: 0.24, filter: 'lowpass', f2: 1100, pan: 0.5 });
528
+ k.play('thud', WRONG, { f: 100, gain: 0.45, pan: 0.5 });
529
+ bleep(L2 + 1.9, 'C5', 0.04, 0.6, 0.5, true); // "NO - IT IS A CAT"
530
+ // ADJUST (tl 2.5): error arrow steps back right -> left (8 steps over 0.5 s, one bleep per two)
531
+ for (let s = 1; s <= 8; s += 2) bleep(L2 + 2.5 + (s / 8) * 0.5 - 0.06, 523 - s * 35, 0.05, 0.9, 0.6 - s * 0.12);
532
+ for (let s = 1; s <= 5; s++) bleep(L2 + 2.8 + (s / 5) * 0.7 - 0.14, [196, 220, 247, 262, 294][s - 1], 0.05, 0.8, -0.1, true); // weight knobs step (5 levels)
533
+ // GUESS again -> CAT / CORRECT (tl 3.6)
534
+ ['C5', 'E5', 'G5'].forEach((n, i) => bleep(L2 + 3.6 + i * 0.07, n, i === 2 ? 0.25 : 0.07, 1.1, 0.5));
535
+ bleep(L2 + 4.1, 'E4', 0.04, 0.6, 0, true); // CHECK band moves on
536
+
537
+ // ---- P103: accuracy bars step in (one bar per 1.3/12 s from tl 0.85), then 97% flashes (tl 2.15)
538
+ page(P[3]);
539
+ const L3 = P[3];
540
+ for (let i = 1; i < 12; i++) bleep(L3 + 0.85 + (i * 1.3) / 12, 262 * Math.pow(2, (i * 12 / 11) / 12), 0.04, 0.75, -0.5 + i * 0.05, true);
541
+ const BEST = L3 + 2.15 + 1e-3;
542
+ k.play('impact', BEST, { f: 110, gain: 0.8 });
543
+ ['C4', 'E4', 'G4', 'C5'].forEach((n, i) => { bleep(BEST + i * 0.06, n, 0.09, 1.3, -0.2); });
544
+ ['C5', 'E5', 'G5'].forEach((n) => k.tone(BEST + 0.24, { f: n, type: 'square', dur: 0.8, gain: 0.045, a: 0.003, hold: 0.45, filter: 'lowpass', f2: 2000, verb: 0.2 }));
545
+ k.tone(BEST + 0.24, { f: 'C3', type: 'triangle', dur: 0.9, gain: 0.2, hold: 0.4 });
546
+ bleep(13.0, 'G4', 0.05, 0.5, 0, true); // 97% flashes back on (Flash attribute) just as P104 is being searched
547
+
548
+ // ---- P104: takeaway. BETTER GUESSES lands as the wipe passes row 14 (tl 0.67), staircase completes at tl 0.9
549
+ page(P[4]);
550
+ const L4 = P[4];
551
+ const TAKE = L4 + 0.35 + 7 / 22;
552
+ bleep(TAKE, 'B4', 0.08, 0.8); bleep(TAKE + 0.09, 'C5', 0.5, 0.8); bleep(TAKE + 0.09, 'G4', 0.5, 0.5);
553
+ k.tone(TAKE + 0.09, { f: 'C3', type: 'triangle', dur: 1.2, gain: 0.14, hold: 0.3 });
554
+ // (the staircase of blocks is revealed by the wipe itself, rows 17-22 at tl 0.76-0.85, so the wipe clicks cover it)
555
+ }
556
+
557
+ registerStyle({
558
+ id: 'teletext',
559
+ name: 'Teletext Page',
560
+ category: 'Retro UI & Neon',
561
+ description: 'A broadcast teletext page: strict 40x25 character grid, eight saturated colours, 2x3 sextant mosaic graphics with one colour per cell, double-height headlines and Fastext links, seen through a CRT.',
562
+ motion: 'Only the header shows while the page counter rolls in green, then the page lands top-down two rows at a time; mosaic bars step up a sub-block at a time, LATEST and the robot eyes flash, the ticker hops whole characters and the clock ticks.',
563
+ fonts: ['400 33px "VT323"', '900 11.2px "Archivo Black"'],
564
+ frameT: 4.8,
565
+ duration: 5,
566
+ readsPixels: true,
567
+ explainerDuration: 14.8,
568
+ explainerNote: 'Each beat is a new teletext page (P100-P104): the header searches in green while the old page lingers, then the new page overwrites it two rows at a time; arrows, pictures and bar charts are sextant mosaics, numbers count in double-height digits and the key moments use the Flash attribute.',
569
+ explainer(ctx, W, H_, t) {
570
+ let i = 0; for (let k = 0; k < EX_PAGES.length; k++) if (t >= EX_PAGES[k][0]) i = k;
571
+ const tl = t - EX_PAGES[i][0], SEARCH = 0.35;
572
+ const gn = buildEx(i, t), go = i > 0 ? buildEx(i - 1, t) : newGrid();
573
+ let g;
574
+ if (tl < SEARCH) { g = go; exHeader(g, EX_PAGES[i][1], t, true); }
575
+ else {
576
+ const rows = 2 * Math.floor((tl - SEARCH) * 22) + 2;
577
+ g = gn.map((row, r) => (r === 0 || r < rows ? row : go[r]));
578
+ }
579
+ renderTT(ctx, W, H_, g, t);
580
+ },
581
+ explainerSound(k) { ttSound(k); },
582
+ draw(ctx, W, H_, t) {
583
+ const g = buildPage(t);
584
+ const cw = W / COLS, rh = H_ / ROWS;
585
+ const X = (c) => Math.round(c * cw), Y = (r) => Math.round(r * rh);
586
+ ttCache(W, H_);
587
+ const L = scratch('page', W, H_), c = L.getContext('2d');
588
+ c.fillStyle = '#000'; c.fillRect(0, 0, W, H_);
589
+ // reveal rows progressively at the start (page "arriving")
590
+ const rowsShown = t < 0.9 ? 1 : Math.min(ROWS, 2 * Math.floor((t - 0.9) * 22) + 2); // rows land two per field
591
+ for (let r = 0; r < ROWS; r++) {
592
+ if (r >= rowsShown && r !== 0) continue;
593
+ for (let col = 0; col < COLS; col++) {
594
+ const cell = g[r][col];
595
+ const x0 = X(col), x1 = X(col + 1), y0 = Y(r), y1 = Y(r + 1);
596
+ c.fillStyle = PAL[cell.bg]; c.fillRect(x0, y0, x1 - x0, y1 - y0);
597
+ if (cell.mos && !(cell.flash && Math.floor(t * 1.5) % 3 === 2)) {
598
+ c.fillStyle = PAL[cell.fg];
599
+ for (let j = 0; j < 3; j++) for (let i = 0; i < 2; i++) {
600
+ if (!(cell.mos & (1 << (j * 2 + i)))) continue;
601
+ const sx0 = Math.round(x0 + (i * (x1 - x0)) / 2), sx1 = Math.round(x0 + ((i + 1) * (x1 - x0)) / 2);
602
+ const sy0 = Math.round(r * rh + (j * rh) / 3), sy1 = Math.round(r * rh + ((j + 1) * rh) / 3);
603
+ c.fillRect(sx0, sy0, sx1 - sx0, sy1 - sy0);
604
+ }
605
+ }
606
+ }
607
+ }
608
+ // text pass (after backgrounds so double-height glyphs spill into the row below)
609
+ for (let r = 0; r < ROWS; r++) {
610
+ if (r >= rowsShown && r !== 0) continue;
611
+ for (let col = 0; col < COLS; col++) {
612
+ const cell = g[r][col];
613
+ if (cell.mos || cell.ch === ' ' || cell.dh === 2) continue;
614
+ drawGlyph(c, cell, X(col), Y(r), cw, rh); // cached rounded glyph (hero cells never use double width)
615
+ }
616
+ }
617
+
618
+ // ---- CRT presentation: soft focus, gun misconvergence, shadow mask, bloom, scanlines, curved glass ----
619
+ crtPresent(ctx, W, H_, L, t);
620
+ },
621
+ });
622
+ }