@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,951 @@
1
+ {
2
+ // =====================================================================================
3
+ // Cosmic Star Chart: deep-space illustration (nebulae, lit planets, comets, spiky stars)
4
+ // crossed with an engraved celestial atlas overlay (hairline gold / pale-cyan chart work).
5
+ // =====================================================================================
6
+ const TAU = Math.PI * 2;
7
+ const GOLD = [230, 198, 128], GOLDB = [255, 226, 168], CYAN = [160, 226, 236], RED = [255, 86, 74];
8
+ const CREAM = [248, 240, 224], WHITE = [255, 255, 255];
9
+ const rgba = (c, a) => `rgba(${c[0] | 0},${c[1] | 0},${c[2] | 0},${a})`;
10
+ const CORM = '"Cormorant Garamond"', SYNE = '"Syne"', BASK = '"Libre Baskerville"';
11
+ const LV = (() => { const v = [0.74, -0.46, 0.48], n = Math.hypot(v[0], v[1], v[2]); return v.map((a) => a / n); })();
12
+ const C = {}; // cached textures (built once, output still depends only on t)
13
+ const wrap = (v, m) => ((v % m) + m) % m;
14
+ const sm = (a, b, x) => H.smooth(a, b, x);
15
+ const bump = (a, b, c, d, x) => sm(a, b, x) * (1 - sm(c, d, x));
16
+
17
+ // ---------------------------------------------------------------- glow sprites
18
+ function glow(c) {
19
+ const key = 'g' + c.join(',');
20
+ if (C[key]) return C[key];
21
+ const cv = H.layer(128, 128), x = cv.getContext('2d'), g = x.createRadialGradient(64, 64, 0, 64, 64, 64);
22
+ g.addColorStop(0, rgba(c, 1)); g.addColorStop(0.07, rgba(c, 0.85)); g.addColorStop(0.2, rgba(c, 0.28));
23
+ g.addColorStop(0.45, rgba(c, 0.07)); g.addColorStop(1, rgba(c, 0));
24
+ x.fillStyle = g; x.fillRect(0, 0, 128, 128);
25
+ return (C[key] = cv);
26
+ }
27
+
28
+ // ---------------------------------------------------------------- nebula (layered fbm, cached)
29
+ // 640x450 texture standing for a 1920x1350 sky (drawn 3x up: nebulae are soft anyway)
30
+ function nebula() {
31
+ if (C.neb) return C.neb;
32
+ const w = 640, h = 450, cv = H.layer(w, h), x = cv.getContext('2d'), img = x.createImageData(w, h), d = img.data;
33
+ const nz = H.makeNoise(7713);
34
+ const P = { teal: [20, 150, 165], violet: [98, 50, 178], rose: [222, 70, 124], gold: [242, 182, 96] };
35
+ for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) {
36
+ const bx = i / w, by = j / h, u = bx * 3.2, v = by * 2.25;
37
+ const qx = nz.fbm3(u, v, 0.1, 3), qy = nz.fbm3(u + 5.2, v + 1.3, 0.1, 3);
38
+ const n = nz.fbm3(u + 1.5 * qx, v + 1.5 * qy, 1.7, 5) * 0.5 + 0.5;
39
+ const n2 = nz.fbm3(u * 1.25 + 3 + 1.3 * qy, v * 1.25 - 2 + 1.3 * qx, 4.4, 5) * 0.5 + 0.5;
40
+ // the nebula lives in a winding diagonal river across the sky
41
+ const dist = by - (0.84 - 0.62 * bx) - 0.07 * Math.sin(bx * 6.5 + 1);
42
+ const band = Math.exp(-(dist * dist) / (2 * 0.17 * 0.17));
43
+ const m = 0.12 + 0.88 * band;
44
+ const A = sm(0.44, 0.8, n) * m, B = sm(0.47, 0.82, n2) * m * (0.4 + 0.6 * sm(0.2, 0.7, bx + 0.2 * qx));
45
+ const hueA = H.clamp(nz.fbm3(u * 0.5 + 9, v * 0.5, 3.3, 3) * 1.2 + 0.5 + (bx - 0.45) * 0.8);
46
+ let r = P.violet[0] + (P.rose[0] - P.violet[0]) * hueA, g = P.violet[1] + (P.rose[1] - P.violet[1]) * hueA, b = P.violet[2] + (P.rose[2] - P.violet[2]) * hueA;
47
+ const inner = 0.55 + 0.75 * sm(0.5, 0.9, n); // brighter where the gas is thickest: reads as lit from within
48
+ let R_ = r * A * inner, G_ = g * A * inner, B_ = b * A * inner;
49
+ R_ += P.teal[0] * B * 0.95; G_ += P.teal[1] * B * 0.95; B_ += P.teal[2] * B * 0.95;
50
+ const gold = sm(0.62, 0.88, n) * sm(0.35, 0.75, hueA) * band;
51
+ R_ += P.gold[0] * gold * 0.9; G_ += P.gold[1] * gold * 0.9; B_ += P.gold[2] * gold * 0.9;
52
+ R_ *= 1.3; G_ *= 1.3; B_ *= 1.3;
53
+ // soft diffuse glow of the whole river
54
+ R_ += 40 * band * 0.35; G_ += 22 * band * 0.35; B_ += 70 * band * 0.35;
55
+ // dark dust lanes cut through the glow
56
+ const dl = nz.fbm3(u * 2.1 + 4 + qx, v * 2.1 + qy, 5.1, 5) * 0.5 + 0.5;
57
+ const dust = sm(0.53, 0.7, dl) * (0.25 + 0.75 * band) * 0.85;
58
+ R_ *= 1 - dust; G_ *= 1 - dust; B_ *= 1 - dust;
59
+ const k = (j * w + i) * 4;
60
+ const br = 3 + 6 * by, bg = 4 + 4 * by, bb = 11 + 14 * (1 - Math.abs(by - 0.5));
61
+ d[k] = Math.min(255, br + R_); d[k + 1] = Math.min(255, bg + G_); d[k + 2] = Math.min(255, bb + B_); d[k + 3] = 255;
62
+ }
63
+ x.putImageData(img, 0, 0);
64
+ return (C.neb = cv);
65
+ }
66
+
67
+ // ---------------------------------------------------------------- distant galaxy smear
68
+ function galaxy() {
69
+ if (C.gal) return C.gal;
70
+ const w = 300, h = 300, cv = H.layer(w, h), x = cv.getContext('2d'), img = x.createImageData(w, h), d = img.data, nz = H.makeNoise(91);
71
+ for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) {
72
+ const u = (i - w / 2) / (w / 2), v = (j - h / 2) / (h / 2), r = Math.hypot(u, v);
73
+ if (r > 1) continue;
74
+ const th = Math.atan2(v, u), arm = 0.5 + 0.5 * Math.cos(2 * th - 7.5 * Math.log(r + 0.06));
75
+ const n = nz.fbm2(u * 5, v * 5, 4) * 0.5 + 0.5;
76
+ const disk = Math.exp(-r * 4.5), core = Math.exp(-r * r * 70);
77
+ const I = disk * (0.25 + 0.75 * Math.pow(arm, 2.2)) * (0.5 + 0.9 * n) * (1 - sm(0.8, 1, r)) + core * 1.4;
78
+ const s = H.clamp(r * 2.2), k = (j * w + i) * 4;
79
+ d[k] = 255 - 90 * s; d[k + 1] = 232 - 40 * s; d[k + 2] = 196 + 59 * s; d[k + 3] = H.clamp(I) * 255;
80
+ }
81
+ x.putImageData(img, 0, 0);
82
+ return (C.gal = cv);
83
+ }
84
+
85
+ // ---------------------------------------------------------------- star fields
86
+ const BW = 1700, BH = 1300, FW = 1500, FH = 1150;
87
+ function starData() {
88
+ if (C.stars) return C.stars;
89
+ const r = H.rng(4401), tints = [[255, 246, 232], [212, 228, 255], [255, 226, 186], [200, 240, 246], [255, 212, 222]];
90
+ const mid = [], near = [];
91
+ for (let i = 0; i < 620; i++) mid.push({ x: r() * BW, y: r() * BH, m: Math.pow(r(), 3), c: r.pick(tints), ph: r() * TAU, w: 0.8 + r() * 3 });
92
+ for (let i = 0; i < 60; i++) near.push({ x: r() * BW, y: r() * BH, m: 0.25 + 0.75 * Math.pow(r(), 2), c: r.pick(tints), ph: r() * TAU, w: 0.6 + r() * 2 });
93
+ // far stars baked once, denser along the nebula river (a milky band)
94
+ const far = H.layer(FW, FH), fx = far.getContext('2d');
95
+ for (let i = 0; i < 5200; i++) {
96
+ const x = r() * FW, y = r() * FH, bx = x / FW, by = y / FH;
97
+ const dist = by - (0.84 - 0.62 * bx);
98
+ if (r() > 0.25 + 0.75 * Math.exp(-(dist * dist) / 0.03)) continue;
99
+ const m = Math.pow(r(), 2.5), c = r.pick(tints);
100
+ fx.fillStyle = rgba(c, 0.15 + m * 0.7); const s = 0.5 + m * 1.1; fx.fillRect(x, y, s, s);
101
+ }
102
+ return (C.stars = { mid, near, far });
103
+ }
104
+
105
+ // cam: { x, y (scene px), z (zoom), warp (0..1), bright, nx, ny (nebula framing offsets) }
106
+ function sky(ctx, W, Hh, t, cam) {
107
+ ctx.fillStyle = '#03040b'; ctx.fillRect(0, 0, W, Hh);
108
+ const neb = nebula(), S = starData(), warp = cam.warp || 0, br = cam.bright == null ? 1 : cam.bright;
109
+ const cx = W / 2, cy = Hh / 2;
110
+ // nebula layer (slowest parallax), with a zoom-blur smear during warp
111
+ const nzm = 1 + (cam.z - 1) * 0.22;
112
+ const drawNeb = (zz, a) => {
113
+ ctx.save(); ctx.globalAlpha = a; ctx.translate(cx, cy); ctx.scale(zz, zz);
114
+ ctx.translate(-960 - cam.x * 0.12 + (cam.nx || 0), -675 - cam.y * 0.12 + (cam.ny || 0));
115
+ ctx.imageSmoothingQuality = 'high'; ctx.drawImage(neb, 0, 0, 1920, 1350);
116
+ const g = galaxy(), gp = cam.gal || [920, 525, 1.5]; ctx.translate(gp[0], gp[1]); ctx.rotate(-0.38); ctx.scale(gp[2], gp[2] * 0.28);
117
+ ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = a * 0.75; ctx.drawImage(g, -150, -150, 300, 300);
118
+ ctx.restore();
119
+ };
120
+ drawNeb(nzm, br);
121
+ if (warp > 0.01) {
122
+ ctx.save(); ctx.globalCompositeOperation = 'lighter';
123
+ for (let i = 1; i <= 4; i++) drawNeb(nzm * (1 + warp * 0.07 * i), 0.18 * warp * br);
124
+ ctx.restore();
125
+ }
126
+ // far stars
127
+ const fz = 1 + (cam.z - 1) * 0.15;
128
+ ctx.save(); ctx.globalAlpha = br; ctx.translate(cx, cy); ctx.scale(fz, fz);
129
+ ctx.drawImage(S.far, -FW / 2 - cam.x * 0.05, -FH / 2 - cam.y * 0.05); ctx.restore();
130
+ // mid stars: twinkle, streak in warp
131
+ const zs = 1 + (cam.z - 1) * 0.5;
132
+ ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.lineCap = 'round';
133
+ for (const s of S.mid) {
134
+ let px = wrap(s.x - cam.x * 0.3, BW) - (BW - W) / 2, py = wrap(s.y - cam.y * 0.3, BH) - (BH - Hh) / 2;
135
+ px = cx + (px - cx) * zs; py = cy + (py - cy) * zs;
136
+ if (px < -60 || py < -60 || px > W + 60 || py > Hh + 60) continue;
137
+ const tw = 0.62 + 0.38 * Math.sin(t * s.w + s.ph), a = (0.22 + 0.78 * s.m) * tw * br, sz = 0.7 + s.m * 1.7;
138
+ if (warp > 0.02) {
139
+ const k = warp * (0.18 + 0.5 * s.m);
140
+ ctx.strokeStyle = rgba(s.c, Math.min(1, a * (1 + warp))); ctx.lineWidth = sz;
141
+ ctx.beginPath(); ctx.moveTo(px, py); ctx.lineTo(px + (px - cx) * k, py + (py - cy) * k); ctx.stroke();
142
+ } else { ctx.fillStyle = rgba(s.c, a); ctx.fillRect(px - sz / 2, py - sz / 2, sz, sz); }
143
+ }
144
+ // near bright stars with soft glow (fastest parallax)
145
+ const zn = 1 + (cam.z - 1) * 1.0;
146
+ for (const s of S.near) {
147
+ let px = wrap(s.x - cam.x * 0.6, BW) - (BW - W) / 2, py = wrap(s.y - cam.y * 0.6, BH) - (BH - Hh) / 2;
148
+ px = cx + (px - cx) * zn; py = cy + (py - cy) * zn;
149
+ if (px < -80 || py < -80 || px > W + 80 || py > Hh + 80) continue;
150
+ const tw = 0.7 + 0.3 * Math.sin(t * s.w * 1.7 + s.ph), a = s.m * tw * br, sz = 6 + s.m * 20;
151
+ if (warp > 0.02) {
152
+ const k = warp * 0.9;
153
+ ctx.strokeStyle = rgba(s.c, Math.min(1, a)); ctx.lineWidth = 1 + s.m * 2;
154
+ ctx.beginPath(); ctx.moveTo(px, py); ctx.lineTo(px + (px - cx) * k, py + (py - cy) * k); ctx.stroke();
155
+ }
156
+ ctx.globalAlpha = a; ctx.drawImage(glow(s.c), px - sz / 2, py - sz / 2, sz, sz);
157
+ ctx.globalAlpha = 1; ctx.fillStyle = rgba(WHITE, Math.min(1, a * 1.2)); ctx.fillRect(px - 0.8, py - 0.8, 1.6, 1.6);
158
+ }
159
+ ctx.restore();
160
+ }
161
+
162
+ // ---------------------------------------------------------------- bright star with diffraction spikes
163
+ function spikeStar(ctx, x, y, s, a, tint = [255, 236, 206], rot = 0.0) { const GA = ctx.globalAlpha;
164
+ if (a <= 0.003) return;
165
+ ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.translate(x, y);
166
+ ctx.globalAlpha = GA * a * 0.55; ctx.drawImage(glow(tint), -s * 4, -s * 4, s * 8, s * 8);
167
+ ctx.globalAlpha = GA * a * 0.5; ctx.drawImage(glow([150, 200, 255]), -s * 1.8, -s * 1.8, s * 3.6, s * 3.6);
168
+ ctx.globalAlpha = GA * a; ctx.drawImage(glow(WHITE), -s, -s, s * 2, s * 2);
169
+ ctx.globalAlpha = GA * 1; ctx.rotate(rot);
170
+ for (let k = 0; k < 8; k++) {
171
+ const main = k % 2 === 0, len = s * (main ? 9 : 3.4), wd = s * (main ? 0.1 : 0.06);
172
+ ctx.save(); ctx.rotate(k * TAU / 8);
173
+ const gr = ctx.createLinearGradient(0, 0, len, 0);
174
+ gr.addColorStop(0, rgba(WHITE, a * (main ? 0.95 : 0.55))); gr.addColorStop(0.2, rgba(tint, a * (main ? 0.45 : 0.22)));
175
+ gr.addColorStop(0.6, rgba([170, 200, 255], a * (main ? 0.12 : 0.05))); gr.addColorStop(1, rgba(tint, 0));
176
+ ctx.fillStyle = gr; ctx.beginPath(); ctx.moveTo(0, -wd); ctx.lineTo(len, 0); ctx.lineTo(0, wd); ctx.closePath(); ctx.fill();
177
+ ctx.restore();
178
+ }
179
+ ctx.globalAlpha = GA * a * 0.14; ctx.strokeStyle = rgba([190, 220, 255], 1); ctx.lineWidth = s * 0.22;
180
+ ctx.beginPath(); ctx.arc(0, 0, s * 2.4, 0, TAU); ctx.stroke();
181
+ ctx.restore();
182
+ }
183
+
184
+ // ---------------------------------------------------------------- ringed gas giant (per-pixel lit, cached)
185
+ const PR = 190, PTILT = 0.3, PINC = 0.27, PR0 = 1.34 * PR, PR1 = 1.96 * PR;
186
+ function planetSprite() {
187
+ if (C.planet) return C.planet;
188
+ const R = PR, cT = Math.cos(PTILT), sT = Math.sin(PTILT), inc = PINC, si = Math.sqrt(1 - inc * inc);
189
+ const Ex = [cT, sT, 0], Ez = [-sT * inc, cT * inc, si], Nn = [sT * si, -cT * si, inc];
190
+ const rx = PR1, ry = PR1 * inc;
191
+ const hw = Math.ceil(Math.max(R * 1.2, Math.sqrt((rx * cT) ** 2 + (ry * sT) ** 2)) + 6);
192
+ const hh = Math.ceil(Math.max(R * 1.2, Math.sqrt((rx * sT) ** 2 + (ry * cT) ** 2)) + 6);
193
+ const w = hw * 2, h = hh * 2, cv = H.layer(w, h), x = cv.getContext('2d'), img = x.createImageData(w, h), d = img.data;
194
+ const nz = H.makeNoise(2207);
195
+ const ringD = (rr) => {
196
+ const u = (rr - PR0) / (PR1 - PR0); if (u < 0 || u > 1) return 0;
197
+ let v = 0.6 + 0.17 * Math.sin(u * 47) + 0.12 * Math.sin(u * 131 + 1.3) + 0.08 * Math.sin(u * 289 + 0.4);
198
+ if (u < 0.24) v *= 0.45 + u * 1.2;
199
+ v *= 1 - 0.93 * (sm(0.6, 0.62, u) * (1 - sm(0.66, 0.68, u)));
200
+ v *= sm(0, 0.03, u) * (1 - sm(0.95, 1, u));
201
+ return H.clamp(v);
202
+ };
203
+ const ringC = (rr) => { const u = H.clamp((rr - PR0) / (PR1 - PR0)); return u < 0.5 ? H.mix([170, 160, 160], [242, 224, 186], u * 2) : H.mix([242, 224, 186], [200, 168, 130], (u - 0.5) * 2); };
204
+ const pal = [[240, 224, 194], [222, 178, 112], [184, 110, 72], [236, 210, 164], [160, 116, 142], [208, 152, 98], [244, 232, 206], [150, 160, 170]];
205
+ const band = (v) => { const n = pal.length, f = wrap(v, n), i = Math.floor(f), fr = f - i, s = fr * fr * (3 - 2 * fr), a = pal[i], b = pal[(i + 1) % n]; return [a[0] + (b[0] - a[0]) * s, a[1] + (b[1] - a[1]) * s, a[2] + (b[2] - a[2]) * s]; };
206
+ const L = LV, L2n = Math.hypot(L[0], L[1]);
207
+ for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) {
208
+ const sx = i - hw + 0.5, sy = j - hh + 0.5, r2 = sx * sx + sy * sy, rd = Math.sqrt(r2);
209
+ const lx = sx * cT + sy * sT, ly = -sx * sT + sy * cT, rz = ly / inc, rr = Math.hypot(lx, rz);
210
+ let ra = 0, rc = null, front = rz > 0;
211
+ if (rr > PR0 && rr < PR1) {
212
+ ra = ringD(rr) * 0.94;
213
+ if (ra > 0) {
214
+ const Px = sx, Py = sy, Pz = rz * si, b = Px * L[0] + Py * L[1] + Pz * L[2], c = Px * Px + Py * Py + Pz * Pz - R * R;
215
+ const lit = b < 0 && b * b > c ? 0.1 : 1;
216
+ const cc = ringC(rr), gr = 0.9 + 0.1 * nz.n2(rr * 0.9, 3.3);
217
+ rc = [cc[0] * lit * gr, cc[1] * lit * gr, cc[2] * lit * gr];
218
+ }
219
+ }
220
+ let oR = 0, oG = 0, oB = 0, oA = 0; // premultiplied
221
+ if (ra > 0 && !front && rd > R - 0.5) { oR = rc[0] * ra; oG = rc[1] * ra; oB = rc[2] * ra; oA = ra; }
222
+ if (rd < R + 0.5) {
223
+ const nx = sx / R, ny = sy / R, nzz = Math.sqrt(Math.max(0, 1 - nx * nx - ny * ny));
224
+ const lat = Math.asin(H.clamp(nx * Nn[0] + ny * Nn[1] + nzz * Nn[2], -1, 1));
225
+ const lon = Math.atan2(nx * Ez[0] + ny * Ez[1] + nzz * Ez[2], nx * Ex[0] + ny * Ex[1]);
226
+ const turb = nz.fbm3(Math.cos(lon) * 1.8, Math.sin(lon) * 1.8, lat * 11, 5);
227
+ let col = band(lat * 6.2 + turb * 0.75 + 0.03 * nz.n3(lon * 9, lat * 60, 2));
228
+ const fine = 1 + 0.07 * Math.sin(lat * 48 + turb * 4); col = [col[0] * fine, col[1] * fine, col[2] * fine];
229
+ const pole = sm(0.75, 1.25, Math.abs(lat));
230
+ col = H.mix(col, [118, 132, 160], pole * 0.7);
231
+ // great storm oval
232
+ const de = Math.pow((wrap(lon - 0.55 + Math.PI, TAU) - Math.PI) * Math.cos(lat) / 0.2, 2) + Math.pow((lat + 0.38) / 0.075, 2);
233
+ if (de < 1.8) { const s = 1 - sm(0.6, 1.0, de), rim = bump(0.7, 1.0, 1.1, 1.6, de); col = H.mix(col, [196, 92, 60], s * 0.85); col = H.mix(col, [250, 236, 210], rim * 0.5); }
234
+ const dl = nx * L[0] + ny * L[1] + nzz * L[2];
235
+ const day = sm(-0.1, 0.32, dl) * (0.3 + 0.7 * Math.pow(Math.max(dl, 0), 0.7));
236
+ let lr = col[0] * day, lg = col[1] * day, lb = col[2] * day;
237
+ const term = bump(-0.1, 0.05, 0.08, 0.3, dl) * 0.25; lr += 120 * term; lg += 40 * term; // warm terminator
238
+ lr += 10; lg += 7; lb += 22; // violet nebula fill on the night side
239
+ const limb = 0.55 + 0.45 * Math.pow(nzz, 0.6); lr *= limb; lg *= limb; lb *= limb;
240
+ // shadow of the rings on the cloud tops
241
+ const Pz = nzz * R, ln = L[0] * Nn[0] + L[1] * Nn[1] + L[2] * Nn[2], pn = sx * Nn[0] + sy * Nn[1] + Pz * Nn[2];
242
+ const s2 = -pn / ln;
243
+ if (s2 > 0) { const Qx = sx + L[0] * s2, Qy = sy + L[1] * s2, Qz = Pz + L[2] * s2, qx = Qx * Ex[0] + Qy * Ex[1], qz = Qx * Ez[0] + Qy * Ez[1] + Qz * Ez[2]; const sd = ringD(Math.hypot(qx, qz)) * 0.8; lr *= 1 - sd; lg *= 1 - sd; lb *= 1 - sd; }
244
+ // atmospheric rim scattering
245
+ const rim = Math.pow(1 - nzz, 2.6) * sm(-0.35, 0.5, dl);
246
+ lr += 110 * rim; lg += 190 * rim; lb += 240 * rim;
247
+ const ea = H.clamp(R - rd + 0.5);
248
+ oR = Math.min(255, lr) * ea + oR * (1 - ea); oG = Math.min(255, lg) * ea + oG * (1 - ea); oB = Math.min(255, lb) * ea + oB * (1 - ea); oA = ea + oA * (1 - ea);
249
+ }
250
+ if (rd > R - 1 && rd < R * 1.2) { // halo
251
+ const dr = Math.max(0, rd - R), face = sm(-0.5, 0.8, (sx * L[0] + sy * L[1]) / (rd * L2n));
252
+ const g = Math.exp(-dr / (0.03 * R)) * (0.12 + 0.88 * face) * 0.9;
253
+ oR += 100 * g; oG += 185 * g; oB += 245 * g; oA = Math.min(1, oA + g * 0.9);
254
+ }
255
+ if (ra > 0 && front) { oR = rc[0] * ra + oR * (1 - ra); oG = rc[1] * ra + oG * (1 - ra); oB = rc[2] * ra + oB * (1 - ra); oA = ra + oA * (1 - ra); }
256
+ const k = (j * w + i) * 4;
257
+ if (oA > 0.001) { d[k] = Math.min(255, oR / oA); d[k + 1] = Math.min(255, oG / oA); d[k + 2] = Math.min(255, oB / oA); d[k + 3] = oA * 255; }
258
+ }
259
+ x.putImageData(img, 0, 0);
260
+ return (C.planet = { cv, hw, hh });
261
+ }
262
+ function drawPlanet(ctx, x, y, R, a = 1) { const GA = ctx.globalAlpha;
263
+ const P = planetSprite(), s = R / PR;
264
+ ctx.save(); ctx.globalAlpha = GA * a; ctx.imageSmoothingQuality = 'high';
265
+ ctx.drawImage(P.cv, x - P.hw * s, y - P.hh * s, P.cv.width * s, P.cv.height * s); ctx.restore();
266
+ }
267
+
268
+ // ---------------------------------------------------------------- cratered moons
269
+ function moonSprite(seed, base) {
270
+ const key = 'moon' + seed;
271
+ if (C[key]) return C[key];
272
+ const R = 40, w = R * 2 + 4, cv = H.layer(w, w), x = cv.getContext('2d'), img = x.createImageData(w, w), d = img.data;
273
+ const r = H.rng(seed), nz = H.makeNoise(seed), cr = [];
274
+ for (let i = 0; i < 16; i++) { const a = r() * TAU, b = Math.acos(r() * 2 - 1); cr.push({ v: [Math.sin(b) * Math.cos(a), Math.sin(b) * Math.sin(a), Math.cos(b)], r: 0.08 + Math.pow(r(), 2) * 0.32 }); }
275
+ for (let j = 0; j < w; j++) for (let i = 0; i < w; i++) {
276
+ const nx = (i - w / 2 + 0.5) / R, ny = (j - w / 2 + 0.5) / R, q = nx * nx + ny * ny;
277
+ if (q > 1.03) continue;
278
+ const nzz = Math.sqrt(Math.max(0, 1 - q));
279
+ let alb = 0.82 + 0.18 * nz.fbm3(nx * 3, ny * 3, nzz * 3, 4);
280
+ for (const c of cr) {
281
+ const dd = Math.acos(H.clamp(nx * c.v[0] + ny * c.v[1] + nzz * c.v[2], -1, 1)) / c.r;
282
+ if (dd < 1) alb *= 0.78 + 0.12 * dd; else if (dd < 1.25) alb *= 1 + 0.16 * (1 - (dd - 1) / 0.25);
283
+ }
284
+ const dl = nx * LV[0] + ny * LV[1] + nzz * LV[2];
285
+ const lit = sm(-0.04, 0.22, dl) * (0.25 + 0.75 * Math.max(0, dl)) + 0.035;
286
+ const rim = Math.pow(1 - nzz, 3) * sm(-0.2, 0.5, dl) * 0.35;
287
+ const k = (j * w + i) * 4, ea = H.clamp((1 - Math.sqrt(q)) * R + 0.5);
288
+ d[k] = Math.min(255, base[0] * alb * lit + 120 * rim); d[k + 1] = Math.min(255, base[1] * alb * lit + 190 * rim); d[k + 2] = Math.min(255, base[2] * alb * lit + 240 * rim); d[k + 3] = ea * 255;
289
+ }
290
+ x.putImageData(img, 0, 0);
291
+ return (C[key] = cv);
292
+ }
293
+ function drawMoon(ctx, x, y, r, seed, base, a = 1) { const GA = ctx.globalAlpha;
294
+ const m = moonSprite(seed, base), s = r / 40;
295
+ ctx.save(); ctx.globalAlpha = GA * a; ctx.drawImage(m, x - m.width / 2 * s, y - m.height / 2 * s, m.width * s, m.height * s); ctx.restore();
296
+ }
297
+
298
+ // ---------------------------------------------------------------- comet
299
+ // pts: positions from the head backwards along the path. dir: unit vector for the straight ion tail.
300
+ function comet(ctx, pts, a, sc = 1, dir = null) { const GA = ctx.globalAlpha;
301
+ if (a <= 0.003 || pts.length < 2) return;
302
+ ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.lineCap = 'butt'; ctx.lineJoin = 'round';
303
+ const n = pts.length;
304
+ // dust tail: a broad curved fan of strands, widening and fading away from the head
305
+ const nrm = pts.map((p, i) => { const q = pts[Math.min(n - 1, i + 1)], o = pts[Math.max(0, i - 1)], dx = q[0] - o[0], dy = q[1] - o[1], l = Math.hypot(dx, dy) || 1; return [-dy / l, dx / l]; });
306
+ for (const [off, w, al, c] of [[0, 34, 0.03, [255, 196, 150]], [0.5, 22, 0.04, [255, 210, 170]], [1, 14, 0.05, [255, 220, 190]], [0, 10, 0.1, [255, 232, 204]], [0.3, 3, 0.22, CREAM], [0.8, 2, 0.12, CREAM]]) {
307
+ for (let i = 0; i < n - 1; i++) {
308
+ const f = i / (n - 1), f2 = (i + 1) / (n - 1), fa = Math.pow(1 - f, 1.4), o1 = off * f * f * 46 * sc, o2 = off * f2 * f2 * 46 * sc;
309
+ ctx.strokeStyle = rgba(c, al * fa * a); ctx.lineWidth = w * sc * (0.3 + 1.6 * f);
310
+ ctx.beginPath(); ctx.moveTo(pts[i][0] + nrm[i][0] * o1, pts[i][1] + nrm[i][1] * o1); ctx.lineTo(pts[i + 1][0] + nrm[i + 1][0] * o2, pts[i + 1][1] + nrm[i + 1][1] * o2); ctx.stroke();
311
+ }
312
+ }
313
+ const [hx, hy] = pts[0];
314
+ if (dir) {
315
+ const len = 230 * sc, ex = hx + dir[0] * len, ey = hy + dir[1] * len;
316
+ for (const [w, al] of [[7, 0.12], [1.6, 0.5]]) {
317
+ const g = ctx.createLinearGradient(hx, hy, ex, ey); g.addColorStop(0, rgba([140, 210, 255], al * a)); g.addColorStop(1, rgba([90, 140, 255], 0));
318
+ ctx.strokeStyle = g; ctx.lineWidth = w * sc; ctx.beginPath(); ctx.moveTo(hx, hy); ctx.lineTo(ex, ey); ctx.stroke();
319
+ }
320
+ }
321
+ ctx.globalAlpha = GA * a * 0.7; ctx.drawImage(glow([170, 230, 255]), hx - 55 * sc, hy - 55 * sc, 110 * sc, 110 * sc);
322
+ ctx.globalAlpha = GA * a * 0.5; ctx.drawImage(glow([200, 255, 240]), hx - 22 * sc, hy - 22 * sc, 44 * sc, 44 * sc);
323
+ ctx.globalAlpha = GA * a; ctx.drawImage(glow(WHITE), hx - 12 * sc, hy - 12 * sc, 24 * sc, 24 * sc);
324
+ ctx.restore();
325
+ }
326
+
327
+ // ---------------------------------------------------------------- chart line work
328
+ const ellPt = (cx, cy, rx, ry, rot, a) => { const c = Math.cos(rot), s = Math.sin(rot), x = rx * Math.cos(a), y = ry * Math.sin(a); return [cx + x * c - y * s, cy + x * s + y * c]; };
329
+ function chevron(ctx, x, y, ang, sz, c, a, lw = 1.4) {
330
+ ctx.save(); ctx.strokeStyle = rgba(c, a); ctx.lineWidth = lw; ctx.lineCap = 'round'; ctx.lineJoin = 'round';
331
+ ctx.beginPath(); ctx.moveTo(x + Math.cos(ang + 2.6) * sz, y + Math.sin(ang + 2.6) * sz); ctx.lineTo(x, y); ctx.lineTo(x + Math.cos(ang - 2.6) * sz, y + Math.sin(ang - 2.6) * sz); ctx.stroke(); ctx.restore();
332
+ }
333
+ // dotted orbit ellipse with small open arrowheads; draws on with prog; occl hides the far half behind a disk
334
+ function orbit(ctx, o) {
335
+ const { cx, cy, rx, ry, rot = 0, a0 = 0, sweep = TAU, prog = 1, c = GOLD, a = 0.8, gap = 7, r = 1.25, arrows = 3, dir = 1, occl = null, crawl = 0 } = o;
336
+ if (prog <= 0 || a <= 0) return;
337
+ const per = Math.PI * (rx + ry) * sweep / TAU, n = Math.max(40, Math.ceil(per / 2));
338
+ ctx.save(); ctx.fillStyle = rgba(c, a);
339
+ let acc = wrap(crawl, gap), last = null;
340
+ for (let k = 0; k <= n * prog; k++) {
341
+ const th = a0 + dir * sweep * k / n, p = ellPt(cx, cy, rx, ry, rot, th);
342
+ if (last) acc += Math.hypot(p[0] - last[0], p[1] - last[1]);
343
+ last = p;
344
+ if (acc >= gap) {
345
+ acc -= gap;
346
+ if (occl && Math.sin(th) < 0 && Math.hypot(p[0] - occl[0], p[1] - occl[1]) < occl[2]) continue;
347
+ ctx.beginPath(); ctx.arc(p[0], p[1], r, 0, TAU); ctx.fill();
348
+ }
349
+ }
350
+ for (let m = 0; m < arrows; m++) {
351
+ const f = (m + 0.5) / arrows; if (f > prog) continue;
352
+ const th = a0 + dir * sweep * f, p = ellPt(cx, cy, rx, ry, rot, th), q = ellPt(cx, cy, rx, ry, rot, th + dir * 0.01);
353
+ if (occl && Math.sin(th) < 0 && Math.hypot(p[0] - occl[0], p[1] - occl[1]) < occl[2]) continue;
354
+ chevron(ctx, p[0], p[1], Math.atan2(q[1] - p[1], q[0] - p[0]), 7, c, a);
355
+ }
356
+ ctx.restore();
357
+ }
358
+ // fine solid chart line (optionally dotted) drawn on from (x0,y0) to (x1,y1), quadratic via (qx,qy)
359
+ function chartLine(ctx, x0, y0, x1, y1, o = {}) {
360
+ const { prog = 1, c = GOLD, a = 0.9, lw = 1.3, q = null, arrow = true, dotted = false, glowA = 0 } = o;
361
+ if (prog <= 0 || a <= 0) return null;
362
+ const P = (u) => q ? [(1 - u) * (1 - u) * x0 + 2 * (1 - u) * u * q[0] + u * u * x1, (1 - u) * (1 - u) * y0 + 2 * (1 - u) * u * q[1] + u * u * y1] : [x0 + (x1 - x0) * u, y0 + (y1 - y0) * u];
363
+ const n = 48, end = Math.max(1, Math.ceil(n * prog));
364
+ ctx.save(); ctx.lineCap = 'round';
365
+ const path = () => { ctx.beginPath(); for (let i = 0; i <= end; i++) { const p = P(Math.min(prog, i / n)); i ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1]); } };
366
+ if (glowA > 0) { ctx.globalCompositeOperation = 'lighter'; ctx.strokeStyle = rgba(c, glowA * a); ctx.lineWidth = lw * 6; path(); ctx.stroke(); ctx.globalCompositeOperation = 'source-over'; }
367
+ ctx.strokeStyle = rgba(c, a); ctx.lineWidth = lw; if (dotted) ctx.setLineDash([1.5, 6]);
368
+ path(); ctx.stroke(); ctx.setLineDash([]);
369
+ const p1 = P(prog), p0 = P(Math.max(0, prog - 0.02));
370
+ if (arrow) chevron(ctx, p1[0], p1[1], Math.atan2(p1[1] - p0[1], p1[0] - p0[0]), 9, c, a, lw + 0.2);
371
+ ctx.restore();
372
+ return p1;
373
+ }
374
+ // graduated degree ring (astrolabe scale)
375
+ function degreeRing(ctx, cx, cy, r, prog, c, a, from = 0, numerals = true) {
376
+ if (prog <= 0 || a <= 0) return;
377
+ ctx.save(); ctx.strokeStyle = rgba(c, a); ctx.lineWidth = 1;
378
+ ctx.beginPath(); ctx.arc(cx, cy, r, from, from + TAU * prog); ctx.stroke();
379
+ const ga = ctx.globalAlpha; ctx.beginPath(); ctx.arc(cx, cy, r + 5, from, from + TAU * prog); ctx.globalAlpha = ga * 0.5; ctx.stroke(); ctx.globalAlpha = ga;
380
+ ctx.beginPath();
381
+ for (let dg = 0; dg < 360 * prog; dg += 2) {
382
+ const an = from + dg * Math.PI / 180, l = dg % 30 === 0 ? 13 : dg % 10 === 0 ? 8 : 4;
383
+ ctx.moveTo(cx + Math.cos(an) * r, cy + Math.sin(an) * r); ctx.lineTo(cx + Math.cos(an) * (r - l), cy + Math.sin(an) * (r - l));
384
+ }
385
+ ctx.stroke();
386
+ if (numerals) {
387
+ ctx.fillStyle = rgba(c, a * 0.9); ctx.font = `600 14px ${CORM}`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
388
+ for (let dg = 0; dg < 360 * prog; dg += 30) { const an = from + dg * Math.PI / 180; ctx.fillText(dg + '°', cx + Math.cos(an) * (r + 17), cy + Math.sin(an) * (r + 17)); }
389
+ }
390
+ ctx.restore();
391
+ }
392
+ function marker(ctx, x, y, r, c, a, lw = 1.2) {
393
+ if (a <= 0) return;
394
+ ctx.save(); ctx.strokeStyle = rgba(c, a); ctx.fillStyle = rgba(c, a); ctx.lineWidth = lw;
395
+ ctx.beginPath(); ctx.arc(x, y, r, 0, TAU); ctx.stroke();
396
+ ctx.beginPath(); ctx.arc(x, y, 1.8, 0, TAU); ctx.fill();
397
+ ctx.beginPath();
398
+ for (let k = 0; k < 4; k++) { const an = k * Math.PI / 2; ctx.moveTo(x + Math.cos(an) * (r + 3), y + Math.sin(an) * (r + 3)); ctx.lineTo(x + Math.cos(an) * (r + 9), y + Math.sin(an) * (r + 9)); }
399
+ ctx.stroke(); ctx.restore();
400
+ }
401
+ function label(ctx, txt, x, y, o = {}) {
402
+ const size = o.size || 26, caps = !o.italic && !o.font && o.caps !== false;
403
+ if ((o.a ?? 1) <= 0) return;
404
+ ctx.save();
405
+ ctx.font = o.font || (o.italic ? `italic 400 ${size}px ${BASK}` : `600 ${size}px ${CORM}`);
406
+ ctx.letterSpacing = caps ? (size * (o.track ?? 0.16)).toFixed(1) + 'px' : (o.track ? (size * o.track).toFixed(1) + 'px' : '0px');
407
+ ctx.textAlign = o.align || 'left'; ctx.textBaseline = o.base || 'alphabetic';
408
+ ctx.globalAlpha *= o.a ?? 1;
409
+ const s = caps ? txt.toUpperCase() : txt;
410
+ ctx.shadowColor = 'rgba(2,3,14,0.95)'; ctx.shadowBlur = o.halo ?? 14;
411
+ ctx.lineJoin = 'round'; ctx.strokeStyle = 'rgba(2,3,14,0.6)'; ctx.lineWidth = Math.max(3, size * 0.16); ctx.strokeText(s, x, y);
412
+ ctx.fillStyle = rgba(o.color || GOLD, 1); ctx.fillText(s, x, y);
413
+ ctx.shadowBlur = 0; ctx.fillText(s, x, y);
414
+ ctx.restore();
415
+ }
416
+ // a labelled chart star (small glow + crisp core)
417
+ function chartStar(ctx, x, y, m, c, a) { const GA = ctx.globalAlpha;
418
+ if (a <= 0) return;
419
+ ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = GA * a;
420
+ const s = 10 + m * 26; ctx.drawImage(glow(c), x - s / 2, y - s / 2, s, s);
421
+ ctx.fillStyle = rgba(WHITE, a); ctx.beginPath(); ctx.arc(x, y, 1.2 + m * 1.8, 0, TAU); ctx.fill(); ctx.restore();
422
+ }
423
+ // constellation figures for the examples: cat (pointed ears) and dog (floppy ears)
424
+ const GLYPH = {
425
+ cat: { p: [[-0.72, -1], [-0.28, -0.42], [0.28, -0.42], [0.72, -1], [0.9, -0.15], [0.72, 0.45], [0, 0.82], [-0.72, 0.45], [-0.9, -0.15], [-0.33, 0.02], [0.33, 0.02], [0, 0.36]],
426
+ l: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 0], [9, 11], [11, 10]] },
427
+ dog: { p: [[-0.45, -0.85], [0.45, -0.85], [1.0, -0.55], [0.95, 0.35], [0.45, 0.25], [0.32, 0.75], [0, 0.9], [-0.32, 0.75], [-0.45, 0.25], [-0.95, 0.35], [-1.0, -0.55], [-0.3, -0.12], [0.3, -0.12]],
428
+ l: [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10], [10, 0], [8, 4]] },
429
+ };
430
+ function glyph(ctx, type, x, y, s, c, a, prog = 1) {
431
+ if (a <= 0) return;
432
+ const G = GLYPH[type], P = G.p.map(([u, v]) => [x + u * s, y + v * s]);
433
+ ctx.save(); ctx.strokeStyle = rgba(c, a * 0.85); ctx.lineWidth = 1.3; ctx.lineCap = 'round';
434
+ const nl = G.l.length, upto = prog * nl;
435
+ for (let i = 0; i < nl && i < upto; i++) {
436
+ const [A, B] = G.l[i], pa = P[A], pb = P[B], f = Math.min(1, upto - i), dx = pb[0] - pa[0], dy = pb[1] - pa[1], L = Math.hypot(dx, dy), g = Math.min(5, L * 0.2);
437
+ ctx.beginPath(); ctx.moveTo(pa[0] + dx / L * g, pa[1] + dy / L * g); ctx.lineTo(pa[0] + dx / L * (g + (L - 2 * g) * f), pa[1] + dy / L * (g + (L - 2 * g) * f)); ctx.stroke();
438
+ }
439
+ ctx.restore();
440
+ P.forEach((p, i) => { if (i / P.length <= prog + 0.05) chartStar(ctx, p[0], p[1], i >= 9 ? 0.15 : 0.32, c, a); });
441
+ }
442
+ const fmt = (n) => Math.round(n).toLocaleString('en-US');
443
+
444
+ // ================================================================= STILL + CLIP
445
+ // b = build progress per element (all 1 on the finished still)
446
+ function stillScene(ctx, W, Hh, t, B) {
447
+ const camz = 1 + 0.1 * (1 - H.ease.out(B.cam));
448
+ sky(ctx, W, Hh, t, { x: t * 6, y: 0, z: camz, bright: B.sky, nx: -40, ny: 40 });
449
+ const PX = 740, PY = 450, R = 186;
450
+ // the light source: a bright star with diffraction spikes
451
+ spikeStar(ctx, 1150, 112, 16 * (0.6 + 0.4 * B.star), B.star, [255, 232, 196], 0.12);
452
+ // planet + moons on their orbits
453
+ const om = [{ rx: 452, ry: 124, rot: PTILT, w: 0.11, ph: 0.26, r: 21, seed: 31, base: [214, 206, 196] },
454
+ { rx: 330, ry: 214, rot: -0.62, w: -0.16, ph: -0.27, r: 15, seed: 47, base: [226, 190, 160] }];
455
+ ctx.save(); ctx.translate(PX, PY); ctx.scale(camz, camz); ctx.translate(-PX, -PY);
456
+ const mpos = om.map((o) => { const th = o.ph + t * o.w; return { p: ellPt(PX, PY, o.rx, o.ry, o.rot, th), back: Math.sin(th) < 0, o }; });
457
+ mpos.filter((m) => m.back).forEach((m) => drawMoon(ctx, m.p[0], m.p[1], m.o.r * 0.92, m.o.seed, m.o.base, B.planet));
458
+ drawPlanet(ctx, PX, PY, R, B.planet);
459
+ mpos.filter((m) => !m.back).forEach((m) => drawMoon(ctx, m.p[0], m.p[1], m.o.r, m.o.seed, m.o.base, B.planet));
460
+ // chart overlay
461
+ const ca = 0.85;
462
+ degreeRing(ctx, PX, PY, 505, B.ring, GOLD, 0.62, -Math.PI * 0.95, true);
463
+ om.forEach((o, i) => orbit(ctx, { cx: PX, cy: PY, rx: o.rx, ry: o.ry, rot: o.rot, prog: B.orbits, c: i ? CYAN : GOLD, a: ca * 0.8, arrows: 4, crawl: t * 9, occl: [PX, PY, R] }));
464
+ mpos.forEach((m, i) => marker(ctx, m.p[0], m.p[1], m.o.r + 9, i ? CYAN : GOLD, 0.8 * B.labels));
465
+ ctx.restore();
466
+ // comet streaking toward the planet
467
+ const cs = H.ease.out(B.comet), path = (u) => [-260 + 570 * u, 200 + 100 * u - 50 * Math.sin(u * Math.PI)];
468
+ const pts = []; for (let i = 0; i < 30; i++) { const u = cs - i * 0.03; if (u < -0.6) break; pts.push(path(u)); }
469
+ comet(ctx, pts, B.comet > 0 ? 1 : 0, 1.3, [-0.966, 0.257]);
470
+ // constellation of examples, bottom left
471
+ const cA = B.const;
472
+ const cst = [[92, 520, 'cat'], [204, 606, 'dog'], [318, 534, 'cat']];
473
+ ctx.save();
474
+ for (let i = 0; i < cst.length - 1; i++) chartLine(ctx, cst[i][0], cst[i][1], cst[i + 1][0], cst[i + 1][1], { prog: H.clamp(cA * 3 - i), c: GOLD, a: 0.45, lw: 1, arrow: false, dotted: true });
475
+ ctx.restore();
476
+ cst.forEach(([x, y, ty], i) => { const p = H.clamp(cA * 3 - i * 0.7); glyph(ctx, ty, x, y, 26, i % 2 ? CYAN : GOLD, p, p); label(ctx, ty, x, y + 60, { italic: true, size: 22, color: CREAM, align: 'center', a: p }); });
477
+ label(ctx, 'Training data', 60, 462, { size: 20, a: sm(0.6, 1, cA), color: GOLD });
478
+ // labels with leader lines
479
+ const la = B.labels;
480
+ chartLine(ctx, PX + 150, PY - 70, 1000, 372, { prog: la, c: GOLD, a: 0.8, arrow: false });
481
+ marker(ctx, PX + 150, PY - 70, 6, GOLD, la);
482
+ label(ctx, 'The model', 1010, 380, { size: 26, a: sm(0.4, 1, la) });
483
+ label(ctx, 'a planet', 1012, 408, { italic: true, size: 19, color: CREAM, a: sm(0.5, 1, la) });
484
+ const wp = ellPt(PX, PY, 452, 124, PTILT, 2.0);
485
+ marker(ctx, wp[0], wp[1], 5, CYAN, la);
486
+ chartLine(ctx, wp[0], wp[1], wp[0] - 14, wp[1] + 108, { prog: la, c: CYAN, a: 0.8, arrow: false });
487
+ label(ctx, 'Weights', wp[0] - 24, wp[1] + 134, { size: 24, color: CYAN, a: sm(0.4, 1, la) });
488
+ label(ctx, 'its orbiting moons', wp[0] - 22, wp[1] + 160, { italic: true, size: 18, color: CREAM, a: sm(0.5, 1, la) });
489
+ // title cartouche
490
+ const ta = B.title;
491
+ label(ctx, 'Chart I · the learning machine', 72, 92, { size: 18, a: sm(0, 0.4, ta), track: 0.22 });
492
+ ctx.save(); ctx.globalAlpha = sm(0.15, 0.7, ta);
493
+ ctx.font = `800 64px ${SYNE}`; ctx.fillStyle = rgba(CREAM, 1); ctx.shadowColor = 'rgba(2,3,14,0.9)'; ctx.shadowBlur = 18;
494
+ ctx.letterSpacing = (8 * (1 - H.ease.out(sm(0.15, 0.8, ta)))).toFixed(1) + 'px';
495
+ ctx.fillText('A Mind in Orbit', 68, 160); ctx.restore();
496
+ ctx.save(); ctx.strokeStyle = rgba(GOLD, 0.8); ctx.lineWidth = 1; const ul = 470 * sm(0.3, 1, ta);
497
+ ctx.beginPath(); ctx.moveTo(72, 182); ctx.lineTo(72 + ul, 182); for (let x = 0; x <= ul; x += 10) { ctx.moveTo(72 + x, 182); ctx.lineTo(72 + x, 182 + (x % 50 === 0 ? 8 : 4)); } ctx.stroke(); ctx.restore();
498
+ H.vignette(ctx, W, Hh, 0.6, '1,2,8');
499
+ }
500
+
501
+ // ================================================================= EXPLAINER
502
+ // Timing table shared by explainer() and explainerSound()
503
+ const X = {
504
+ D: 15,
505
+ hookIn: [0.15, 0.95], shrink: [1.5, 2.3], ruleIn: [0.5, 1.25],
506
+ push: [0, 2.9],
507
+ model: 2.65, weights: 3.2,
508
+ comets: Array.from({ length: 8 }, (_, i) => ({ t0: 2.5 + i * 0.3, dur: 1.1, type: 'cddccdcd'[i] === 'c' ? 'cat' : 'dog', y0: [180, 450, 300, 520, 140, 380, 250, 470][i] })),
509
+ count: [2.6, 5.65],
510
+ warp: [5.85, 6.25, 6.7],
511
+ exIn: [6.45, 7.0], stars: 6.75,
512
+ guess: [7.05, 7.5], check: 7.95, truth: 8.15, back: [8.3, 8.85], adjust: [8.9, 9.55], guess2: [9.6, 9.95],
513
+ pan: [10.45, 11.15],
514
+ pts0: 11.05, ptsDt: 0.12, hit: 12.55,
515
+ take: [13.15, 13.45, 13.75],
516
+ };
517
+ const PA = { x: 900, y: 400, R: 140 };
518
+ const PB = { x: 300, y: 432, R: 104 };
519
+ const DOG = [1000, 282], CAT = [1000, 566], EXP = [176, 238];
520
+ const ACC = Array.from({ length: 13 }, (_, i) => i === 12 ? 97 : 61 + 36 * (1 - Math.exp(-i / 3.0)) / (1 - Math.exp(-12 / 3.0)) + [0, -1.5, 1.2, -0.8, 1.4, -1.1, 0.6, -0.7, 0.5, -0.4, 0.3, -0.2, 0][i]);
521
+ const cometPath = (c) => {
522
+ const ex = PA.x - PA.R * 0.94, ey = PA.y + (c.y0 - 330) * 0.28;
523
+ const q = [380, c.y0 + (ey - c.y0) * 0.15 - 40];
524
+ return (u) => [(1 - u) * (1 - u) * -90 + 2 * (1 - u) * u * q[0] + u * u * ex, (1 - u) * (1 - u) * c.y0 + 2 * (1 - u) * u * q[1] + u * u * ey];
525
+ };
526
+ const cometU = (c, t) => { const u = (t - c.t0) / c.dur; return u < 0 ? -1 : u * (0.55 + 0.45 * u); };
527
+
528
+ function sceneA(ctx, t) { const GA = ctx.globalAlpha;
529
+ const E = H.ease;
530
+ // moons (weights) on orbits around the model planet
531
+ const om = [{ rx: 318, ry: 86, rot: PTILT, w: 0.32, ph: 0.6, r: 16, seed: 31, base: [214, 206, 196] },
532
+ { rx: 236, ry: 150, rot: -0.62, w: -0.42, ph: 2.4, r: 12, seed: 47, base: [226, 190, 160] }];
533
+ const mp = om.map((o) => { const th = o.ph + t * o.w; return { p: ellPt(PA.x, PA.y, o.rx, o.ry, o.rot, th), back: Math.sin(th) < 0, o }; });
534
+ mp.filter((m) => m.back).forEach((m) => drawMoon(ctx, m.p[0], m.p[1], m.o.r * 0.9, m.o.seed, m.o.base));
535
+ drawPlanet(ctx, PA.x, PA.y, PA.R);
536
+ // arrival flashes on the planet's limb
537
+ for (const c of X.comets) {
538
+ const ta = c.t0 + c.dur, fa = bump(ta - 0.02, ta + 0.03, ta + 0.1, ta + 0.55, t);
539
+ if (fa > 0) { const P = cometPath(c)(1); ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = GA * fa; ctx.drawImage(glow([170, 230, 255]), P[0] - 60, P[1] - 60, 120, 120); ctx.restore(); marker(ctx, P[0], P[1], 6 + 26 * sm(ta, ta + 0.55, t), CYAN, fa * 0.9); }
540
+ }
541
+ mp.filter((m) => !m.back).forEach((m) => drawMoon(ctx, m.p[0], m.p[1], m.o.r, m.o.seed, m.o.base));
542
+ // chart overlay
543
+ degreeRing(ctx, PA.x, PA.y, 372, E.inOut(sm(0.6, 2.4, t)), GOLD, 0.38, -Math.PI / 2, true);
544
+ om.forEach((o, i) => orbit(ctx, { cx: PA.x, cy: PA.y, rx: o.rx, ry: o.ry, rot: o.rot, prog: E.inOut(sm(1.0 + i * 0.3, 2.2 + i * 0.3, t)), c: i ? CYAN : GOLD, a: 0.75, arrows: 4, crawl: t * 10, occl: [PA.x, PA.y, PA.R] }));
545
+ // labels
546
+ const ml = sm(X.model, X.model + 0.35, t);
547
+ const m0 = [PA.x + 70, PA.y - 122];
548
+ chartLine(ctx, m0[0], m0[1], 976, 150, { prog: E.out(sm(X.model - 0.15, X.model + 0.2, t)), arrow: false, a: 0.85 });
549
+ marker(ctx, m0[0], m0[1], 6, GOLD, ml);
550
+ label(ctx, 'The model', 988, 148, { size: 36, a: ml });
551
+ label(ctx, 'a planet', 990, 188, { italic: true, size: 30, color: CREAM, a: sm(X.model + 0.2, X.model + 0.5, t) });
552
+ const wl = sm(X.weights, X.weights + 0.35, t), wp = ellPt(PA.x, PA.y, om[0].rx, om[0].ry, om[0].rot, 0.5);
553
+ if (wl > 0) {
554
+ chartLine(ctx, wp[0], wp[1] + 8, wp[0] - 40, 612, { prog: E.out(sm(X.weights - 0.15, X.weights + 0.2, t)), c: CYAN, arrow: false, a: 0.8 });
555
+ marker(ctx, wp[0], wp[1], 7, CYAN, wl * 0.9);
556
+ label(ctx, 'Weights', wp[0] - 110, 640, { size: 36, color: CYAN, a: wl });
557
+ label(ctx, 'its moons', wp[0] - 108, 678, { italic: true, size: 30, color: CREAM, a: sm(X.weights + 0.2, X.weights + 0.5, t) });
558
+ }
559
+ // comets of training data: each head is a constellation figure with its label
560
+ for (const c of X.comets) {
561
+ const u = cometU(c, t); if (u < -0.05 || u > 1.05) continue;
562
+ const P = cometPath(c), head = P(Math.min(1, u));
563
+ const pa = (1 - sm(0.97, 1.04, u)) * sm(-0.05, 0.05, u);
564
+ // faint dotted path ahead of the comet
565
+ ctx.save(); ctx.fillStyle = rgba(GOLD, 0.45 * pa); for (let s = Math.max(0, u); s < 1; s += 0.025) { const p = P(s); ctx.beginPath(); ctx.arc(p[0], p[1], 1.1, 0, TAU); ctx.fill(); } ctx.restore();
566
+ if (u < 0.92) { const p = P(Math.max(u + 0.05, 0.5)), p2 = P(Math.max(u + 0.06, 0.51)); chevron(ctx, p[0], p[1], Math.atan2(p2[1] - p[1], p2[0] - p[0]), 7, GOLD, 0.6 * pa); }
567
+ const pts = []; for (let i = 0; i < 22; i++) pts.push(P(Math.min(1, u) - i * 0.022));
568
+ const vx = head[0] - pts[1][0], vy = head[1] - pts[1][1], vl = Math.hypot(vx, vy) || 1;
569
+ const ion = [-vx / vl * 0.94 - vy / vl * 0.34, -vy / vl * 0.94 + vx / vl * 0.34 - 0.1];
570
+ comet(ctx, pts, pa, 0.75, ion);
571
+ const gs = 40 * (1 - 0.35 * sm(0.75, 1, u));
572
+ glyph(ctx, c.type, head[0], head[1] - 64, gs, c.type === 'cat' ? GOLDB : CYAN, pa);
573
+ label(ctx, c.type, head[0], head[1] + 44, { italic: true, size: 36, color: CREAM, align: 'center', a: pa * (1 - sm(0.8, 0.95, u)) });
574
+ }
575
+ }
576
+
577
+ function counterA(ctx, t) { const GA = ctx.globalAlpha;
578
+ const a = sm(X.count[0] - 0.1, X.count[0] + 0.3, t);
579
+ if (a <= 0) return;
580
+ const p = H.ease.inOut(sm(X.count[0], X.count[1], t)), v = p >= 1 ? 1e6 : Math.round(Math.pow(10, 6 * p));
581
+ label(ctx, 'Training data', 72, 540, { size: 34, a });
582
+ ctx.save(); ctx.globalAlpha = GA * a; ctx.font = `800 72px ${SYNE}`; ctx.fillStyle = rgba(CREAM, 1); ctx.shadowColor = 'rgba(2,3,14,0.9)'; ctx.shadowBlur = 16;
583
+ const s = fmt(v); ctx.fillText(s, 66, 634); const w = ctx.measureText(s).width; ctx.restore();
584
+ label(ctx, 'examples', 66 + w + 16, 632, { italic: true, size: 34, color: GOLD, a });
585
+ // fine magnitude scale under the number (log ticks 10^0..10^6)
586
+ ctx.save(); ctx.strokeStyle = rgba(GOLD, 0.7 * a); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(72, 660); ctx.lineTo(560, 660);
587
+ for (let k = 0; k <= 60; k++) { const x = 72 + k * 488 / 60; ctx.moveTo(x, 660); ctx.lineTo(x, 660 + (k % 10 === 0 ? 10 : 4)); }
588
+ ctx.stroke(); const x = 72 + 488 * p; ctx.fillStyle = rgba(GOLDB, a); ctx.beginPath(); ctx.moveTo(x, 656); ctx.lineTo(x - 5, 648); ctx.lineTo(x + 5, 648); ctx.fill(); ctx.restore();
589
+ }
590
+
591
+ function sceneB(ctx, t) { const GA = ctx.globalAlpha;
592
+ const E = H.ease;
593
+ const adj = E.inOut(sm(X.adjust[0], X.adjust[1], t));
594
+ const rot0 = 0.34, rot1 = -0.12, rot = rot0 + (rot1 - rot0) * adj;
595
+ const om = [{ rx: 236, ry: 76, rot, w: 0.34, ph: 0.4, r: 14, seed: 31, base: [214, 206, 196] }, { rx: 236, ry: 76, rot, w: 0.34, ph: 0.4 + Math.PI, r: 10, seed: 59, base: [200, 214, 222] },
596
+ { rx: 168, ry: 118, rot: -0.7, w: -0.45, ph: 2.0, r: 10, seed: 47, base: [226, 190, 160] }];
597
+ const mp = om.map((o) => { const th = o.ph + t * o.w + adj * 1.1 * (o.rot === rot ? 1 : 0); return { p: ellPt(PB.x, PB.y, o.rx, o.ry, o.rot, th), back: Math.sin(th) < 0, o }; });
598
+ mp.filter((m) => m.back).forEach((m) => drawMoon(ctx, m.p[0], m.p[1], m.o.r * 0.9, m.o.seed, m.o.base));
599
+ drawPlanet(ctx, PB.x, PB.y, PB.R);
600
+ mp.filter((m) => !m.back).forEach((m) => drawMoon(ctx, m.p[0], m.p[1], m.o.r, m.o.seed, m.o.base));
601
+ // ghost of the old orbit once it has been adjusted
602
+ if (adj > 0) orbit(ctx, { cx: PB.x, cy: PB.y, rx: 236, ry: 76, rot: rot0, c: RED, a: 0.35 * adj, arrows: 0, gap: 9, r: 1, occl: [PB.x, PB.y, PB.R] });
603
+ orbit(ctx, { cx: PB.x, cy: PB.y, rx: 236, ry: 76, rot, c: adj > 0 ? GOLDB : GOLD, a: 0.85, arrows: 4, crawl: t * 10, occl: [PB.x, PB.y, PB.R], r: 1.25 + adj * 0.4 * (1 - sm(9.6, 10.2, t)) });
604
+ orbit(ctx, { cx: PB.x, cy: PB.y, rx: 168, ry: 118, rot: -0.7, c: CYAN, a: 0.6, arrows: 3, crawl: t * 10, occl: [PB.x, PB.y, PB.R] });
605
+ // the target stars
606
+ const sa = sm(X.stars, X.stars + 0.3, t);
607
+ for (const [P, nm] of [[DOG, 'dog'], [CAT, 'cat']]) {
608
+ spikeStar(ctx, P[0], P[1], 7, sa * 0.9, nm === 'dog' ? [200, 220, 255] : [255, 226, 180], 0.2);
609
+ }
610
+ // the new example arrives as a comet and parks top-left
611
+ const eu = E.out(sm(X.exIn[0], X.exIn[1], t)), ep = [EXP[0] - 260 * (1 - eu), EXP[1] - 150 * (1 - eu)];
612
+ if (t > X.exIn[0]) {
613
+ const pts = []; for (let i = 0; i < 16; i++) { const uu = Math.max(0, eu - i * 0.06); pts.push([EXP[0] - 260 * (1 - uu) - i * 4 * (1 - eu), EXP[1] - 150 * (1 - uu) - i * 2.3 * (1 - eu)]); }
614
+ comet(ctx, pts, 1 - 0.75 * sm(X.exIn[1], X.exIn[1] + 0.4, t), 0.8, null);
615
+ glyph(ctx, 'cat', ep[0], ep[1], 60, GOLDB, sm(X.exIn[0], X.exIn[0] + 0.2, t));
616
+ label(ctx, 'New example', 92, EXP[1] - 82, { size: 32, a: sm(X.exIn[1] - 0.1, X.exIn[1] + 0.2, t) });
617
+ chartLine(ctx, EXP[0] + 30, EXP[1] + 62, PB.x - 70, PB.y - 92, { prog: E.out(sm(X.exIn[1], X.guess[0], t)), q: [EXP[0] + 90, EXP[1] + 110], a: 0.7, c: GOLD });
618
+ }
619
+ // labels on stars
620
+ label(ctx, 'dog', DOG[0] + 38, DOG[1] + 14, { italic: true, size: 42, color: CREAM, a: sa * (1 - sm(X.guess[1] - 0.1, X.guess[1], t)) });
621
+ label(ctx, 'cat', CAT[0] + 38, CAT[1] + 14, { italic: true, size: 42, color: CREAM, a: sa * (1 - sm(X.guess2[1] - 0.1, X.guess2[1], t)) });
622
+ // GUESS: a beam from the model to the "dog" star
623
+ const o0 = [PB.x + PB.R * 0.92, PB.y - 40];
624
+ const g1 = E.inOut(sm(X.guess[0], X.guess[1], t)), wrong = sm(X.check, X.check + 0.12, t);
625
+ const bc = H.mix(CYAN, RED, wrong);
626
+ const fade1 = 1 - 0.55 * sm(X.guess2[0], X.guess2[0] + 0.3, t);
627
+ if (g1 > 0) {
628
+ const tip = chartLine(ctx, o0[0], o0[1], DOG[0] - 22, DOG[1] + 6, { prog: g1, c: bc, a: 0.95 * fade1, lw: 1.6, glowA: 0.18 });
629
+ if (g1 < 1) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.drawImage(glow(CYAN), tip[0] - 20, tip[1] - 20, 40, 40); ctx.restore(); }
630
+ label(ctx, 'Guess', 600, 404, { size: 36, color: H.mix(CYAN, CREAM, 0.3), a: sm(X.guess[0] + 0.15, X.guess[0] + 0.45, t) * fade1 });
631
+ const dq = sm(X.guess[1] - 0.05, X.guess[1] + 0.15, t);
632
+ label(ctx, 'dog?', DOG[0] + 38, DOG[1] + 14, { italic: true, size: 44, color: H.mix(CREAM, RED, wrong), a: dq });
633
+ }
634
+ // CHECK: red ring marker + cross, line turns red
635
+ if (t > X.check) {
636
+ const k = sm(X.check, X.check + 0.25, t), pulse = 1 + 0.5 * Math.exp(-(t - X.check) * 5);
637
+ ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = GA * 0.6 * Math.exp(-(t - X.check) * 3); ctx.drawImage(glow(RED), DOG[0] - 90, DOG[1] - 90, 180, 180); ctx.restore();
638
+ marker(ctx, DOG[0], DOG[1], 28 * pulse, RED, k, 1.8);
639
+ ctx.save(); ctx.strokeStyle = rgba(RED, k); ctx.lineWidth = 2.4; ctx.lineCap = 'round'; const cx = DOG[0] + 160, cy = DOG[1] - 2, s = 13 * k;
640
+ ctx.beginPath(); ctx.moveTo(cx - s, cy - s); ctx.lineTo(cx + s, cy + s); ctx.moveTo(cx + s, cy - s); ctx.lineTo(cx - s, cy + s); ctx.stroke(); ctx.restore();
641
+ label(ctx, 'Check', DOG[0] + 40, DOG[1] - 48, { size: 36, color: RED, a: sm(X.check + 0.05, X.check + 0.3, t) });
642
+ label(ctx, 'it’s a cat', EXP[0] + 78, EXP[1] + 14, { italic: true, size: 32, color: GOLDB, a: sm(X.truth, X.truth + 0.25, t) });
643
+ }
644
+ // error signal travels back to the model along a curved return path
645
+ const bk = sm(X.back[0], X.back[1], t);
646
+ if (bk > 0) {
647
+ const tip = chartLine(ctx, DOG[0] - 10, DOG[1] - 30, PB.x + 20, PB.y - PB.R - 14, { prog: E.inOut(bk), q: [700, 70], c: RED, a: 0.8 * fade1, dotted: true, lw: 1.8 });
648
+ if (bk < 1) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.drawImage(glow(RED), tip[0] - 26, tip[1] - 26, 52, 52); ctx.restore(); }
649
+ label(ctx, 'error', 676, 146, { italic: true, size: 34, color: RED, align: 'center', a: sm(X.back[0] + 0.2, X.back[0] + 0.45, t) * fade1 });
650
+ }
651
+ // ADJUST: the weights' orbit swings to a new tilt; curved arrow shows the turn
652
+ const al = sm(X.adjust[0], X.adjust[0] + 0.3, t);
653
+ const ae = ellPt(PB.x, PB.y, 236, 76, rot, 0.0);
654
+ if (al > 0) {
655
+ ctx.save(); ctx.strokeStyle = rgba(GOLDB, al); ctx.lineWidth = 1.6; ctx.beginPath();
656
+ ctx.arc(PB.x, PB.y, 262, rot0 - 0.02, rot0 + (rot - rot0) - 0.02, true); ctx.stroke(); ctx.restore();
657
+ const aa = rot - 0.02; chevron(ctx, PB.x + Math.cos(aa) * 262, PB.y + Math.sin(aa) * 262, aa - Math.PI / 2, 9, GOLDB, al, 1.8);
658
+ label(ctx, 'Adjust', 400, 636, { size: 38, a: al, color: GOLDB });
659
+ label(ctx, 'tilt the weights', 402, 674, { italic: true, size: 30, color: CREAM, a: sm(X.adjust[0] + 0.2, X.adjust[0] + 0.5, t) });
660
+ }
661
+ label(ctx, 'Weights', ae[0] + 30, ae[1] + 12, { size: 32, color: CYAN, a: sm(6.9, 7.2, t) * (1 - al) });
662
+ // GUESS AGAIN: beam re-aimed to the "cat" star
663
+ const g2 = E.inOut(sm(X.guess2[0], X.guess2[1], t));
664
+ if (g2 > 0) {
665
+ const o1 = [PB.x + PB.R * 0.9, PB.y + 10];
666
+ const tip = chartLine(ctx, o1[0], o1[1], CAT[0] - 22, CAT[1] - 4, { prog: g2, c: GOLDB, a: 0.95, lw: 1.8, glowA: 0.22 });
667
+ if (g2 < 1) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.drawImage(glow(GOLDB), tip[0] - 22, tip[1] - 22, 44, 44); ctx.restore(); }
668
+ const ok = sm(X.guess2[1], X.guess2[1] + 0.2, t);
669
+ if (ok > 0) {
670
+ ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = GA * 0.7 * Math.exp(-(t - X.guess2[1]) * 2.5); ctx.drawImage(glow(GOLDB), CAT[0] - 110, CAT[1] - 110, 220, 220); ctx.restore();
671
+ marker(ctx, CAT[0], CAT[1], 28 * (1 + 0.4 * Math.exp(-(t - X.guess2[1]) * 5)), GOLDB, ok, 1.8);
672
+ label(ctx, 'cat', CAT[0] + 40, CAT[1] + 14, { italic: true, size: 44, color: GOLDB, a: ok });
673
+ ctx.save(); ctx.strokeStyle = rgba(GOLDB, ok); ctx.lineWidth = 2.6; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; const cx = CAT[0] + 152, cy = CAT[1] - 2;
674
+ ctx.beginPath(); ctx.moveTo(cx - 12, cy); ctx.lineTo(cx - 3, cy + 10 * ok); ctx.lineTo(cx - 3 + 18 * ok, cy - 14 * ok); ctx.stroke(); ctx.restore();
675
+ label(ctx, 'Guess again', 960, 640, { size: 34, color: GOLDB, a: ok, align: 'right' });
676
+ }
677
+ }
678
+ }
679
+
680
+ function sceneC(ctx, t) { const GA = ctx.globalAlpha;
681
+ const E = H.ease, x0 = 112, x1 = 742, yT = 150, yB = 600;
682
+ const ya = (v) => yB - (v - 50) / 50 * (yB - yT);
683
+ // atlas grid: curved meridians and parallels (conic projection feel)
684
+ const ga = sm(10.5, 11.1, t);
685
+ ctx.save(); ctx.strokeStyle = rgba(CYAN, 0.16 * ga); ctx.lineWidth = 1;
686
+ for (let k = 0; k <= 6; k++) { const x = x0 + k * (x1 - x0) / 6; ctx.beginPath(); ctx.moveTo(x, yB); ctx.quadraticCurveTo(x + (x - 427) * 0.06, (yT + yB) / 2, x + (x - 427) * 0.1, yT); ctx.stroke(); }
687
+ for (let v = 50; v <= 100; v += 10) { const y = ya(v); ctx.beginPath(); ctx.moveTo(x0, y); ctx.quadraticCurveTo(427, y - 22, x1 + 20, y); ctx.stroke(); }
688
+ ctx.restore();
689
+ // graduated axes
690
+ ctx.save(); ctx.strokeStyle = rgba(GOLD, 0.85 * ga); ctx.lineWidth = 1.1; ctx.beginPath(); ctx.moveTo(x0, yT - 10); ctx.lineTo(x0, yB); ctx.lineTo(x1 + 30, yB);
691
+ for (let v = 50; v <= 100; v++) { const y = ya(v), l = v % 10 === 0 ? 14 : v % 5 === 0 ? 8 : 4; ctx.moveTo(x0, y); ctx.lineTo(x0 - l, y); }
692
+ for (let k = 0; k <= 60; k++) { const x = x0 + k * (x1 - x0) / 60; ctx.moveTo(x, yB); ctx.lineTo(x, yB + (k % 10 === 0 ? 12 : 4)); }
693
+ ctx.stroke(); ctx.restore();
694
+ for (let v = 50; v <= 100; v += 10) label(ctx, v + '%', x0 - 20, ya(v) + 7, { size: 24, align: 'right', a: ga * 0.9, track: 0.02, color: GOLD });
695
+ label(ctx, 'Training rounds', x0 + 4, yB + 50, { size: 34, a: ga });
696
+ chevron(ctx, x1 + 30, yB, 0, 9, GOLD, ga);
697
+ // the constellation forms, one star per round
698
+ const pts = ACC.map((v, i) => [x0 + 34 + i * (x1 - x0 - 60) / 12, ya(v)]);
699
+ ctx.save(); ctx.strokeStyle = rgba(GOLDB, 0.8); ctx.lineWidth = 1.3; ctx.lineCap = 'round';
700
+ for (let i = 1; i < pts.length; i++) {
701
+ const f = sm(X.pts0 + i * X.ptsDt - 0.1, X.pts0 + i * X.ptsDt, t); if (f <= 0) continue;
702
+ const [ax, ay] = pts[i - 1], [bx, by] = pts[i], L = Math.hypot(bx - ax, by - ay), dx = (bx - ax) / L, dy = (by - ay) / L, g = 7;
703
+ ctx.beginPath(); ctx.moveTo(ax + dx * g, ay + dy * g); ctx.lineTo(ax + dx * (g + (L - 2 * g) * f), ay + dy * (g + (L - 2 * g) * f)); ctx.stroke();
704
+ }
705
+ ctx.restore();
706
+ pts.forEach((p, i) => { const a = sm(X.pts0 + i * X.ptsDt - 0.03, X.pts0 + i * X.ptsDt + 0.05, t); chartStar(ctx, p[0], p[1], 0.2 + 0.6 * i / 12, i === 0 ? CYAN : GOLDB, a); });
707
+ // dotted best-fit arc behind the stars
708
+ const fitA = sm(12.0, 12.5, t);
709
+ if (fitA > 0) { ctx.save(); ctx.setLineDash([1.5, 6]); ctx.strokeStyle = rgba(CYAN, 0.5 * fitA); ctx.lineWidth = 1.2; ctx.beginPath(); pts.forEach((p, i) => { const y = ya(61 + 36 * (1 - Math.exp(-i / 3.0)) / (1 - Math.exp(-12 / 3.0))); i ? ctx.lineTo(p[0], y) : ctx.moveTo(p[0], y); }); ctx.stroke(); ctx.restore(); }
710
+ // first point marker
711
+ marker(ctx, pts[0][0], pts[0][1], 12, CYAN, sm(X.pts0, X.pts0 + 0.2, t));
712
+ label(ctx, '61%', pts[0][0] + 22, pts[0][1] + 40, { size: 32, color: CYAN, a: sm(X.pts0, X.pts0 + 0.2, t), track: 0.04 });
713
+ // stellar flare at 97%
714
+ const hit = t - X.hit, last = pts[12];
715
+ if (hit > -0.05) {
716
+ const fl = hit < 0 ? 0 : Math.exp(-hit * 1.6) * 0.8 + 0.35;
717
+ spikeStar(ctx, last[0], last[1], 9 + 16 * Math.exp(-hit * 2.2) * sm(-0.05, 0.05, hit), H.clamp(fl), [255, 228, 180], 0.0);
718
+ marker(ctx, last[0], last[1], 22 + 30 * (1 - Math.exp(-Math.max(0, hit) * 4)), GOLDB, sm(0, 0.15, hit) * (0.5 + 0.5 * Math.exp(-hit * 2)), 1.6);
719
+ if (hit > 0) { ctx.save(); ctx.strokeStyle = rgba(GOLDB, 0.6 * Math.exp(-hit * 2.5)); ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(last[0], last[1], 30 + hit * 260, 0, TAU); ctx.stroke(); ctx.restore(); }
720
+ }
721
+ // the number
722
+ const fi = H.clamp((t - X.pts0) / X.ptsDt, 0, 12), i0 = Math.floor(fi), v = i0 >= 12 ? 97 : ACC[i0] + (ACC[i0 + 1] - ACC[i0]) * (fi - i0);
723
+ const na = sm(10.95, 11.25, t);
724
+ label(ctx, 'Accuracy', 826, 262, { size: 40, a: na });
725
+ ctx.save(); ctx.globalAlpha = GA * na; ctx.font = `800 116px ${SYNE}`; ctx.shadowColor = 'rgba(2,3,14,0.9)'; ctx.shadowBlur = 20;
726
+ const fl2 = hit > 0 ? Math.exp(-hit * 2) : 0;
727
+ ctx.fillStyle = rgba(H.mix(CREAM, GOLDB, sm(-0.1, 0.1, hit) * 0.85 + 0), 1); ctx.fillText(Math.round(v) + '%', 822, 404);
728
+ if (fl2 > 0) { ctx.globalCompositeOperation = 'lighter'; ctx.shadowBlur = 40; ctx.shadowColor = rgba(GOLDB, fl2); ctx.fillStyle = rgba(GOLDB, 0.35 * fl2); ctx.fillText(Math.round(v) + '%', 822, 404); }
729
+ ctx.restore();
730
+ const ra = sm(X.hit, X.hit + 0.3, t);
731
+ label(ctx, 'from 61%', 830, 474, { italic: true, size: 34, color: CYAN, a: Math.max(sm(11.3, 11.6, t) * 0.8, ra) });
732
+ ctx.save(); ctx.strokeStyle = rgba(GOLD, 0.7 * na); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(830, 290); ctx.lineTo(1190, 290); for (let x = 0; x <= 360; x += 12) { ctx.moveTo(830 + x, 290); ctx.lineTo(830 + x, 290 + (x % 60 === 0 ? 8 : 4)); } ctx.stroke(); ctx.restore();
733
+ }
734
+
735
+ function takeaway(ctx, W, t) {
736
+ const [a, b, c] = X.take, E = H.ease;
737
+ const ra = sm(a, a + 0.3, t);
738
+ if (ra <= 0) return;
739
+ const cx = W / 2, y1 = 300, y2 = 520;
740
+ label(ctx, 'The takeaway', cx, 190, { size: 32, align: 'center', a: ra, track: 0.3 });
741
+ ctx.save(); ctx.strokeStyle = rgba(GOLD, 0.8 * ra); ctx.lineWidth = 1; const hw = 150 * E.out(ra);
742
+ ctx.beginPath(); ctx.moveTo(cx - 140 - hw, 178); ctx.lineTo(cx - 140, 178); ctx.moveTo(cx + 140, 178); ctx.lineTo(cx + 140 + hw, 178); ctx.stroke(); ctx.restore();
743
+ ctx.save(); ctx.font = `800 64px ${SYNE}`; ctx.textAlign = 'center';
744
+ ctx.shadowColor = 'rgba(2,3,14,0.95)'; ctx.shadowBlur = 20;
745
+ ctx.globalAlpha = ra; ctx.fillStyle = rgba(CREAM, 1); ctx.fillText('More examples', cx, y1 - 14 * (1 - E.out(sm(a, a + 0.35, t))));
746
+ ctx.globalAlpha = sm(c, c + 0.3, t); ctx.fillStyle = rgba(GOLDB, 1); ctx.fillText('better guesses', cx, y2 - 14 * (1 - E.out(sm(c, c + 0.35, t))));
747
+ const w2 = ctx.measureText('better guesses').width;
748
+ ctx.restore();
749
+ // the arrow is a dotted orbit arc with an arrowhead, swinging down from line one to line two
750
+ const ap = E.inOut(sm(b, c, t));
751
+ chartLine(ctx, cx, y1 + 34, cx, y2 - 70, { prog: ap, q: [cx + 90, (y1 + y2) / 2 - 20], c: GOLDB, a: 1, lw: 2, dotted: true });
752
+ marker(ctx, cx, y1 + 34, 5, GOLD, sm(b - 0.1, b, t));
753
+ // degree scale underline
754
+ ctx.save(); ctx.strokeStyle = rgba(GOLD, 0.75 * sm(c, c + 0.4, t)); ctx.lineWidth = 1; const ul = w2 * E.out(sm(c, c + 0.5, t)), x0 = cx - w2 / 2;
755
+ ctx.beginPath(); ctx.moveTo(x0, y2 + 26); ctx.lineTo(x0 + ul, y2 + 26); for (let x = 0; x <= ul; x += 9) { ctx.moveTo(x0 + x, y2 + 26); ctx.lineTo(x0 + x, y2 + 26 + (x % 45 === 0 ? 9 : 4)); } ctx.stroke(); ctx.restore();
756
+ }
757
+
758
+ function explainer(ctx, W, Hh, t) {
759
+ const E = H.ease;
760
+ const [w0, wm, w1] = X.warp;
761
+ const warpIn = sm(w0, wm, t), warpOut = sm(wm, w1, t);
762
+ const warp = t < wm ? E.in(warpIn) : 1 - E.out(warpOut);
763
+ const pan = E.inOut(sm(X.pan[0], X.pan[1], t));
764
+ const zA = H.lerp(0.62, 1, E.inOut(sm(X.push[0], X.push[1], t))) + 0.05 * sm(X.push[1], w0, t);
765
+ const camZ = t < wm ? zA + 0.6 * warpIn : 1 + 0.6 * (1 - E.out(warpOut)) + 0.03 * sm(w1, X.pan[0], t);
766
+ const tk = sm(X.take[0] - 0.1, X.take[0] + 0.45, t);
767
+ sky(ctx, W, Hh, t, { x: t * 8, y: -720 * pan, z: camZ - 0.08 * E.inOut(tk), warp, bright: 0.35 + 0.65 * sm(0, 0.8, t), nx: 60, ny: 30, gal: [1440, 350, 1.0] });
768
+
769
+ // ---- scene A: the model planet + data comets
770
+ if (t < wm + 0.05) {
771
+ ctx.save();
772
+ ctx.globalAlpha = 1 - warpIn;
773
+ ctx.translate(W / 2, Hh / 2); ctx.scale(zA, zA); ctx.translate(-W / 2, -Hh / 2);
774
+ const ws = 1 + 3.2 * E.in(warpIn); ctx.translate(PA.x, PA.y); ctx.scale(ws, ws); ctx.translate(-PA.x, -PA.y);
775
+ sceneA(ctx, t);
776
+ ctx.restore();
777
+ ctx.save(); ctx.globalAlpha = 1 - sm(w0, w0 + 0.25, t); counterA(ctx, t); ctx.restore();
778
+ }
779
+ // ---- scene B: guess / check / adjust
780
+ if (t > wm - 0.05 && t < X.pan[1] + 0.05) {
781
+ ctx.save();
782
+ ctx.globalAlpha = sm(wm - 0.05, wm + 0.3, t);
783
+ ctx.translate(0, 720 * pan);
784
+ const sb = 0.55 + 0.45 * E.out(sm(wm - 0.05, w1 + 0.05, t));
785
+ ctx.translate(W / 2, Hh / 2); ctx.scale(sb, sb); ctx.translate(-W / 2, -Hh / 2);
786
+ const punch = 0.05 * E.inOut(sm(X.check - 0.05, X.check + 0.3, t)) * (1 - E.inOut(sm(X.back[0], X.adjust[0], t)));
787
+ ctx.translate(DOG[0], DOG[1]); ctx.scale(1 + punch, 1 + punch); ctx.translate(-DOG[0], -DOG[1]);
788
+ sceneB(ctx, t);
789
+ ctx.restore();
790
+ }
791
+ // ---- scene C: the accuracy constellation (above scene B; camera tilts up), then the takeaway
792
+ const rec = E.inOut(tk);
793
+ if (t > X.pan[0] - 0.05) {
794
+ if (tk < 1) {
795
+ ctx.save();
796
+ ctx.translate(0, -720 * (1 - pan));
797
+ ctx.globalAlpha = 1 - rec;
798
+ ctx.translate(W / 2, 330); ctx.scale(1 - 0.1 * rec, 1 - 0.1 * rec); ctx.translate(-W / 2, -330 - 50 * rec);
799
+ sceneC(ctx, t);
800
+ ctx.restore();
801
+ }
802
+ if (tk > 0) { ctx.save(); const g = ctx.createRadialGradient(W / 2, 360, 40, W / 2, 360, 520); g.addColorStop(0, rgba([3, 4, 12], 0.6 * rec)); g.addColorStop(1, rgba([3, 4, 12], 0)); ctx.fillStyle = g; ctx.fillRect(0, 0, W, Hh); ctx.restore(); }
803
+ takeaway(ctx, W, t);
804
+ }
805
+ // ---- header: hook title shrinks into the corner
806
+ {
807
+ const sh = E.inOut(sm(X.shrink[0], X.shrink[1], t));
808
+ ctx.save(); ctx.font = `800 70px ${SYNE}`; const s = 'How does AI learn?', tw = ctx.measureText(s).width;
809
+ const bx = W / 2 - tw / 2, by = 250, sx = 40, sy = 62, sc = 1 + (36 / 70 - 1) * sh;
810
+ const x = bx + (sx - bx) * sh, y = by + (sy - by) * sh;
811
+ ctx.translate(x, y); ctx.scale(sc, sc);
812
+ const n = s.length, rev = sm(X.hookIn[0], X.hookIn[1], t) * n;
813
+ ctx.shadowColor = 'rgba(2,3,14,0.95)'; ctx.shadowBlur = 20; ctx.fillStyle = rgba(CREAM, 1);
814
+ let cx = 0;
815
+ for (let i = 0; i < n; i++) { const ch = s[i], cw = ctx.measureText(ch).width, a = H.clamp(rev - i); if (a > 0) { ctx.globalAlpha = a; ctx.fillText(ch, cx, -10 * (1 - a)); } cx += cw; }
816
+ ctx.globalAlpha = 1; ctx.shadowBlur = 0;
817
+ // fine graduated rule + subtitle under the big title (fades as it shrinks)
818
+ const rp = E.out(sm(X.ruleIn[0], X.ruleIn[1], t)), ra = 1 - sm(X.shrink[0], X.shrink[0] + 0.4, t);
819
+ ctx.strokeStyle = rgba(GOLD, 0.85 * ra); ctx.lineWidth = 1.2 / sc; ctx.beginPath(); ctx.moveTo(0, 28); ctx.lineTo(tw * rp, 28);
820
+ for (let k = 0; k <= tw * rp; k += 12) { ctx.moveTo(k, 28); ctx.lineTo(k, 28 + (k % 60 === 0 ? 12 : 5)); } ctx.stroke();
821
+ ctx.restore();
822
+ label(ctx, 'a star chart of machine learning', W / 2, by - 84, { italic: true, size: 30, color: GOLD, align: 'center', a: sm(0.8, 1.2, t) * (1 - sm(X.shrink[0], X.shrink[0] + 0.3, t)) });
823
+ if (sh > 0.9) { ctx.save(); ctx.strokeStyle = rgba(GOLD, 0.6 * sm(0.9, 1, sh)); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(40, 78); ctx.lineTo(40 + tw * 36 / 70, 78); ctx.stroke(); ctx.restore(); }
824
+ }
825
+ // ---- warp flash
826
+ const fl = Math.exp(-Math.pow((t - wm) / 0.09, 2));
827
+ if (fl > 0.01) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; const g = ctx.createRadialGradient(W / 2, Hh / 2, 0, W / 2, Hh / 2, 700); g.addColorStop(0, rgba([210, 235, 255], 0.75 * fl)); g.addColorStop(0.5, rgba([120, 90, 220], 0.3 * fl)); g.addColorStop(1, rgba([40, 20, 80], 0)); ctx.fillStyle = g; ctx.fillRect(0, 0, W, Hh); ctx.restore(); }
828
+ H.vignette(ctx, W, Hh, 0.55, '1,2,8');
829
+ }
830
+
831
+ // ================================================================= SOUND
832
+ function explainerSound(k) {
833
+ const D = X.D, E = H.ease;
834
+ // deep space pad: detuned low sines/triangles with slow swells, lowpassed
835
+ const pad = (t, notes, dur, g = 1, o = {}) => notes.forEach((n, i) => {
836
+ for (const det of [-6, 6]) k.tone(t, { f: n, type: i ? 'triangle' : 'sine', detune: det, dur, gain: 0.05 * g, a: o.a || dur * 0.4, hold: dur * 0.3, filter: 'lowpass', f2: o.cut || 900, q: 0.5, pan: (i - (notes.length - 1) / 2) * 0.35, verb: 0.5, minor: o.minor });
837
+ });
838
+ // soft low radar ping (sine + quiet fifth, long reverb tail, all under 1.5 kHz)
839
+ const ping = (t, f = 880, g = 1, pan = 0, minor = true) => {
840
+ k.tone(t, { f, dur: 0.9, gain: 0.12 * g, a: 0.004, verb: 0.6, pan, minor });
841
+ k.tone(t, { f: k.hz(f) * 1.5, dur: 0.35, gain: 0.03 * g, a: 0.004, verb: 0.5, pan, minor });
842
+ };
843
+ // airy comet whoosh: pink noise band sweeping low -> mid -> low
844
+ const airy = (t, dur, g = 1, pan = 0, top = 1300) => k.noise(t, { dur, color: 'pink', filter: 'bandpass', f: [[0, 220], [dur * 0.6, top], [dur, 380]], q: 0.7, gain: 0.13 * g, a: dur * 0.6, verb: 0.35, pan, minor: true });
845
+ // throttled counter ticks: soft low tock under 1.5 kHz
846
+ const counter = (val, a, b, gap, f, g = 1) => { let last = -1, prev = val(a); for (let t = a; t <= b; t += 0.002) { const v = val(t); if (v !== prev && t - last >= gap) { k.noise(t, { dur: 0.02, filter: 'bandpass', f, q: 4, gain: 0.22 * g, a: 0.001, pan: -0.35, minor: true }); k.tone(t, { f: f * 0.5, dur: 0.05, gain: 0.04 * g, pan: -0.35, minor: true }); last = t; } prev = v; } };
847
+
848
+ // ---- bed: sub drone + slow evolving pad (dropped in voiceover mode via music/bed... pads are tones, mark minor)
849
+ k.bed('room', 0, D, { gain: 0.5 });
850
+ k.withMinor(() => {
851
+ k.tone(0, { f: 'A1', dur: X.warp[0] + 0.3, gain: 0.07, a: 1.5, hold: X.warp[0] - 1.8, filter: 'lowpass', f2: 300, bus: 'amb' });
852
+ pad(0.0, ['A2', 'E3', 'B3'], 3.2, 0.9, { a: 1.6 });
853
+ pad(2.8, ['F2', 'C3', 'A3', 'E4'], 3.4, 0.8, { a: 1.2 });
854
+ k.tone(X.warp[2], { f: 'D2', dur: X.pan[0] - X.warp[2] + 0.5, gain: 0.06, a: 0.8, hold: 2.6, filter: 'lowpass', f2: 300, bus: 'amb' });
855
+ pad(6.7, ['D3', 'A3', 'E4'], 1.4, 0.65, { a: 0.6 });
856
+ k.tone(X.pan[0], { f: 'C2', dur: D - X.pan[0], gain: 0.06, a: 1.0, hold: 2.5, filter: 'lowpass', f2: 300, bus: 'amb' });
857
+ });
858
+
859
+ // ---- hook: title letters reveal; graduated rule draws; title slides to the corner
860
+ k.noise(0.1, { dur: 1.1, color: 'pink', filter: 'bandpass', f: [[0, 200], [1.1, 700]], q: 0.8, gain: 0.08, a: 0.8, verb: 0.4 });
861
+ ping(0.95, 'A4', 0.9, 0);
862
+ k.noise(X.shrink[0], { dur: 0.8, color: 'pink', filter: 'lowpass', f: [[0, 260], [0.45, 700], [0.8, 260]], gain: 0.12, a: 0.35, minor: true });
863
+
864
+ // ---- degree ring + orbits draw on (0.6-2.5): faint scanning sweep
865
+ k.noise(0.6, { dur: 1.8, color: 'pink', filter: 'bandpass', f: [[0, 400], [1.8, 900]], q: 3, gain: 0.04, a: 0.9, minor: true });
866
+
867
+ // ---- labels land: radar pings
868
+ ping(X.model, 'E5', 1.7, 0.5, false);
869
+ ping(X.weights, 'B4', 0.9, 0.4);
870
+
871
+ // ---- comets stream in: airy whoosh on approach, soft ping on arrival at the planet
872
+ X.comets.forEach((c, i) => {
873
+ airy(c.t0 + 0.1, c.dur * 0.9, 0.9, -0.6 + 0.1 * (i % 4));
874
+ ping(c.t0 + c.dur, ['A4', 'C5', 'E5', 'A4', 'G4', 'E5', 'C5', 'A5'][i], 0.55, 0.35);
875
+ });
876
+ // examples counter 10^(6p): ticks, throttled, then a lock ping
877
+ counter((t) => Math.round(Math.pow(10, 6 * E.inOut(sm(X.count[0], X.count[1], t)))), X.count[0], X.count[1] - 0.02, 0.1, 1100, 0.8);
878
+ ping(X.count[1], 'A4', 1.1, -0.4, false);
879
+
880
+ // ---- warp: deep sub rumble + rising airy streak, a soft bloom at the midpoint
881
+ const [w0, wm, w1] = X.warp;
882
+ k.tone(w0 - 0.1, { f: [[0, 55], [wm - w0, 34], [w1 - w0 + 0.4, 28]], dur: w1 - w0 + 0.6, gain: 0.42, a: wm - w0, hold: 0.05 });
883
+ k.noise(w0, { dur: w1 - w0 + 0.3, color: 'brown', filter: 'lowpass', f: [[0, 120], [wm - w0, 600], [w1 - w0 + 0.3, 90]], gain: 0.4, a: wm - w0 });
884
+ k.noise(w0, { dur: w1 - w0, color: 'pink', filter: 'bandpass', f: [[0, 300], [wm - w0, 1600], [w1 - w0, 300]], q: 0.8, gain: 0.12, a: wm - w0 - 0.05, verb: 0.4 });
885
+ k.tone(wm, { f: 'A2', dur: 1.2, gain: 0.08, a: 0.01, verb: 0.6 });
886
+
887
+ // ---- scene B
888
+ airy(X.exIn[0], X.exIn[1] - X.exIn[0] + 0.15, 1, -0.6); // new example comet arrives
889
+ ping(X.exIn[1], 'C5', 0.8, -0.6);
890
+ ping(X.stars, 'E4', 0.6, 0.5); ping(X.stars + 0.06, 'G4', 0.5, 0.6); // two target stars appear
891
+ // guess beam: a soft rising glide while the line draws
892
+ k.tone(X.guess[0], { f: [[0, 'A3'], [X.guess[1] - X.guess[0], 'E4']], type: 'sine', dur: X.guess[1] - X.guess[0] + 0.1, gain: 0.06, a: 0.2, filter: 'lowpass', f2: 1200, verb: 0.3, pan: 0.2, minor: true });
893
+ ping(X.guess[1], 'E5', 0.8, 0.5, false); // "dog?" lands
894
+ // CHECK: wrong. Low dissonant swell (minor second + tritone, detuned saws, dark lowpass)
895
+ ['Bb2', 'B2', 'F3'].forEach((n, i) => k.tone(X.check, { f: n, type: 'sawtooth', detune: (i - 1) * 9, dur: 1.1, gain: 0.07, a: 0.05, hold: 0.4, filter: 'lowpass', f2: [[0, 300], [0.15, 900], [1.1, 300]], q: 1, pan: 0.5, verb: 0.4 }));
896
+ k.tone(X.check, { f: [[0, 90], [0.4, 60]], dur: 0.6, gain: 0.3, a: 0.01 });
897
+ // error signal travels back: a falling dark glide + red pulse
898
+ k.tone(X.back[0], { f: [[0, 'E4'], [X.back[1] - X.back[0], 'A2']], type: 'triangle', dur: X.back[1] - X.back[0] + 0.1, gain: 0.07, a: 0.15, filter: 'lowpass', f2: 900, pan: -0.2, verb: 0.3 });
899
+ ping(X.back[1], 'A3', 0.8, -0.5);
900
+ // ADJUST: the orbit swings; a slow low whoosh and a settling ping
901
+ k.noise(X.adjust[0], { dur: X.adjust[1] - X.adjust[0] + 0.2, color: 'pink', filter: 'bandpass', f: [[0, 250], [0.4, 800], [0.85, 300]], q: 1.2, gain: 0.12, a: 0.35, pan: -0.4, verb: 0.3 });
902
+ k.tone(X.adjust[0], { f: [[0, 'D3'], [0.65, 'A3']], type: 'triangle', dur: 0.75, gain: 0.05, a: 0.3, filter: 'lowpass', f2: 900, verb: 0.3, pan: -0.4, minor: true });
903
+ ping(X.adjust[1], 'D5', 0.7, -0.4);
904
+ // guess again -> "cat" correct: warm consonant swell
905
+ k.tone(X.guess2[0], { f: [[0, 'A3'], [X.guess2[1] - X.guess2[0], 'E4']], dur: X.guess2[1] - X.guess2[0] + 0.1, gain: 0.06, a: 0.2, filter: 'lowpass', f2: 1200, verb: 0.3, pan: 0.2, minor: true });
906
+ pad(X.guess2[1], ['D3', 'A3', 'F#4', 'A4'], 1.5, 1.6, { a: 0.06, cut: 1400 });
907
+ ping(X.guess2[1], 'A5', 0.9, 0.5, false);
908
+
909
+ // ---- tilt-up pan to the accuracy chart: smooth low air
910
+ k.noise(X.pan[0], { dur: X.pan[1] - X.pan[0] + 0.2, color: 'pink', filter: 'bandpass', f: [[0, 200], [0.4, 700], [0.85, 220]], q: 0.8, gain: 0.14, a: 0.35, verb: 0.3 });
911
+ // constellation stars appear one per round: soft pings rising in pitch
912
+ const scale = ['A3', 'C4', 'D4', 'E4', 'G4', 'A4', 'C5', 'D5', 'E5', 'G5', 'A5', 'C6', 'D6'];
913
+ for (let i = 0; i < 13; i++) ping(X.pts0 + i * X.ptsDt, scale[i] === 'C6' ? 'A5' : scale[i] === 'D6' ? 'C6' : scale[i], 0.6, -0.3 + i * 0.05);
914
+ // big low bloom with shimmer at 97%
915
+ k.tone(X.hit, { f: 'C2', dur: 2.6, gain: 0.32, a: 0.03, hold: 0.4, verb: 0.4 });
916
+ k.tone(X.hit, { f: [[0, 70], [0.3, 45]], dur: 0.6, gain: 0.35, a: 0.005 });
917
+ pad(X.hit, ['C3', 'G3', 'E4', 'B4'], 2.2, 2.0, { a: 0.04, cut: 1600 });
918
+ k.noise(X.hit, { dur: 1.6, color: 'pink', filter: 'bandpass', f: [[0, 2200], [1.6, 900]], q: 2, gain: 0.07, a: 0.1, verb: 0.6 });
919
+ ['E5', 'G5', 'B5', 'C6', 'E6'].forEach((n, i) => k.tone(X.hit + 0.08 + i * 0.07, { f: n, dur: 1.4, gain: 0.025, a: 0.05, verb: 0.6, pan: -0.5 + i * 0.25 }));
920
+
921
+ // ---- takeaway: recede, then a resolving warm pad as the line lands
922
+ k.noise(X.take[0] - 0.1, { dur: 0.7, color: 'pink', filter: 'lowpass', f: [[0, 240], [0.4, 600], [0.7, 240]], gain: 0.1, a: 0.3, minor: true });
923
+ k.tone(X.take[1], { f: [[0, 'C4'], [0.3, 'G4']], dur: 0.4, gain: 0.04, a: 0.15, filter: 'lowpass', f2: 1200, verb: 0.3, minor: true });
924
+ pad(X.take[2], ['F2', 'C3', 'A3', 'E4'], D - X.take[2], 1.4, { a: 0.25, cut: 1200 });
925
+ ping(X.take[2], 'C5', 1.0, 0, false); ping(X.take[2] + 0.12, 'G5', 0.6, 0.3, false);
926
+ }
927
+
928
+ registerStyle({
929
+ id: 'cosmic',
930
+ name: 'Cosmic Star Chart',
931
+ category: 'Cosmic & Psychedelic',
932
+ description: 'Deep-space illustration crossed with an engraved celestial atlas: fbm nebulae in teal, violet, rose and gold, a lit ringed gas giant with cratered moons, comets and a diffraction-spiked star, overlaid with hairline gold and pale-cyan chart work (dotted orbits with arrowheads, degree rings, ring markers, small-caps serif labels).',
933
+ motion: 'The sky fades up star by star while the camera drifts in, the key star flares, the planet lights up, then the chart engraves itself over it: degree ring sweeping round, orbits dotting on, markers and labels landing, a comet streaking past.',
934
+ fonts: [`600 26px ${CORM}`, `italic 400 26px ${CORM}`, `800 64px ${SYNE}`, `italic 400 26px ${BASK}`, `400 26px ${BASK}`],
935
+ frameT: 5.8,
936
+ duration: 6,
937
+ readsPixels: false,
938
+ draw(ctx, W, Hh, t) {
939
+ const B = {
940
+ sky: sm(0, 1.0, t), cam: sm(0, 6, t), star: sm(0.4, 1.2, t), planet: sm(0.8, 1.8, t),
941
+ ring: H.ease.inOut(sm(1.5, 3.2, t)), orbits: H.ease.inOut(sm(1.8, 3.3, t)), labels: sm(2.8, 3.6, t),
942
+ comet: sm(2.0, 5.2, t), const: sm(3.0, 4.4, t), title: sm(3.3, 4.6, t),
943
+ };
944
+ stillScene(ctx, W, Hh, t, B);
945
+ },
946
+ explainerDuration: X.D,
947
+ explainerNote: 'Told as one celestial chart overlaid on deep space: the model is a ringed planet whose moons are its weights, cat/dog examples arrive as constellation-headed comets, a warp streak changes scene, the guess is a chart beam to a labelled star that turns red with a red ring marker, the weights’ orbit visibly tilts, the camera tilts up to an atlas chart where the accuracy constellation forms and flares at 97%; numbers are big Syne figures over graduated sextant scales.',
948
+ explainer,
949
+ explainerSound,
950
+ });
951
+ }