@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,991 @@
1
+ {
2
+ // ---------- isometric helpers ----------
3
+ const C30 = Math.cos(Math.PI / 6);
4
+ const makeIso = (ox, oy, s) => (x, y, z = 0) => [ox + (x - y) * C30 * s, oy + (x + y) * 0.5 * s - z * s];
5
+ const SHADE_DARK = [92, 88, 150];
6
+ const shade = (base, k = 1) => ({
7
+ top: H.rgba(H.mix(base, '#ffffff', 0.42 * k)),
8
+ left: H.rgba(H.mix(base, '#ffffff', 0.08)),
9
+ right: H.rgba(H.mix(base, SHADE_DARK, 0.28 * k)),
10
+ edge: H.rgba(H.mix(base, '#ffffff', 0.75), 0.9),
11
+ });
12
+ function poly(ctx, pts, fill) {
13
+ ctx.beginPath(); ctx.moveTo(pts[0][0], pts[0][1]);
14
+ for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i][0], pts[i][1]);
15
+ ctx.closePath(); if (fill) { ctx.fillStyle = fill; ctx.fill(); }
16
+ }
17
+ function box(ctx, P, x, y, z, w, d, h, c, edges = true) {
18
+ const px = P(x, y, 0)[1] - P(x, y, 1)[1], hp = h * px; // pixels per unit, face height in px
19
+ // ambient-occlusion contact strip where a box stands on the floor (faces drawn next cover its inner half)
20
+ if (Math.abs(z) < 0.01 && hp > 6) {
21
+ const a = P(x, y + d, 0), b = P(x + w, y + d, 0), e = P(x + w, y, 0);
22
+ ctx.save(); ctx.lineCap = 'butt'; ctx.lineJoin = 'round';
23
+ for (const [lw, al] of [[Math.min(12, px * 0.3), 0.07], [Math.min(6, px * 0.15), 0.1], [Math.min(3, px * 0.07), 0.14]]) {
24
+ ctx.strokeStyle = `rgba(60,50,110,${al})`; ctx.lineWidth = lw;
25
+ ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(e[0], e[1]); ctx.stroke();
26
+ }
27
+ ctx.restore();
28
+ }
29
+ poly(ctx, [P(x + w, y, z), P(x + w, y + d, z), P(x + w, y + d, z + h), P(x + w, y, z + h)], c.right);
30
+ poly(ctx, [P(x, y + d, z), P(x + w, y + d, z), P(x + w, y + d, z + h), P(x, y + d, z + h)], c.left);
31
+ // tonal falloff on the side faces: a touch lighter at the top edge, darker toward the floor
32
+ if (hp > 10) {
33
+ const t0 = P(x + w, y + d, z + h), b0 = P(x + w, y + d, z);
34
+ const g = ctx.createLinearGradient(0, t0[1], 0, b0[1]);
35
+ g.addColorStop(0, 'rgba(255,255,255,0.13)'); g.addColorStop(0.4, 'rgba(255,255,255,0)'); g.addColorStop(1, 'rgba(60,50,110,0.10)');
36
+ ctx.beginPath();
37
+ const f1 = [P(x + w, y, z), P(x + w, y + d, z), P(x + w, y + d, z + h), P(x + w, y, z + h)], f2 = [P(x, y + d, z), P(x + w, y + d, z), P(x + w, y + d, z + h), P(x, y + d, z + h)];
38
+ for (const f of [f1, f2]) { ctx.moveTo(f[0][0], f[0][1]); for (let i = 1; i < 4; i++) ctx.lineTo(f[i][0], f[i][1]); ctx.closePath(); }
39
+ ctx.fillStyle = g; ctx.fill();
40
+ }
41
+ poly(ctx, [P(x, y, z + h), P(x + w, y, z + h), P(x + w, y + d, z + h), P(x, y + d, z + h)], c.top);
42
+ if (edges) {
43
+ ctx.strokeStyle = c.edge; ctx.lineWidth = 1; ctx.lineJoin = 'round';
44
+ ctx.beginPath();
45
+ const a = P(x, y + d, z + h), b = P(x + w, y + d, z + h), e = P(x + w, y, z + h), f = P(x + w, y + d, z);
46
+ ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(e[0], e[1]);
47
+ ctx.moveTo(b[0], b[1]); ctx.lineTo(f[0], f[1]);
48
+ ctx.stroke();
49
+ }
50
+ }
51
+ // convex hull (monotone chain) of 2D world points
52
+ function hull(pts) {
53
+ pts = pts.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
54
+ const cr = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
55
+ const lo = [], up = [];
56
+ for (const p of pts) { while (lo.length >= 2 && cr(lo[lo.length - 2], lo[lo.length - 1], p) <= 0) lo.pop(); lo.push(p); }
57
+ for (let i = pts.length - 1; i >= 0; i--) { const p = pts[i]; while (up.length >= 2 && cr(up[up.length - 2], up[up.length - 1], p) <= 0) up.pop(); up.push(p); }
58
+ up.pop(); lo.pop(); return lo.concat(up);
59
+ }
60
+ function cylinder(ctx, P, s, cx, cy, z, r, h, base) {
61
+ const b = P(cx, cy, z), tp = P(cx, cy, z + h);
62
+ const rx = 1.2247 * r * s, ry = 0.7071 * r * s;
63
+ const g = ctx.createLinearGradient(b[0] - rx, 0, b[0] + rx, 0);
64
+ g.addColorStop(0, H.rgba(H.mix(base, '#ffffff', 0.15)));
65
+ g.addColorStop(0.35, H.rgba(H.mix(base, '#ffffff', 0.3)));
66
+ g.addColorStop(0.55, H.rgba(base));
67
+ g.addColorStop(1, H.rgba(H.mix(base, SHADE_DARK, 0.32)));
68
+ ctx.fillStyle = g;
69
+ ctx.beginPath(); ctx.ellipse(b[0], b[1], rx, ry, 0, 0, Math.PI); ctx.lineTo(tp[0] - rx, tp[1]);
70
+ ctx.ellipse(tp[0], tp[1], rx, ry, 0, Math.PI, 0, true); ctx.closePath(); ctx.fill();
71
+ ctx.fillStyle = H.rgba(H.mix(base, '#ffffff', 0.5));
72
+ ctx.beginPath(); ctx.ellipse(tp[0], tp[1], rx, ry, 0, 0, Math.PI * 2); ctx.fill();
73
+ return { b, tp, rx, ry };
74
+ }
75
+
76
+ // ================= EXPLAINER: "How does AI learn?" as one long isometric factory the camera travels along =================
77
+ function petFace(c, kind, x, y, r, col, ink) {
78
+ c.save(); c.translate(x, y);
79
+ c.fillStyle = col;
80
+ if (kind === 'cat') {
81
+ for (const sx of [-1, 1]) { c.beginPath(); c.moveTo(sx * 0.85 * r, -0.2 * r); c.lineTo(sx * 0.62 * r, -1.15 * r); c.lineTo(sx * 0.12 * r, -0.78 * r); c.closePath(); c.fill(); }
82
+ } else {
83
+ c.fillStyle = H.rgba(H.mix(col, ink, 0.35));
84
+ for (const sx of [-1, 1]) { c.beginPath(); c.ellipse(sx * 0.88 * r, 0.05 * r, 0.32 * r, 0.62 * r, sx * 0.35, 0, 7); c.fill(); }
85
+ c.fillStyle = col;
86
+ }
87
+ c.beginPath(); c.arc(0, 0, r, 0, 7); c.fill();
88
+ c.fillStyle = ink;
89
+ for (const sx of [-1, 1]) { c.beginPath(); c.arc(sx * 0.36 * r, -0.12 * r, r * 0.12, 0, 7); c.fill(); }
90
+ if (kind === 'cat') {
91
+ c.beginPath(); c.moveTo(-0.1 * r, 0.18 * r); c.lineTo(0.1 * r, 0.18 * r); c.lineTo(0, 0.3 * r); c.closePath(); c.fill();
92
+ c.strokeStyle = ink; c.lineWidth = Math.max(0.6, r * 0.05);
93
+ c.beginPath(); for (const sx of [-1, 1]) for (const dy of [0.22, 0.36]) { c.moveTo(sx * 0.3 * r, dy * r); c.lineTo(sx * 0.85 * r, (dy - 0.06) * r); } c.stroke();
94
+ } else {
95
+ c.beginPath(); c.ellipse(0, 0.32 * r, 0.2 * r, 0.14 * r, 0, 0, 7); c.fill();
96
+ }
97
+ c.restore();
98
+ }
99
+ const wallX = (ctx, p) => ctx.transform(C30, 0.5, 0, 1, p[0], p[1]); // plane facing +y (lower-left), px units
100
+ const floorX = (ctx, p) => ctx.transform(C30, 0.5, -C30, 0.5, p[0], p[1]); // floor plane, px units
101
+ function tag(ctx, x, y, text, o = {}) {
102
+ ctx.save();
103
+ ctx.font = o.font || '600 13px "Inter"';
104
+ const fsz = parseInt((ctx.font.match(/(\d+)px/) || [0, 14])[1]);
105
+ const w = ctx.measureText(text).width + (o.icon ? fsz * 2.4 : fsz * 1.4), h = o.h || Math.round(fsz * 1.75);
106
+ const k = o.k ?? 1;
107
+ ctx.translate(x, y); ctx.scale(k, k);
108
+ ctx.shadowColor = 'rgba(80,70,140,0.2)'; ctx.shadowBlur = 14; ctx.shadowOffsetY = 4;
109
+ ctx.fillStyle = o.bg || '#ffffff'; ctx.beginPath(); ctx.roundRect(-w / 2, -h / 2, w, h, h / 2); ctx.fill();
110
+ ctx.shadowColor = 'transparent';
111
+ if (o.icon) { ctx.fillStyle = o.iconCol || '#ff6b5b'; ctx.beginPath(); ctx.arc(-w / 2 + fsz * 0.9, 0, fsz * 0.3, 0, 7); ctx.fill(); }
112
+ ctx.fillStyle = o.col || '#2d3058'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
113
+ ctx.fillText(text, o.icon ? fsz * 0.5 : 0, 1);
114
+ ctx.restore();
115
+ }
116
+ function numberTag(ctx, x, y, num, label, o = {}) {
117
+ const k = o.k ?? 1; if (k <= 0.01) return;
118
+ ctx.save(); ctx.translate(x, y); ctx.scale(k, k);
119
+ ctx.font = '800 66px "Inter"'; const nw = ctx.measureText(num).width;
120
+ ctx.font = '600 24px "Inter"'; const lw = ctx.measureText(label).width;
121
+ const w = Math.max(nw, lw) + 56, h = 118;
122
+ ctx.shadowColor = 'rgba(80,70,140,0.25)'; ctx.shadowBlur = 22; ctx.shadowOffsetY = 6;
123
+ ctx.fillStyle = o.bg || '#ffffff'; ctx.beginPath(); ctx.roundRect(-w / 2, -h / 2, w, h, 26); ctx.fill(); ctx.shadowColor = 'transparent';
124
+ ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic';
125
+ ctx.fillStyle = o.sub || '#7b7aa3'; ctx.fillText(label, 0, -h / 2 + 34);
126
+ ctx.font = '800 66px "Inter"'; ctx.fillStyle = o.col || '#2d3058'; ctx.fillText(num, 0, h / 2 - 18);
127
+ ctx.restore();
128
+ }
129
+ function arrow(ctx, pts, prog, col, dash, w = 2) {
130
+ // pts: quadratic [a, ctrl, b] in screen space, drawn on up to prog
131
+ if (prog <= 0) return;
132
+ const [a, c, b] = pts;
133
+ const q = (u) => [(1 - u) * (1 - u) * a[0] + 2 * u * (1 - u) * c[0] + u * u * b[0], (1 - u) * (1 - u) * a[1] + 2 * u * (1 - u) * c[1] + u * u * b[1]];
134
+ ctx.save(); ctx.strokeStyle = col; ctx.lineWidth = w; ctx.lineCap = 'round'; if (dash) ctx.setLineDash(dash);
135
+ ctx.beginPath(); ctx.moveTo(a[0], a[1]); const n = 30; for (let i = 1; i <= n; i++) { const p = q(prog * i / n); ctx.lineTo(p[0], p[1]); } ctx.stroke();
136
+ ctx.setLineDash([]);
137
+ const e = q(prog), e0 = q(Math.max(0, prog - 0.04)), ang = Math.atan2(e[1] - e0[1], e[0] - e0[0]);
138
+ ctx.fillStyle = col; ctx.translate(e[0], e[1]); ctx.rotate(ang);
139
+ ctx.beginPath(); ctx.moveTo(4, 0); ctx.lineTo(-7, -5.5); ctx.lineTo(-7, 5.5); ctx.closePath(); ctx.fill();
140
+ ctx.restore();
141
+ return q;
142
+ }
143
+ function isoExplainer(ctx, W, H_, t) {
144
+ const ph = (a, b) => H.clamp((t - a) / (b - a));
145
+ const E = (a, b) => H.ease.inOut(ph(a, b));
146
+ const EO = (a, b) => H.ease.out(ph(a, b));
147
+ const ACC = '#ff6b5b', MINT = '#3ccf9a', INK = '#2d3058';
148
+ // ---------- camera keyframes: [t, fx, fy, scale] ----------
149
+ const KF = [[0, 11, 4, 0.5, 24], [1.6, 11, 4, 0.5, 26], [2.8, 4.6, 3.4, 2.0, 46], [5.9, 5.6, 3.6, 1.9, 46], [6.9, 15.3, 5.0, 1.8, 54], [10.3, 15.3, 5.0, 1.8, 54], [11.1, 22.9, 3.6, 2.9, 68], [12.9, 22.9, 3.6, 2.9, 68], [13.8, 13, 4, 0.8, 27], [20, 13, 4, 0.8, 27]];
150
+ let k = 0; while (k < KF.length - 2 && t > KF[k + 1][0]) k++;
151
+ const [t0, fx0, fy0, fz0, s0] = KF[k], [t1, fx1, fy1, fz1, s1] = KF[k + 1];
152
+ const u = H.ease.inOut(H.clamp((t - t0) / (t1 - t0)));
153
+ const fx = H.lerp(fx0, fx1, u), fy = H.lerp(fy0, fy1, u), fz = H.lerp(fz0, fz1, u), s = H.lerp(s0, s1, u);
154
+ const bob = Math.sin(t * 1.2) * 3;
155
+ const P = (x, y, z = 0) => [640 + ((x - fx) - (y - fy)) * C30 * s, 390 + bob + ((x - fx) + (y - fy)) * 0.5 * s - (z - fz) * s];
156
+ const LX = 26, LY = 8, TH = 0.9;
157
+
158
+ // ---------- background ----------
159
+ const bg = ctx.createLinearGradient(0, 0, W, H_);
160
+ bg.addColorStop(0, '#f6efe8'); bg.addColorStop(0.55, '#efeaf4'); bg.addColorStop(1, '#e4e3f3');
161
+ ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
162
+ ctx.fillStyle = 'rgba(120,110,170,0.10)';
163
+ for (let y = 18; y < H_; y += 28) for (let x = 18 + ((y / 28) % 2) * 14; x < W; x += 28) ctx.fillRect(x, y, 2, 2);
164
+ { const glow = ctx.createRadialGradient(660, 380, 40, 660, 380, 620); glow.addColorStop(0, 'rgba(255,255,255,0.7)'); glow.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H_); }
165
+ // long soft shadow of the slab
166
+ {
167
+ const L = H.layer(W, H_), lc = L.getContext('2d');
168
+ lc.filter = `blur(${Math.round(s * 0.5)}px)`;
169
+ const G = (x, y) => { const p = P(x + 2.2, y, -TH); return [p[0], p[1] + s * 3.2]; };
170
+ poly(lc, [G(0, 0), G(LX, 0), G(LX, LY), G(0, LY)], 'rgb(90,80,150)');
171
+ ctx.globalAlpha = 0.14; ctx.drawImage(L, 0, 0); ctx.globalAlpha = 1;
172
+ }
173
+ // slab
174
+ box(ctx, P, 0.3, 0.3, -TH - 0.6, LX - 0.6, LY - 0.6, 0.6, shade('#b9addf'), false);
175
+ box(ctx, P, 0, 0, -TH, LX, LY, TH, shade('#cfc6ea'), false);
176
+ poly(ctx, [P(0, 0), P(LX, 0), P(LX, LY), P(0, LY)], '#fbf8f3');
177
+ ctx.strokeStyle = 'rgba(170,160,205,0.2)'; ctx.lineWidth = 1; ctx.beginPath();
178
+ for (let i = 1; i < LX; i++) { const a = P(i, 0), b = P(i, LY); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
179
+ for (let i = 1; i < LY; i++) { const a = P(0, i), b = P(LX, i); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
180
+ ctx.stroke();
181
+ // zone tints + painted floor labels
182
+ poly(ctx, [P(0.3, 0.3), P(9.6, 0.3), P(9.6, 3.6), P(0.3, 3.6)], 'rgba(255,214,196,0.35)');
183
+ poly(ctx, [P(19.2, 0.6), P(25.7, 0.6), P(25.7, 6.6), P(19.2, 6.6)], 'rgba(205,240,224,0.45)');
184
+ const floorText = (x, y, str, a = 1, size = 22) => { const o = P(x, y); ctx.save(); floorX(ctx, o); ctx.scale(s / 35, s / 35); ctx.globalAlpha = a; ctx.font = `800 ${size}px "Inter"`; ctx.fillStyle = 'rgba(150,140,200,0.55)'; ctx.fillText(str, 0, 0); ctx.restore(); };
185
+ floorText(0.9, 7.6, 'TRAINING DATA', 1, 20);
186
+ floorText(19.6, 7.4, 'TRAINING ROUNDS →', EO(10.6, 11.4), 20);
187
+ ctx.strokeStyle = 'rgba(255,255,255,0.95)'; ctx.lineWidth = 1.5;
188
+ ctx.beginPath(); { const a = P(0, LY), b = P(LX, LY), c = P(LX, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(c[0], c[1]); } ctx.stroke();
189
+
190
+ // ---------- scene objects (painter-sorted) ----------
191
+ const objs = [], shadows = [];
192
+ const add = (key, fn) => objs.push({ key, fn });
193
+ const R = H.rng(31);
194
+ // data stacks at the back: grow while the counter runs
195
+ const cnt = Math.floor(1e6 * Math.pow(EO(2.8, 5.7), 1.6));
196
+ const grow = 0.25 + 2.2 * Math.pow(EO(2.6, 5.8), 1.2);
197
+ [[1.0, 1.0], [3.2, 1.0], [5.4, 1.0], [7.6, 1.0]].forEach(([x, y], i) => {
198
+ const h = Math.max(0.15, grow * (0.8 + 0.25 * Math.sin(i * 2.3)));
199
+ shadows.push([x, y, 1.3, 1.6, h]);
200
+ add(x + 0.65 + y + 0.8, () => {
201
+ const n = Math.max(1, Math.round(h / 0.16));
202
+ for (let j = 0; j < n; j++) box(ctx, P, x + Math.sin(j * 1.7 + i) * 0.04, y, j * 0.16, 1.3, 1.6, 0.14, shade(j % 2 ? '#ffffff' : (i % 2 ? '#ffd9cf' : '#dbe3f6')), false);
203
+ // top card shows a pet face in the floor plane
204
+ const o = P(x + 0.65, y + 0.8, n * 0.16);
205
+ ctx.save(); floorX(ctx, o); ctx.scale(s / 40, s / 40); petFace(ctx, i % 2 ? 'dog' : 'cat', 0, 0, 13, i % 2 ? '#f2c39a' : '#c8cde6', INK); ctx.restore();
206
+ });
207
+ });
208
+ // billboard counter over the data zone
209
+ add(4.4 + 0.4, () => {
210
+ for (const x of [1.4, 7.6]) box(ctx, P, x, 0.35, 0, 0.18, 0.18, 3.0, shade('#d8d4ea'), false);
211
+ box(ctx, P, 1.0, 0.25, 3.0, 7.2, 0.25, 1.15, shade('#ffffff'));
212
+ const o = P(1.0, 0.5, 4.15);
213
+ ctx.save(); wallX(ctx, o); ctx.scale(s / 40, s / 40);
214
+ ctx.font = '800 32px "Inter"'; ctx.fillStyle = INK; ctx.fillText('DATASET', 60, 36);
215
+ ctx.restore();
216
+ });
217
+ // conveyor belt (segmented for correct depth)
218
+ for (let i = 0; i < 10; i++) {
219
+ add(i + 0.5 + 5.5, () => {
220
+ box(ctx, P, i, 5.0, 0, 1, 1.0, 0.4, shade('#8e97c9'), false);
221
+ ctx.strokeStyle = 'rgba(255,255,255,0.35)'; ctx.lineWidth = 1;
222
+ const off = ((t * 2.2) % 0.5);
223
+ for (let k2 = 0; k2 < 2; k2++) { const xx = i + k2 * 0.5 + off; if (xx > i + 1) continue; const a = P(xx, 5.0, 0.4), b = P(xx, 6.0, 0.4); ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke(); }
224
+ });
225
+ }
226
+ // example cards riding the belt (beat 2) + the single test card (beat 3)
227
+ const cards = [];
228
+ if (t > 2.4 && t < 6.6) {
229
+ for (let j = 0; j < 9; j++) {
230
+ const x = (t - 2.4) * 2.6 - j * 2.6 + 0.3;
231
+ if (x < 0.2 || x > 9.4) continue;
232
+ cards.push({ x, kind: (j * 7 + 3) % 3 === 0 ? 'dog' : ((j * 5) % 2 ? 'cat' : 'dog'), lab: true });
233
+ }
234
+ }
235
+ if (t > 5.9 && t < 7.4) cards.push({ x: H.lerp(0.4, 9.6, E(5.9, 7.3)), kind: 'cat', test: true });
236
+ for (const cd of cards) {
237
+ add(cd.x + 0.5 + 5.5 + 0.6, () => {
238
+ const y = 5.45;
239
+ box(ctx, P, cd.x, y, 0.4, 1.0, 0.1, 1.0, shade(cd.test ? '#ffd9cf' : '#ffffff'), false);
240
+ const o = P(cd.x + 0.5, y + 0.1, 0.9);
241
+ ctx.save(); wallX(ctx, o); ctx.scale(s / 40, s / 40);
242
+ petFace(ctx, cd.kind, 0, 0, 12, cd.kind === 'cat' ? '#b9c2ea' : '#f2c39a', INK);
243
+ ctx.restore();
244
+ });
245
+ }
246
+ // the model: a machine with a glass-top neural net and three tuning dials
247
+ const adj = E(8.6, 9.3);
248
+ const nodes = [];
249
+ for (let c = 0; c < 3; c++) for (let r = 0; r < 3; r++) nodes.push([10.8 + c * 1.2, 3.9 + r * 1.25, c]);
250
+ shadows.push([10, 3.2, 4, 3.8, 2.6]);
251
+ add(12 + 5.1, () => {
252
+ box(ctx, P, 10, 3.2, 0, 4, 3.8, 2.6, shade('#aab6e8'));
253
+ // intake slot on the front
254
+ poly(ctx, [P(10.25, 7, 0.45), P(10.95, 7, 0.45), P(10.95, 7, 1.5), P(10.25, 7, 1.5)], '#4a5488');
255
+ // dials on the +y face
256
+ const err = ph(7.4, 8.6) * (1 - ph(9.3, 9.8));
257
+ for (let d = 0; d < 3; d++) {
258
+ const o = P(11.6 + d * 0.85, 7, 1.15);
259
+ ctx.save(); wallX(ctx, o); ctx.scale(s / 40, s / 40);
260
+ ctx.fillStyle = '#eef1fa'; ctx.beginPath(); ctx.arc(0, 0, 13, 0, 7); ctx.fill();
261
+ ctx.strokeStyle = err > 0 ? H.rgba(ACC, 0.4 + 0.6 * err) : '#8e9bd0'; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.arc(0, 0, 13, 0, 7); ctx.stroke();
262
+ const ang = -0.9 + d * 0.7 + adj * (d === 1 ? -1.6 : 1.1) + Math.sin(t * 9) * 0.15 * (adj > 0 && adj < 1 ? 1 : 0);
263
+ ctx.strokeStyle = INK; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(Math.cos(ang) * 10, Math.sin(ang) * 10); ctx.stroke();
264
+ ctx.restore();
265
+ }
266
+ // net on top (glass panel)
267
+ poly(ctx, [P(10.3, 3.5, 2.61), P(13.7, 3.5, 2.61), P(13.7, 6.7, 2.61), P(10.3, 6.7, 2.61)], 'rgba(230,236,255,0.9)');
268
+ const wts = (i, j) => { const a = Math.abs(Math.sin(i * 3.1 + j * 1.7)), b = Math.abs(Math.cos(i * 2.3 + j * 0.9)); return H.lerp(a, b, adj); };
269
+ const pulse = ph(6.9, 7.4) * (1 - ph(7.4, 7.6)) + ph(9.3, 9.6) * (1 - ph(9.6, 9.9));
270
+ for (let i = 0; i < 9; i++) for (let j = 0; j < 9; j++) {
271
+ if (nodes[j][2] !== nodes[i][2] + 1) continue;
272
+ const a = P(nodes[i][0], nodes[i][1], 2.62), b = P(nodes[j][0], nodes[j][1], 2.62);
273
+ const w = wts(i, j), errCol = H.mix([142, 155, 208], H.hex(ACC), ph(8.0, 8.6) * (1 - ph(9.0, 9.4)));
274
+ ctx.strokeStyle = H.rgba(errCol, 0.35 + 0.6 * w); ctx.lineWidth = 0.6 + 2.6 * w * (s / 40);
275
+ ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
276
+ }
277
+ for (const [x, y, c] of nodes) {
278
+ const p = P(x, y, 2.62), rr = 0.17 * s;
279
+ const lit = pulse > 0 ? Math.max(0, Math.sin((t * 6) - c * 1.2)) * pulse : 0;
280
+ ctx.fillStyle = H.rgba(H.mix('#7d8fd6', '#ffe28a', lit)); ctx.beginPath(); ctx.ellipse(p[0], p[1], rr, rr * 0.7, 0, 0, 7); ctx.fill();
281
+ ctx.fillStyle = 'rgba(255,255,255,0.7)'; ctx.beginPath(); ctx.ellipse(p[0] - rr * 0.3, p[1] - rr * 0.25, rr * 0.35, rr * 0.22, 0, 0, 7); ctx.fill();
282
+ }
283
+ });
284
+ // output monitor
285
+ const shake = (t > 7.4 && t < 7.8) ? Math.sin(t * 90) * 0.06 : 0;
286
+ shadows.push([15.3, 4.4, 3.2, 0.6, 3.2]);
287
+ add(16.9 + 4.8, () => {
288
+ box(ctx, P, 16.6, 4.5, 0, 0.4, 0.4, 1.0, shade('#d8d4ea'), false);
289
+ box(ctx, P, 15.2 + shake, 4.4, 1.0, 3.4, 0.5, 2.3, shade('#4a5488'));
290
+ const sx0 = 15.4 + shake, sx1 = 18.4 + shake;
291
+ const wrong = t > 7.05 && t < 9.4, right = t >= 9.4;
292
+ const scr = wrong && t > 7.4 ? '#ffe3df' : right ? '#dcf7ec' : '#eef1fa';
293
+ poly(ctx, [P(sx0, 4.9, 1.2), P(sx1, 4.9, 1.2), P(sx1, 4.9, 3.1), P(sx0, 4.9, 3.1)], scr);
294
+ { // glass: inner shade along the bezel + a diagonal sheen band
295
+ const a0 = P(sx0, 4.9, 3.1), a1 = P(sx1, 4.9, 1.2);
296
+ const ig = ctx.createLinearGradient(0, a0[1], 0, P(sx0, 4.9, 1.2)[1]); ig.addColorStop(0, 'rgba(60,50,110,0.10)'); ig.addColorStop(0.18, 'rgba(60,50,110,0)'); ig.addColorStop(1, 'rgba(255,255,255,0.12)');
297
+ poly(ctx, [P(sx0, 4.9, 1.2), P(sx1, 4.9, 1.2), P(sx1, 4.9, 3.1), P(sx0, 4.9, 3.1)], ig);
298
+ const sg = ctx.createLinearGradient(a0[0], a0[1], a1[0], a1[1]);
299
+ sg.addColorStop(0.32, 'rgba(255,255,255,0)'); sg.addColorStop(0.4, 'rgba(255,255,255,0.32)'); sg.addColorStop(0.5, 'rgba(255,255,255,0.08)'); sg.addColorStop(0.56, 'rgba(255,255,255,0)');
300
+ poly(ctx, [P(sx0, 4.9, 1.2), P(sx1, 4.9, 1.2), P(sx1, 4.9, 3.1), P(sx0, 4.9, 3.1)], sg);
301
+ }
302
+ const o = P(sx0, 4.9, 3.1);
303
+ ctx.save(); wallX(ctx, o); ctx.scale(s / 40, s / 40);
304
+ const SW = 3 * 40, SH = 1.9 * 40;
305
+ if (t > 6.9 && t < 7.05) { ctx.fillStyle = '#9aa6d6'; for (let i = 0; i < 3; i++) { ctx.beginPath(); ctx.arc(SW / 2 - 14 + i * 14, SH / 2, 4, 0, 7); ctx.fill(); } }
306
+ if (wrong || right) {
307
+ petFace(ctx, right ? 'cat' : 'dog', 26, SH / 2, 15, right ? '#b9c2ea' : '#f2c39a', INK);
308
+ ctx.font = '800 30px "Inter"'; ctx.fillStyle = INK; ctx.fillText(right ? 'cat' : 'dog?', 46, SH / 2 + 11);
309
+ }
310
+ ctx.restore();
311
+ });
312
+ // accuracy chart: one bar per training round
313
+ const ACCS = [61, 72, 80, 86, 90, 93, 95, 97];
314
+ const roundT = (i) => 10.9 + i * 0.22;
315
+ ACCS.forEach((a, i) => {
316
+ const x = 19.7 + i * 0.72, y = 3.4, hh = (a - 50) / 50 * 4.4 * EO(roundT(i), roundT(i) + 0.35);
317
+ if (hh <= 0.01) return;
318
+ shadows.push([x, y, 0.5, 0.6, hh]);
319
+ add(x + 0.25 + y + 0.3, () => box(ctx, P, x, y, 0, 0.5, 0.6, hh, shade(i === 7 ? '#ff8a7a' : H.mix('#aab6e8', '#8fd9bd', i / 7))));
320
+ });
321
+ add(19.4 + 2, () => box(ctx, P, 19.4, 2.9, 0, 6.2, 0.3, 0.2, shade('#d8d4ea'), false));
322
+ // decorative trees
323
+ for (const [tx, ty] of [[0.7, 7.3], [25.3, 0.8], [25.2, 7.2], [9.3, 0.7]]) {
324
+ shadows.push([tx - 0.3, ty - 0.3, 0.6, 0.6, 1.8]);
325
+ add(tx + ty, () => {
326
+ const b = P(tx, ty), top = P(tx, ty, 1.4); const rr = 0.6 * s;
327
+ ctx.strokeStyle = '#b79a8a'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(b[0], b[1]); ctx.lineTo(top[0], top[1] + 4); ctx.stroke();
328
+ ctx.fillStyle = '#7fc8a8'; ctx.beginPath(); ctx.arc(top[0], top[1] - rr * 0.3, rr, 0, 7); ctx.fill();
329
+ ctx.fillStyle = '#a3dcc0'; ctx.beginPath(); ctx.arc(top[0] - rr * 0.22, top[1] - rr * 0.5, rr * 0.72, 0, 7); ctx.fill();
330
+ });
331
+ }
332
+ // shadows (one layer)
333
+ {
334
+ const L = H.layer(W, H_), lc = L.getContext('2d');
335
+ for (const [x, y, w, d, h] of shadows) {
336
+ const base = [[x, y], [x + w, y], [x + w, y + d], [x, y + d]];
337
+ poly(lc, hull(base.concat(base.map(p => [p[0] + 1.1 * h, p[1] + 0.12 * h]))).map(p => P(p[0], p[1])), '#000');
338
+ }
339
+ lc.globalCompositeOperation = 'destination-in'; poly(lc, [P(0, 0), P(LX, 0), P(LX, LY), P(0, LY)], '#000');
340
+ lc.globalCompositeOperation = 'source-in'; lc.fillStyle = 'rgb(110,96,170)'; lc.fillRect(0, 0, W, H_);
341
+ ctx.globalAlpha = 0.16; ctx.drawImage(L, 0, 0); ctx.globalAlpha = 1;
342
+ }
343
+ objs.sort((a, b) => a.key - b.key);
344
+ for (const o of objs) { ctx.save(); o.fn(); ctx.restore(); }
345
+
346
+ // ---------- screen-space annotation layer: tags that land on things, connectors ----------
347
+ // beat 2: label tags riding on each card
348
+ for (const cd of cards) {
349
+ if (cd.test) {
350
+ const p = P(cd.x + 0.5, 5.5, 1.75);
351
+ tag(ctx, p[0], p[1] - 14, '?', { k: EO(5.9, 6.2), font: '800 26px "Inter"', bg: ACC, col: '#fff' });
352
+ continue;
353
+ }
354
+ const p = P(cd.x + 0.5, 5.5, 1.75);
355
+ const k = EO(2.4 + 0.0, 2.8) * H.clamp((cd.x - 0.2) * 3) * H.clamp((9.4 - cd.x) * 3);
356
+ if (k > 0.05) tag(ctx, p[0], p[1] - 16, cd.kind, { k, font: '700 24px "Inter"', icon: true, iconCol: cd.kind === 'cat' ? '#7f8fd8' : '#f2a46b' });
357
+ }
358
+ if (t > 2.6 && t < 6.4) { const b = P(4.6, 0.4, 4.4); numberTag(ctx, b[0], b[1] - 70, cnt.toLocaleString('en-US'), 'examples', { k: H.ease.back(ph(2.6, 2.95)) * (1 - E(6.0, 6.4)) }); }
359
+ // flow arrow: data -> model
360
+ {
361
+ const a = P(1.2, 6.6, 2.2), b = P(10.2, 6.6, 2.0);
362
+ arrow(ctx, [a, [(a[0] + b[0]) / 2, Math.min(a[1], b[1]) - 0.9 * s], b], E(3.0, 3.9) * (1 - ph(6.2, 6.5)), 'rgba(74,84,136,0.75)', [6, 6], 2);
363
+ if (t > 3.6 && t < 6.4) { const m = P(9.3, 7.0, 3.2); tag(ctx, m[0], m[1] - 0.6 * s, 'into the model', { k: EO(3.6, 3.9), font: '700 24px "Inter"', bg: '#4a5488', col: '#fff' }); }
364
+ }
365
+ // beat 3 loop: Guess -> Check -> Adjust
366
+ if (t > 6.6 && t < 10.6) {
367
+ const fade = 1 - ph(10.3, 10.6);
368
+ ctx.save(); ctx.globalAlpha = fade;
369
+ const mTop = P(12.6, 5.6, 2.75), scrL = P(15.4, 4.9, 2.7), scrTop = P(16.9, 4.7, 3.4), mBack = P(12.4, 5.2, 2.9);
370
+ // Guess
371
+ arrow(ctx, [mTop, [(mTop[0] + scrL[0]) / 2, Math.min(mTop[1], scrL[1]) - 1.4 * s], scrL], E(6.8, 7.15), INK, null, 2.5);
372
+ if (t > 6.85) { const g = [(mTop[0] + scrL[0]) / 2, Math.min(mTop[1], scrL[1]) - 0.75 * s]; tag(ctx, g[0], g[1] - 14, '1 Guess', { k: H.ease.back(ph(6.85, 7.15)), font: '800 26px "Inter"' }); }
373
+ // Check: emphasis ring on the wrong answer
374
+ if (t > 7.4) {
375
+ tag(ctx, scrTop[0] + 0.4 * s, scrTop[1] - 0.6 * s, '2 Check', { k: H.ease.back(ph(7.45, 7.75)), font: '800 26px "Inter"' });
376
+ if (t < 9.4) {
377
+ const c = P(16.9, 4.9, 2.15), pr = EO(7.45, 7.9);
378
+ ctx.save(); ctx.strokeStyle = ACC; ctx.lineWidth = 3; ctx.globalAlpha = fade * (0.6 + 0.4 * Math.sin(t * 10));
379
+ ctx.beginPath(); ctx.ellipse(c[0], c[1], 1.95 * s * (1.4 - 0.4 * pr), 1.25 * s * (1.4 - 0.4 * pr), -0.05, 0, Math.PI * 2 * pr); ctx.stroke(); ctx.restore();
380
+ if (t < 7.6) { ctx.fillStyle = `rgba(255,107,91,${0.25 * (1 - ph(7.4, 7.6))})`; ctx.fillRect(0, 0, W, H_); }
381
+ }
382
+ }
383
+ // Adjust: error signal travels back
384
+ if (t > 8.0) {
385
+ const q = arrow(ctx, [scrTop, [(scrTop[0] + mBack[0]) / 2, scrTop[1] - 2.2 * s], mBack], E(8.0, 8.6), ACC, [7, 5], 2.5);
386
+ if (q && t < 8.9) { const p2 = q(E(8.0, 8.6)); ctx.fillStyle = 'rgba(255,107,91,0.3)'; ctx.beginPath(); ctx.arc(p2[0], p2[1], 10, 0, 7); ctx.fill(); }
387
+ const dl = P(12.45, 7.0, 0.4);
388
+ tag(ctx, dl[0], dl[1] + 0.55 * s, '3 Adjust', { k: H.ease.back(ph(8.5, 8.8)), font: '800 26px "Inter"', bg: ACC, col: '#fff' });
389
+ const lt = P(14.0, 3.2, 4.4);
390
+ tag(ctx, lt[0], lt[1], 'error!', { k: H.ease.back(ph(8.1, 8.35)) * (1 - ph(9.2, 9.4)), font: '700 22px "Inter"', bg: '#ffe3df', col: ACC });
391
+ }
392
+ // truth label under the screen + verdict badge stamped on the screen corner
393
+ if (t > 7.5 && t < 9.4) { const c = P(16.9, 4.9, 0.85); tag(ctx, c[0], c[1] + 16, "answer: cat", { k: H.ease.back(ph(7.6, 7.9)), font: '700 26px "Inter"', bg: INK, col: '#fff' }); }
394
+ if (t > 7.4) {
395
+ const right = t >= 9.4, b = P(18.5 + shake, 4.9, 3.25);
396
+ const kk = right ? H.ease.back(ph(9.45, 9.75)) : H.ease.back(ph(7.4, 7.65));
397
+ ctx.save(); ctx.translate(b[0], b[1]); ctx.scale(kk, kk);
398
+ ctx.fillStyle = right ? MINT : ACC; ctx.beginPath(); ctx.arc(0, 0, 19, 0, 7); ctx.fill();
399
+ ctx.strokeStyle = '#fff'; ctx.lineWidth = 4; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath();
400
+ if (right) { ctx.moveTo(-8, 0); ctx.lineTo(-2, 7); ctx.lineTo(9, -7); } else { ctx.moveTo(-7, -7); ctx.lineTo(7, 7); ctx.moveTo(7, -7); ctx.lineTo(-7, 7); }
401
+ ctx.stroke(); ctx.restore();
402
+ }
403
+ if (t > 9.4) { const c = P(16.9, 4.7, 3.4); tag(ctx, c[0] + 0.4 * s, c[1] - 0.6 * s, 'Guess again ✓', { k: H.ease.back(ph(9.45, 9.75)), font: '800 26px "Inter"', bg: MINT, col: '#fff' }); }
404
+ ctx.restore();
405
+ }
406
+ // beat 4: counting accuracy tag that rides the newest bar
407
+ if (t > 10.8) {
408
+ const i = Math.min(7, Math.max(0, Math.floor((t - 10.9) / 0.22)));
409
+ const cur = Math.round(H.lerp(61, 97, H.ease.inOut(ph(10.9, 12.6))));
410
+ const x = 19.7 + i * 0.72 + 0.25, hh = (ACCS[i] - 50) / 50 * 4.4;
411
+ const p = P(x, 3.7, hh + 0.2);
412
+ const pop = t > 12.6 ? 1 + 0.18 * Math.sin(Math.min(1, (t - 12.6) / 0.35) * Math.PI) : 1;
413
+ ctx.strokeStyle = 'rgba(74,84,136,0.6)'; ctx.lineWidth = 1.3; ctx.beginPath(); ctx.moveTo(p[0], p[1]); ctx.lineTo(p[0], p[1] - 42); ctx.stroke();
414
+ ctx.fillStyle = INK; ctx.beginPath(); ctx.arc(p[0], p[1], 3.5, 0, 7); ctx.fill();
415
+ numberTag(ctx, p[0], p[1] - 100, `${cur}%`, 'Accuracy', { k: H.ease.back(ph(10.85, 11.15)) * pop, bg: cur >= 97 ? ACC : '#ffffff', col: cur >= 97 ? '#fff' : INK, sub: cur >= 97 ? 'rgba(255,255,255,0.85)' : '#7b7aa3' });
416
+ if (t > 12.6) { // sparkle emphasis
417
+ ctx.save(); ctx.strokeStyle = ACC; ctx.lineWidth = 2; ctx.globalAlpha = 1 - ph(12.6, 13.2);
418
+ const rr2 = 80 + 60 * EO(12.6, 13.2); ctx.beginPath(); ctx.arc(p[0], p[1] - 100, rr2, 0, 7); ctx.stroke(); ctx.restore();
419
+ }
420
+ const p0 = P(19.95, 3.7, (61 - 50) / 50 * 4.4 + 0.15);
421
+ if (t < 13.4 && t > 11.0) tag(ctx, p0[0] - 40, p0[1] - 68, 'round 1: 61%', { k: EO(10.9, 11.2) * (1 - ph(13.0, 13.4)), font: '700 22px "Inter"', col: '#7b7aa3' });
422
+ }
423
+
424
+ // ---------- hook: big question that shrinks into the corner header ----------
425
+ {
426
+ const sh = E(1.5, 2.4);
427
+ const a = EO(0.15, 0.7);
428
+ const size = H.lerp(66, 30, sh);
429
+ ctx.save(); ctx.globalAlpha = a;
430
+ if (sh < 1) { ctx.fillStyle = `rgba(251,248,243,${0.7 * (1 - sh)})`; ctx.fillRect(0, 0, W, H_); }
431
+ ctx.font = `800 ${size}px "Inter"`; ctx.fillStyle = INK;
432
+ const w = ctx.measureText('How does AI learn?').width;
433
+ const x = H.lerp(640 - w / 2, 48, sh), y = H.lerp(330, 62, sh) + (1 - a) * 20;
434
+ if (sh > 0) { ctx.save(); ctx.globalAlpha = a * sh; ctx.shadowColor = 'rgba(80,70,140,0.18)'; ctx.shadowBlur = 16; ctx.shadowOffsetY = 4; ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.roundRect(x - 18, y - size * 1.6, w + 36, size * 2.1, 14); ctx.fill(); ctx.restore(); ctx.fillStyle = INK; }
435
+ ctx.fillText('How does AI learn?', x, y);
436
+ ctx.fillStyle = ACC; ctx.beginPath(); ctx.roundRect(x + 2, y - size * 1.32, H.lerp(40, 22, sh), H.lerp(7, 4, sh), 3); ctx.fill();
437
+ ctx.restore();
438
+ }
439
+ // ---------- takeaway ----------
440
+ if (t > 13.0) {
441
+ const k2 = H.ease.back(ph(13.0, 13.5));
442
+ ctx.save(); ctx.translate(640, 640 + (1 - EO(13.0, 13.4)) * 40); ctx.scale(k2, k2);
443
+ ctx.font = '800 44px "Inter"';
444
+ const str = 'More examples → better guesses', w = ctx.measureText(str).width + 80;
445
+ ctx.shadowColor = 'rgba(80,70,140,0.25)'; ctx.shadowBlur = 24; ctx.shadowOffsetY = 8;
446
+ ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.roundRect(-w / 2, -44, w, 88, 44); ctx.fill(); ctx.shadowColor = 'transparent';
447
+ ctx.fillStyle = ACC; ctx.beginPath(); ctx.arc(-w / 2 + 30, 0, 7, 0, 7); ctx.fill();
448
+ ctx.fillStyle = INK; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(str, 12, 1);
449
+ ctx.restore();
450
+ }
451
+ }
452
+
453
+ // ---------- sound: a tidy toy factory (wooden-block pops, a soft belt motor, gentle camera glides) ----------
454
+ function isoSound(k) {
455
+ const D = 14.5;
456
+ const ph = (t, a, b) => H.clamp((t - a) / (b - a));
457
+ const inv = (u) => (u < 0.5 ? Math.cbrt(u / 4) : 1 - Math.cbrt((1 - u) * 2) / 2); // inverse of H.ease.inOut
458
+ // camera keyframes copied from isoExplainer (KF), used to pan sounds to where their object sits on screen
459
+ const KF = [[0, 11, 4, 0.5, 24], [1.6, 11, 4, 0.5, 26], [2.8, 4.6, 3.4, 2.0, 46], [5.9, 5.6, 3.6, 1.9, 46], [6.9, 15.3, 5.0, 1.8, 54], [10.3, 15.3, 5.0, 1.8, 54], [11.1, 22.9, 3.6, 2.9, 68], [12.9, 22.9, 3.6, 2.9, 68], [13.8, 13, 4, 0.8, 27], [20, 13, 4, 0.8, 27]];
460
+ const pan = (x, y, t) => {
461
+ let i = 0; while (i < KF.length - 2 && t > KF[i + 1][0]) i++;
462
+ const [t0, fx0, fy0, , s0] = KF[i], [t1, fx1, fy1, , s1] = KF[i + 1], u = H.ease.inOut(H.clamp((t - t0) / (t1 - t0)));
463
+ const sx = 640 + ((x - H.lerp(fx0, fx1, u)) - (y - H.lerp(fy0, fy1, u))) * C30 * H.lerp(s0, s1, u);
464
+ return H.clamp(sx / 640 - 1, -1, 1) * 0.6;
465
+ };
466
+ // toy pop: a little lacquered-wood block snapping into place (body tock + soft pop), never squeaky
467
+ const toy = (t, f, g, p, minor) => { const o = { pan: p, minor }; k.play('pop', t, { ...o, f, gain: 0.8 * g }); k.play('tock', t + 0.004, { ...o, f: f * 0.9, gain: 0.35 * g }); };
468
+ // camera glide: a low, smooth air movement that swells with the eased camera speed (no swoosh)
469
+ const glide = (t0, t1, g = 1) => { const d = t1 - t0; k.noise(t0, { dur: d + 0.15, color: 'pink', filter: 'bandpass', f: [[0, 220], [d * 0.5, 520], [d, 240]], q: 0.7, gain: 0.045 * g, a: d * 0.5, verb: 0.1 }); };
470
+ // counter ticks: one per visible change, at most ~12/s, spaced by how fast the value is moving
471
+ const counter = (a, b, val, f, g, p, minGap = 1 / 12, avoid = []) => {
472
+ let last = -1, lastV = val(a);
473
+ for (let t = a; t <= b + 1e-6; t += 1 / 120) {
474
+ const v = val(t); if (v === lastV) continue;
475
+ const speed = Math.abs(val(Math.min(b, t + 0.05)) - val(Math.max(a, t - 0.05)));
476
+ const top = Math.abs(val(b) - val(a)) * 0.1 / (b - a) * 2.2; // roughly the peak change per 0.1 s
477
+ const gap = H.clamp(minGap / Math.max(0.2, Math.min(1, speed / top)), minGap, 0.4);
478
+ if (t - last >= gap && !avoid.some((x) => Math.abs(x - t) < 0.07)) { k.play('tick', t, { f, gain: g, pan: p, minor: true }); k.tone(t, { f: f / 8, dur: 0.03, gain: 0.05 * g, pan: p, minor: true }); last = t; }
479
+ lastV = v;
480
+ }
481
+ };
482
+
483
+ k.bed('room', 0, D, { gain: 0.25 });
484
+ k.music('pad', 0, D, { bpm: 100, gain: 0.32, chords: [['C4', 'E4', 'G4', 'B4'], ['F3', 'A3', 'C4', 'E4'], ['A3', 'C4', 'E4', 'G4'], ['F3', 'A3', 'C4', 'E4'], ['G3', 'B3', 'D4', 'F4'], ['C4', 'E4', 'G4', 'C5']] });
485
+
486
+ // ---- hook: the question fades up (EO 0.15-0.7), then slides small into the corner header (1.5-2.4)
487
+ toy(0.2, 520, 0.7, 0);
488
+ k.noise(1.5, { dur: 0.9, color: 'pink', filter: 'lowpass', f: [[0, 700], [0.9, 300]], gain: 0.06, a: 0.35, minor: true });
489
+ glide(1.6, 2.8); // camera dives onto the training-data shelves
490
+
491
+ // ---- beat 2: data. belt motor + slat ticks while the cards ride, cards clack onto the belt once a second
492
+ k.tone(2.4, { f: 72, type: 'sawtooth', dur: 4.3, gain: 0.025, a: 0.3, hold: 3.6, filter: 'lowpass', f2: 260, minor: true }); // belt motor
493
+ for (let t = 2.4 + (0.5 - ((2.4 * 2.2) % 0.5)) / 2.2; t < 6.5; t += 0.5 / 2.2) if (t > 2.9 && (t - 2.36) % 1 > 0.1) k.play('tock', t, { f: 330, gain: 0.22, pan: pan(5, 5.5, t), minor: true }); // a belt slat passes
494
+ for (let j = 0; j < 5; j++) { const t = Math.max(2.4, 2.4 + j - 0.1 / 2.6); if (t < 6.5) toy(t, 430, 0.55, pan(0.6, 5.5, t), true); } // card lands on the belt
495
+ k.play('pop', 2.45, { f: 560, gain: 0.5, pan: pan(1, 5.5, 2.45), minor: true }); // cat/dog tags pop on
496
+ toy(2.66, 600, 0.9, pan(4.6, 0.4, 2.66)); // examples counter pops up over the billboard
497
+ counter(2.85, 5.66, (t) => Math.floor(1e6 * Math.pow(H.ease.out(ph(t, 2.8, 5.7)), 1.6)), 3400, 0.9, pan(4.6, 0.4, 4), 1 / 8);
498
+ // dashed flow arrow draws (E 3.0-3.9): soft dabs, then the "into the model" tag
499
+ for (let i = 1; i <= 5; i++) { const u = i / 5, t = 3.0 + 0.9 * inv(u); k.noise(t, { dur: 0.025, filter: 'bandpass', f: 1800, q: 1.4, gain: 0.12, a: 0.002, pan: pan(1.2 + 9 * u, 6.6, t), minor: true }); }
500
+ toy(3.63, 470, 0.8, pan(9.3, 7, 3.63));
501
+ // 1,000,000 lands (EO reaches 1 at 5.7)
502
+ toy(5.7, 640, 1.1, pan(4.6, 0.4, 5.7));
503
+ k.play('ding', 5.7, { f: 'G5', gain: 0.45, pan: pan(4.6, 0.4, 5.7) });
504
+
505
+ // ---- beat 3: test card -> guess -> check -> adjust
506
+ toy(5.93, 380, 0.9, pan(0.9, 5.5, 5.93)); // coral "?" on the test card
507
+ glide(5.9, 6.9);
508
+ k.play('tock', 7.3, { f: 300, gain: 0.35, pan: pan(10.25, 7, 7.3), minor: true }); // test card slides into the intake slot
509
+ k.play('swish', 6.8, { dur: 0.32, gain: 0.25, pan: pan(14, 5, 6.95), minor: true }); // Guess arrow arcs over
510
+ toy(6.9, 560, 0.85, pan(14, 5, 6.9)); // "1 Guess" tag
511
+ k.tone(7.05, { f: 'E4', type: 'triangle', dur: 0.18, gain: 0.12, pan: pan(16.9, 4.9, 7.05) }); // "dog?" pops on the monitor
512
+ // CHECK: wrong. red flash, monitor shakes, X badge stamps
513
+ k.play('thud', 7.4, { f: 120, gain: 0.7, pan: pan(16.9, 4.9, 7.4) });
514
+ k.play('error', 7.42, { f: 'A3', gain: 0.85, pan: pan(16.9, 4.9, 7.4) });
515
+ toy(7.64, 440, 0.75, pan(16.9, 4.9, 7.64)); // "answer: cat"
516
+ // ADJUST: error signal travels back along the dashed arc (E 8.0-8.6), then "error!" and "3 Adjust"
517
+ k.tone(8.0, { f: [[0, 520], [0.6, 260]], type: 'triangle', dur: 0.6, gain: 0.06, a: 0.25, pan: pan(14.6, 5, 8.3), minor: true });
518
+ k.noise(8.0, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 900], [0.6, 500]], q: 1.2, gain: 0.07, a: 0.3, pan: pan(14.6, 5, 8.3), minor: true });
519
+ toy(8.15, 680, 0.6, pan(14, 3.2, 8.15), true); // "error!"
520
+ toy(8.55, 500, 0.85, pan(12.45, 7, 8.55)); // "3 Adjust"
521
+ // dials turn (E 8.6-9.3, with a wobble): even ratchet clicks across the turn
522
+ for (let i = 0; i < 5; i++) { k.play('click', 8.66 + i * 0.145, { f: 1100 + (i % 3) * 120, gain: 0.7, pan: pan(12.4, 7, 9), minor: true }); }
523
+ // CORRECT: screen turns to cat (9.4), check badge + "Guess again" pop
524
+ k.tone(9.4, { f: 'C5', type: 'triangle', dur: 0.15, gain: 0.1, pan: pan(16.9, 4.9, 9.4) });
525
+ k.play('success', 9.46, { f: 'C5', gain: 0.8, pan: pan(18.3, 4.9, 9.46) });
526
+ toy(9.48, 620, 0.7, pan(17.3, 4.7, 9.48), true);
527
+
528
+ // ---- beat 4: the camera glides to the chart, bars pop up one round at a time (61 -> 97)
529
+ glide(10.3, 11.1);
530
+ ['C4', 'D4', 'E4', 'G4', 'A4', 'C5', 'D5', 'E5'].forEach((n, i) => { const t = 10.9 + i * 0.22 + 0.03; k.tone(t, { f: [[0, k.hz(n)], [0.08, k.hz(n) * 0.75]], dur: 0.12, gain: 0.16, pan: pan(19.7 + i * 0.72, 3.4, t), minor: i < 7 }); k.play('tock', t, { f: k.hz(n) * 0.8, gain: 0.22, pan: pan(19.7 + i * 0.72, 3.4, t), minor: true }); });
531
+ counter(10.92, 12.56, (t) => Math.round(H.lerp(61, 97, H.ease.inOut(ph(t, 10.9, 12.6)))), 2900, 0.7, pan(23, 3.7, 12), 1 / 12, [0, 1, 2, 3, 4, 5, 6, 7].map((i) => 10.93 + i * 0.22));
532
+ // 97% (the biggest hit): coral flip, the tag swells, a ring expands
533
+ k.play('impact', 12.6, { gain: 0.75, pan: pan(25, 3.7, 12.6) });
534
+ ['C5', 'E5', 'G5', 'C6'].forEach((n, i) => k.play('ding', 12.6 + i * 0.03, { f: n, gain: 0.42 - i * 0.05, pan: (i - 1.5) * 0.2 }));
535
+ k.noise(12.62, { dur: 0.6, filter: 'bandpass', f: [[0, 1200], [0.6, 2400]], q: 2, gain: 0.04, a: 0.05, verb: 0.3, minor: true }); // ring shimmer
536
+ glide(12.9, 13.8, 0.9); // pull back out to the whole floor
537
+ // takeaway pill bounces in (back 13.0-13.5) and resolves
538
+ toy(13.08, 470, 0.9, 0);
539
+ ['G4', 'C5', 'E5'].forEach((n, i) => k.play('ding', 13.3 + i * 0.08, { f: n, gain: 0.35 - i * 0.06, pan: (i - 1) * 0.25 }));
540
+ k.tone(13.3, { f: 'C3', type: 'triangle', dur: 1.2, gain: 0.12, a: 0.02 });
541
+ }
542
+
543
+ registerStyle({
544
+ id: 'isometric',
545
+ name: 'Isometric Pastel',
546
+ category: 'Dimensional',
547
+ description: 'Clean vector isometric illustration: true 30° projection, flat three-tone face shading, soft pastel palette with a single coral accent, tiny people for scale and long soft shadows on a floating tile.',
548
+ motion: 'The floating tile rises into frame, buildings, tanks and racks pop up one by one on springy overshoot, then LEDs blink, fans spin, data cubes stream up and tiny workers stroll during a slow camera push.',
549
+ duration: 5,
550
+ fonts: ['800 46px "Inter"', '400 18px "Inter"', '600 13px "Inter"', '800 20px "Inter"'],
551
+ frameT: 4.6,
552
+ readsPixels: false,
553
+ explainerDuration: 14.5,
554
+ explainerNote: 'One long isometric factory floor (data shelves → conveyor → glass-top neural machine → monitor → bar chart) that the camera glides along beat to beat, with white label tags pinned to objects, curved dashed connectors, a counting billboard and an accuracy tag riding the newest bar.',
555
+ explainer: isoExplainer,
556
+ explainerSound: isoSound,
557
+ draw(ctx, W, H_, t) {
558
+ const R = H.rng(11);
559
+ const s = 35;
560
+ const bob = Math.sin(t * 1.2) * 4;
561
+ const ph = (a, b) => H.clamp((t - a) / (b - a));
562
+ const rise = H.ease.out(ph(0, 1.1));
563
+ const P = makeIso(745, 118 + bob + (1 - rise) * 110, s);
564
+ const TILE = 12, TH = 0.9;
565
+
566
+ // ---------- background ----------
567
+ const bg = ctx.createLinearGradient(0, 0, W, H_);
568
+ bg.addColorStop(0, '#f6efe8'); bg.addColorStop(0.55, '#efeaf4'); bg.addColorStop(1, '#e4e3f3');
569
+ ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
570
+ // faint dot grid
571
+ ctx.fillStyle = 'rgba(120,110,170,0.10)';
572
+ for (let y = 18; y < H_; y += 28) for (let x = 18 + ((y / 28) % 2) * 14; x < W; x += 28) ctx.fillRect(x, y, 2, 2);
573
+ // soft glow behind tile
574
+ const glow = ctx.createRadialGradient(745, 380, 40, 745, 380, 560);
575
+ glow.addColorStop(0, 'rgba(255,255,255,0.75)'); glow.addColorStop(1, 'rgba(255,255,255,0)');
576
+ ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H_);
577
+ // slow camera push on the world (title stays put)
578
+ ctx.save();
579
+ { const z = 0.955 + 0.045 * H.ease.inOut(H.clamp(t / 5)); ctx.translate(745, 380); ctx.scale(z, z); ctx.translate(-745, -380); }
580
+ ctx.globalAlpha = ph(0, 0.45);
581
+
582
+ // ground shadow of floating tile (long, soft, to the lower right)
583
+ {
584
+ const L = H.layer(W, H_), lc = L.getContext('2d');
585
+ const G = makeIso(745 + 60, 118 + 130 - bob * 0.6, s);
586
+ lc.filter = 'blur(16px)';
587
+ poly(lc, [G(0, 0, -TH), G(TILE, 0, -TH), G(TILE + 3, TILE, -TH), G(3, TILE, -TH)].map(p => p), 'rgba(90,80,150,1)');
588
+ ctx.globalAlpha = 0.16 * rise; ctx.drawImage(L, 0, 0); ctx.globalAlpha = ph(0, 0.45);
589
+ }
590
+
591
+ // ---------- floating tile slab ----------
592
+ const slab = shade('#cfc6ea');
593
+ // under-layer (soil) slightly inset
594
+ box(ctx, P, 0.35, 0.35, -TH - 0.7, TILE - 0.7, TILE - 0.7, 0.7, shade('#b9addf'), false);
595
+ box(ctx, P, 0, 0, -TH, TILE, TILE, TH, slab, false);
596
+ // floor top
597
+ poly(ctx, [P(0, 0, 0), P(TILE, 0, 0), P(TILE, TILE, 0), P(0, TILE, 0)], '#fbf8f3');
598
+ // paving grid
599
+ ctx.strokeStyle = 'rgba(170,160,205,0.22)'; ctx.lineWidth = 1;
600
+ ctx.beginPath();
601
+ for (let i = 1; i < TILE; i++) {
602
+ let a = P(i, 0, 0), b = P(i, TILE, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]);
603
+ a = P(0, i, 0); b = P(TILE, i, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]);
604
+ }
605
+ ctx.stroke();
606
+ // grass patch (left corner)
607
+ poly(ctx, [P(0.3, 7.3, 0), P(2.9, 7.3, 0), P(2.9, 11.7, 0), P(0.3, 11.7, 0)], '#d5efe1');
608
+ poly(ctx, [P(9.4, 9.2, 0), P(11.7, 9.2, 0), P(11.7, 11.7, 0), P(9.4, 11.7, 0)], '#d5efe1');
609
+ // walkway stripes
610
+ ctx.strokeStyle = 'rgba(255,200,120,0.9)'; ctx.lineWidth = 2; ctx.setLineDash([7, 7]);
611
+ ctx.beginPath(); { const a = P(3.2, 7.55, 0), b = P(9.0, 7.55, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
612
+ { const a = P(3.2, 11.3, 0), b = P(9.0, 11.3, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
613
+ ctx.stroke(); ctx.setLineDash([]);
614
+ // painted floor marking "DC-01"
615
+ {
616
+ const o = P(8.9, 7.0, 0);
617
+ ctx.save(); ctx.transform(C30, 0.5, -C30, 0.5, o[0], o[1]);
618
+ ctx.font = '800 30px "Inter"'; ctx.fillStyle = 'rgba(160,150,205,0.45)'; ctx.fillText('DC-01', 0, 0);
619
+ ctx.restore();
620
+ }
621
+ // slab front edge highlight
622
+ ctx.strokeStyle = 'rgba(255,255,255,0.95)'; ctx.lineWidth = 1.5;
623
+ ctx.beginPath(); { const a = P(0, TILE, 0), b = P(TILE, TILE, 0), c = P(TILE, 0, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(c[0], c[1]); } ctx.stroke();
624
+
625
+ ctx.globalAlpha = 1;
626
+ // ---------- scene objects ----------
627
+ const objs = [];
628
+ const shadowBoxes = []; // [x,y,w,d,h]
629
+ const add = (key, fn, at, base) => objs.push({ key, fn, at, base });
630
+
631
+ // main data hall (back)
632
+ const hall = { x: 0.7, y: 0.7, w: 5.2, d: 3.6, h: 3.0 };
633
+ shadowBoxes.push([hall.x, hall.y, hall.w, hall.d, hall.h, 0.75]);
634
+ add(hall.x + hall.w / 2 + hall.y + hall.d / 2, () => {
635
+ const c = shade('#dbe3f6');
636
+ box(ctx, P, hall.x, hall.y, 0, hall.w, hall.d, hall.h, c);
637
+ // window band on left (front) face
638
+ const y = hall.y + hall.d;
639
+ for (let i = 0; i < 6; i++) {
640
+ const x0 = hall.x + 0.3 + i * 0.8;
641
+ poly(ctx, [P(x0, y, 1.1), P(x0 + 0.6, y, 1.1), P(x0 + 0.6, y, 2.4), P(x0, y, 2.4)], '#a9b8e6');
642
+ poly(ctx, [P(x0, y, 1.9), P(x0 + 0.25, y, 2.4), P(x0 + 0.4, y, 2.4), P(x0 + 0.08, y, 1.75)], 'rgba(255,255,255,0.55)');
643
+ }
644
+ // door
645
+ poly(ctx, [P(hall.x + 4.75, y, 0), P(hall.x + 5.05, y, 0), P(hall.x + 5.05, y, 0.9), P(hall.x + 4.75, y, 0.9)], '#8e9bd0');
646
+ // vents on right face
647
+ const x = hall.x + hall.w;
648
+ for (let i = 0; i < 4; i++) {
649
+ const y0 = hall.y + 0.35 + i * 0.8;
650
+ for (let k = 0; k < 4; k++) {
651
+ const zz = 1.0 + k * 0.32;
652
+ poly(ctx, [P(x, y0, zz), P(x, y0 + 0.55, zz), P(x, y0 + 0.55, zz + 0.12), P(x, y0, zz + 0.12)], 'rgba(110,105,170,0.35)');
653
+ }
654
+ }
655
+ // signage strip
656
+ poly(ctx, [P(hall.x + 0.3, y, 2.55), P(hall.x + 2.6, y, 2.55), P(hall.x + 2.6, y, 2.82), P(hall.x + 0.3, y, 2.82)], '#ff6b5b');
657
+ // roof units: AC fans
658
+ for (let i = 0; i < 3; i++) {
659
+ const fx = hall.x + 0.5 + i * 1.6, fy = hall.y + 0.5;
660
+ box(ctx, P, fx, fy, hall.h, 1.2, 1.2, 0.35, shade('#eef1fa'));
661
+ const cpt = P(fx + 0.6, fy + 0.6, hall.h + 0.35);
662
+ ctx.fillStyle = '#9aa6d6'; ctx.beginPath(); ctx.ellipse(cpt[0], cpt[1], 0.45 * 1.2247 * s * 0.85, 0.45 * 0.7071 * s * 0.85, 0, 0, 7); ctx.fill();
663
+ ctx.save(); ctx.translate(cpt[0], cpt[1]); ctx.scale(1, 0.577);
664
+ ctx.rotate(t * 6 + i);
665
+ ctx.fillStyle = '#eef1fa';
666
+ for (let b = 0; b < 3; b++) { ctx.rotate(Math.PI * 2 / 3); ctx.beginPath(); ctx.ellipse(5, 0, 7, 2.6, 0, 0, 7); ctx.fill(); }
667
+ ctx.restore();
668
+ }
669
+ // solar panels on roof front
670
+ for (let i = 0; i < 4; i++) {
671
+ const px = hall.x + 0.4 + i * 1.2, py = hall.y + 2.1;
672
+ poly(ctx, [P(px, py, hall.h + 0.1), P(px + 1.0, py, hall.h + 0.1), P(px + 1.0, py + 1.1, hall.h + 0.45), P(px, py + 1.1, hall.h + 0.45)], '#7d8fd6');
673
+ ctx.strokeStyle = 'rgba(255,255,255,0.45)'; ctx.lineWidth = 0.8; ctx.beginPath();
674
+ for (let k = 1; k < 3; k++) { const a = P(px + k / 3, py, hall.h + 0.1), b = P(px + k / 3, py + 1.1, hall.h + 0.45); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
675
+ const a = P(px, py + 0.55, hall.h + 0.275), b = P(px + 1, py + 0.55, hall.h + 0.275); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]);
676
+ ctx.stroke();
677
+ }
678
+ }, 0.75, P(hall.x + hall.w / 2, hall.y + hall.d / 2, 0));
679
+
680
+ // cooling tanks (right corner)
681
+ const tanks = [[9.2, 1.6, 0.85, 2.7], [10.6, 3.4, 0.75, 2.2]];
682
+ for (const [cx, cy, r, h] of tanks) {
683
+ shadowBoxes.push([cx - r * 0.8, cy - r * 0.8, r * 1.6, r * 1.6, h, 1.0 + (cx - 9.2) * 0.12]);
684
+ add(cx + cy, () => {
685
+ const base = H.hex('#bfe7da');
686
+ const c = cylinder(ctx, P, s, cx, cy, 0, r, h, base);
687
+ // bands
688
+ ctx.strokeStyle = 'rgba(90,150,140,0.35)'; ctx.lineWidth = 1.5;
689
+ for (const zz of [0.5, h - 0.5]) { const p = P(cx, cy, zz); ctx.beginPath(); ctx.ellipse(p[0], p[1], c.rx, c.ry, 0, 0, Math.PI); ctx.stroke(); }
690
+ // top rim + vent
691
+ ctx.fillStyle = H.rgba(H.mix(base, '#5a8f86', 0.35)); ctx.beginPath(); ctx.ellipse(c.tp[0], c.tp[1], c.rx * 0.45, c.ry * 0.45, 0, 0, 7); ctx.fill();
692
+ // ladder
693
+ ctx.strokeStyle = 'rgba(80,120,120,0.5)'; ctx.lineWidth = 1;
694
+ const lx = c.b[0] - c.rx * 0.35;
695
+ ctx.beginPath(); ctx.moveTo(lx, c.b[1] + c.ry * 0.93); ctx.lineTo(lx, c.tp[1] + c.ry * 0.93);
696
+ ctx.moveTo(lx + 6, c.b[1] + c.ry * 0.9); ctx.lineTo(lx + 6, c.tp[1] + c.ry * 0.9);
697
+ for (let yy = c.tp[1] + c.ry + 4; yy < c.b[1] + c.ry; yy += 6) { ctx.moveTo(lx, yy); ctx.lineTo(lx + 6, yy); }
698
+ ctx.stroke();
699
+ }, 1.0 + (cx - 9.2) * 0.12, P(cx, cy, 0));
700
+ }
701
+ // pipes: from tanks to hall (drawn at depth just after hall, before racks)
702
+ add(5.5, () => {
703
+ const pipe = (pts, col, wpx) => {
704
+ const sp = pts.map(p => P(...p));
705
+ ctx.lineCap = 'round'; ctx.lineJoin = 'round';
706
+ ctx.strokeStyle = H.rgba(H.mix(col, SHADE_DARK, 0.3)); ctx.lineWidth = wpx;
707
+ ctx.beginPath(); sp.forEach((p, i) => i ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1])); ctx.stroke();
708
+ ctx.strokeStyle = H.rgba(col); ctx.lineWidth = wpx * 0.6;
709
+ ctx.beginPath(); sp.forEach((p, i) => i ? ctx.lineTo(p[0], p[1] - wpx * 0.15) : ctx.moveTo(p[0], p[1] - wpx * 0.15)); ctx.stroke();
710
+ ctx.strokeStyle = 'rgba(255,255,255,0.7)'; ctx.lineWidth = 1.2;
711
+ ctx.beginPath(); sp.forEach((p, i) => i ? ctx.lineTo(p[0], p[1] - wpx * 0.3) : ctx.moveTo(p[0], p[1] - wpx * 0.3)); ctx.stroke();
712
+ // flanges
713
+ for (let i = 1; i < sp.length - 1; i++) { ctx.fillStyle = H.rgba(H.mix(col, SHADE_DARK, 0.15)); ctx.beginPath(); ctx.arc(sp[i][0], sp[i][1], wpx * 0.62, 0, 7); ctx.fill(); }
714
+ };
715
+ pipe([[8.4, 1.4, 1.6], [6.6, 1.4, 1.6], [6.6, 1.4, 1.6], [hall.x + hall.w, 1.4, 1.6]], H.hex('#c8d2f0'), 9);
716
+ pipe([[9.9, 3.4, 1.0], [7.2, 3.4, 1.0], [7.2, 3.4, 1.0], [hall.x + hall.w, 3.4, 1.0]], H.hex('#ffd2c4'), 7);
717
+ // pipe supports
718
+ for (const xx of [6.9, 7.8]) box(ctx, P, xx, 1.25, 0, 0.18, 0.3, 1.45, shade('#d8d4ea'), false);
719
+ }, 1.45, P(7.5, 2.4, 1.3));
720
+
721
+ // server racks (two rows, open-air cutaway)
722
+ const racks = [];
723
+ const rowY = [5.4, 8.1];
724
+ for (let r = 0; r < 2; r++) for (let i = 0; i < 4; i++) {
725
+ const accent = r === 0 && i === 2;
726
+ racks.push({ x: 3.4 + i * 1.3, y: rowY[r], w: 1.05, d: 0.95, h: accent ? 2.5 : 2.15, accent, id: r * 4 + i });
727
+ }
728
+ for (const rk of racks) {
729
+ shadowBoxes.push([rk.x, rk.y, rk.w, rk.d, rk.h, 1.15 + rk.id * 0.08]);
730
+ add(rk.x + rk.w / 2 + rk.y + rk.d / 2, () => {
731
+ const base = rk.accent ? '#ff8a7a' : '#aab6e8';
732
+ const c = shade(base);
733
+ box(ctx, P, rk.x, rk.y, 0, rk.w, rk.d, rk.h, c);
734
+ // front panel inset on left face (facing viewer)
735
+ const y = rk.y + rk.d;
736
+ const fx0 = rk.x + 0.1, fx1 = rk.x + rk.w - 0.1;
737
+ poly(ctx, [P(fx0, y, 0.12), P(fx1, y, 0.12), P(fx1, y, rk.h - 0.12), P(fx0, y, rk.h - 0.12)], rk.accent ? '#7a3a52' : '#4a5488');
738
+ const units = rk.accent ? 7 : 6;
739
+ const uh = (rk.h - 0.3) / units;
740
+ const lr = H.rng(100 + rk.id);
741
+ for (let u = 0; u < units; u++) {
742
+ const z0 = 0.17 + u * uh;
743
+ poly(ctx, [P(fx0 + 0.05, y, z0), P(fx1 - 0.05, y, z0), P(fx1 - 0.05, y, z0 + uh * 0.72), P(fx0 + 0.05, y, z0 + uh * 0.72)], rk.accent ? '#93465f' : '#5d68a0');
744
+ // slats
745
+ ctx.strokeStyle = 'rgba(255,255,255,0.12)'; ctx.lineWidth = 0.8; ctx.beginPath();
746
+ for (let k = 0; k < 4; k++) { const a = P(fx0 + 0.33 + k * 0.1, y, z0 + 0.06), b = P(fx0 + 0.33 + k * 0.1, y, z0 + uh * 0.66); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
747
+ ctx.stroke();
748
+ // LEDs
749
+ for (let k = 0; k < 2; k++) {
750
+ const ph = lr() * 10, rate = lr.range(2, 6);
751
+ const on = Math.sin(t * rate + ph) > -0.2;
752
+ const lp = P(fx0 + 0.13 + k * 0.1, y, z0 + uh * 0.36);
753
+ const col = rk.accent ? '#ffe28a' : (lr() < 0.2 ? '#ffb38a' : '#8ff0c8');
754
+ if (on) {
755
+ ctx.fillStyle = H.rgba(col, 0.35); ctx.beginPath(); ctx.arc(lp[0], lp[1], 3.6, 0, 7); ctx.fill();
756
+ ctx.fillStyle = col;
757
+ } else ctx.fillStyle = 'rgba(30,30,60,0.6)';
758
+ ctx.beginPath(); ctx.arc(lp[0], lp[1], 1.5, 0, 7); ctx.fill();
759
+ }
760
+ }
761
+ // top label strip on the right face
762
+ poly(ctx, [P(rk.x + rk.w, rk.y + 0.15, rk.h - 0.35), P(rk.x + rk.w, rk.y + rk.d - 0.15, rk.h - 0.35), P(rk.x + rk.w, rk.y + rk.d - 0.15, rk.h - 0.2), P(rk.x + rk.w, rk.y + 0.15, rk.h - 0.2)], 'rgba(255,255,255,0.35)');
763
+ if (rk.accent) {
764
+ // glow halo on top
765
+ const tp = P(rk.x + rk.w / 2, rk.y + rk.d / 2, rk.h);
766
+ const g = ctx.createRadialGradient(tp[0], tp[1], 2, tp[0], tp[1], 60);
767
+ g.addColorStop(0, 'rgba(255,120,100,0.35)'); g.addColorStop(1, 'rgba(255,120,100,0)');
768
+ ctx.fillStyle = g; ctx.beginPath(); ctx.arc(tp[0], tp[1], 60, 0, 7); ctx.fill();
769
+ }
770
+ }, 1.15 + rk.id * 0.08, P(rk.x + rk.w / 2, rk.y + rk.d / 2, 0));
771
+ }
772
+ // overhead cable tray along each row (drawn right after the row, before the next row)
773
+ for (let r = 0; r < 2; r++) {
774
+ const ry = rowY[r] + 0.3, z = 2.62, x0 = 3.25, x1 = 8.6;
775
+ const lastKey = racks[r * 4 + 3].x + 0.53 + rowY[r] + 0.48;
776
+ add(lastKey + 0.01, () => {
777
+ box(ctx, P, x0, ry, z, x1 - x0, 0.35, 0.1, shade('#d9d6ee'));
778
+ // cables lying in the tray
779
+ const cols = ['#4a5488', '#ff6b5b', '#7fc8a8'];
780
+ cols.forEach((c, k) => {
781
+ const a = P(x0 + 0.05, ry + 0.09 + k * 0.09, z + 0.12), b = P(x1 - 0.05, ry + 0.09 + k * 0.09, z + 0.12);
782
+ ctx.strokeStyle = c; ctx.lineWidth = 1.6; ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
783
+ });
784
+ // drops into racks
785
+ for (let i = 0; i < 4; i++) {
786
+ const rk = racks[r * 4 + i];
787
+ const a = P(rk.x + 0.5, ry + 0.2, z), b = P(rk.x + 0.5, ry + 0.2, rk.h);
788
+ ctx.strokeStyle = i === 2 && r === 0 ? '#ff6b5b' : '#4a5488'; ctx.lineWidth = 1.6;
789
+ ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.bezierCurveTo(a[0] + 3, a[1] + 4, b[0] - 3, b[1] - 4, b[0], b[1]); ctx.stroke();
790
+ }
791
+ }, 1.95 + r * 0.12, P(5.9, ry, 2.62));
792
+ }
793
+ // ground cable run from rack row to hall (a channel on the floor)
794
+ add(4.0, () => {
795
+ ctx.strokeStyle = '#6b74ad'; ctx.lineWidth = 2.2; ctx.lineCap = 'round';
796
+ for (let k = 0; k < 3; k++) {
797
+ const pts = [P(3.0 + k * 0.15, 5.9, 0.02), P(3.0 + k * 0.15, 4.3 + 0.0, 0.02)];
798
+ ctx.strokeStyle = k === 1 ? '#ff6b5b' : '#6b74ad';
799
+ ctx.beginPath(); ctx.moveTo(...pts[0]); ctx.lineTo(...pts[1]); ctx.stroke();
800
+ }
801
+ }, 1.9, P(3.1, 5.1, 0));
802
+
803
+ // trees
804
+ const trees = [[1.0, 8.2, 1.0], [1.9, 9.6, 1.25], [0.9, 10.9, 0.85], [10.0, 10.4, 1.05], [11.1, 11.0, 0.8], [7.4, 0.9, 0.0]];
805
+ for (const [tx, ty, sc] of trees) {
806
+ if (!sc) continue;
807
+ shadowBoxes.push([tx - 0.3 * sc, ty - 0.3 * sc, 0.6 * sc, 0.6 * sc, 2.0 * sc, 0.95 + (tx + ty) * 0.035]);
808
+ add(tx + ty, () => {
809
+ const b = P(tx, ty, 0), top = P(tx, ty, 1.5 * sc);
810
+ ctx.strokeStyle = '#b79a8a'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(b[0], b[1]); ctx.lineTo(top[0], top[1] + 4); ctx.stroke();
811
+ const sway = Math.sin(t * 1.5 + tx) * 1.2;
812
+ const rr = 0.62 * sc * s;
813
+ const cxp = top[0] + sway, cyp = top[1] - rr * 0.3;
814
+ ctx.fillStyle = '#7fc8a8'; ctx.beginPath(); ctx.arc(cxp, cyp, rr, 0, 7); ctx.fill();
815
+ ctx.fillStyle = '#a3dcc0'; ctx.beginPath(); ctx.arc(cxp - rr * 0.22, cyp - rr * 0.2, rr * 0.72, 0, 7); ctx.fill();
816
+ ctx.fillStyle = '#c4ecd6'; ctx.beginPath(); ctx.arc(cxp - rr * 0.4, cyp - rr * 0.42, rr * 0.25, 0, 7); ctx.fill();
817
+ // shading crescent
818
+ ctx.save(); ctx.beginPath(); ctx.arc(cxp, cyp, rr, 0, 7); ctx.clip();
819
+ ctx.fillStyle = 'rgba(80,120,140,0.18)'; ctx.beginPath(); ctx.arc(cxp - rr * 0.6, cyp - rr * 0.6, rr * 1.35, 0, 7); ctx.arc(cxp, cyp, rr * 2, 0, 7, true); ctx.fill('evenodd');
820
+ ctx.restore();
821
+ }, 0.95 + (tx + ty) * 0.035, P(tx, ty, 0));
822
+ }
823
+ // bench + lamp post
824
+ add(2.6 + 7.9, () => { box(ctx, P, 2.3, 7.7, 0, 0.35, 1.0, 0.35, shade('#e7d6c9')); }, 1.5, P(2.5, 8.2, 0));
825
+
826
+ // people
827
+ const person = (x, y, shirt, face, carry) => {
828
+ const p = P(x, y, 0);
829
+ const hh = 0.95 * s;
830
+ ctx.fillStyle = 'rgba(80,70,140,0.18)'; ctx.beginPath(); ctx.ellipse(p[0] + 4, p[1] + 1, 6, 2.5, 0, 0, 7); ctx.fill();
831
+ ctx.strokeStyle = '#4a4f78'; ctx.lineWidth = 2.6; ctx.lineCap = 'round';
832
+ ctx.beginPath(); ctx.moveTo(p[0] - 2, p[1]); ctx.lineTo(p[0] - 1.5, p[1] - hh * 0.42); ctx.moveTo(p[0] + 2, p[1] - 1); ctx.lineTo(p[0] + 1.5, p[1] - hh * 0.42); ctx.stroke();
833
+ ctx.fillStyle = shirt;
834
+ ctx.beginPath(); ctx.roundRect(p[0] - 4.5, p[1] - hh * 0.8, 9, hh * 0.42, 3.5); ctx.fill();
835
+ ctx.fillStyle = 'rgba(255,255,255,0.35)'; ctx.fillRect(p[0] - 4.5, p[1] - hh * 0.78, 3, hh * 0.36);
836
+ ctx.fillStyle = '#f1c6a8'; ctx.beginPath(); ctx.arc(p[0], p[1] - hh * 0.9, 3.8, 0, 7); ctx.fill();
837
+ ctx.fillStyle = face; ctx.beginPath(); ctx.arc(p[0] + 0.6, p[1] - hh * 0.93, 3.9, Math.PI * 1.05, Math.PI * 2.05); ctx.fill();
838
+ if (carry) { ctx.fillStyle = '#e9ecf8'; ctx.save(); ctx.translate(p[0] + 5, p[1] - hh * 0.6); ctx.transform(1, -0.5, 0, 1, 0, 0); ctx.fillRect(0, 0, 8, 5); ctx.fillStyle = '#ff6b5b'; ctx.fillRect(1, 1, 6, 3); ctx.restore(); }
839
+ };
840
+ const walk = H.clamp((t - 2.7) * 0.32);
841
+ add(7.0 + 7.3, () => person(6.6, 7.2, '#ff6b5b', '#3d3550', true), 2.25, P(6.6, 7.2, 0));
842
+ add(3.0 + 7.4, () => person(3.0, 7.6, '#7f8fd8', '#6b4a3a', false), 2.4, P(3.0, 7.6, 0));
843
+ add(9.6 + 5.1, () => person(9.6, 5.1, '#9fd8c2', '#2f2a40', false), 2.55, P(9.6, 5.1, 0));
844
+ add(4.2 + 10.9 + walk * 3, () => person(4.2 + walk * 3, 10.9, '#f5b78f', '#3d3550', false), 2.7, P(4.2 + walk * 3, 10.9, 0));
845
+
846
+ // rising data cubes from the GPU rack
847
+ const acc = racks[2];
848
+ add(99, () => {
849
+ for (let i = 0; i < 6; i++) {
850
+ const ph = (Math.max(0, t - 2.4) * 0.45 + i / 6) % 1;
851
+ if (t < 2.4 + i * 0.37) continue;
852
+ const z = acc.h + 0.3 + ph * 3.2;
853
+ const ox = acc.x + acc.w / 2 - 0.15 + Math.sin(i * 2.1) * 0.35, oy = acc.y + acc.d / 2 - 0.15 + Math.cos(i * 1.7) * 0.35;
854
+ const a = Math.sin(ph * Math.PI);
855
+ ctx.globalAlpha = a;
856
+ const sz = 0.26 * (1 - ph * 0.4);
857
+ box(ctx, P, ox, oy, z, sz, sz, sz, shade(i % 2 ? '#ff8a7a' : '#ffc2b4'), false);
858
+ ctx.globalAlpha = 1;
859
+ }
860
+ });
861
+
862
+ // ---------- shadows on floor (single layer, no doubled overlaps) ----------
863
+ {
864
+ const L = H.layer(W, H_), lc = L.getContext('2d');
865
+ const sv = [1.15, 0.12];
866
+ lc.fillStyle = '#000';
867
+ for (const [x, y, w, d, h0, at] of shadowBoxes) {
868
+ const kk = H.ease.out(ph(at, at + 0.5)); if (kk <= 0) continue;
869
+ const h = h0 * kk;
870
+ const base = [[x, y], [x + w, y], [x + w, y + d], [x, y + d]];
871
+ const pts = base.concat(base.map(p => [p[0] + sv[0] * h, p[1] + sv[1] * h]));
872
+ const hl = hull(pts).map(p => P(p[0], p[1], 0));
873
+ poly(lc, hl, '#000');
874
+ }
875
+ lc.globalCompositeOperation = 'destination-in';
876
+ poly(lc, [P(0, 0, 0), P(TILE, 0, 0), P(TILE, TILE, 0), P(0, TILE, 0)], '#000');
877
+ lc.globalCompositeOperation = 'source-in';
878
+ lc.fillStyle = 'rgb(110,96,170)'; lc.fillRect(0, 0, W, H_);
879
+ ctx.globalAlpha = 0.17; ctx.drawImage(L, 0, 0); ctx.globalAlpha = 1;
880
+ }
881
+
882
+ // ---------- painter's sort + draw ----------
883
+ objs.sort((a, b) => a.key - b.key);
884
+ for (const o of objs) {
885
+ const k = o.at == null ? 1 : H.ease.back(ph(o.at, o.at + 0.5));
886
+ if (k <= 0.001) continue;
887
+ ctx.save();
888
+ if (k !== 1 && o.base) { ctx.translate(o.base[0], o.base[1]); ctx.scale(k, k); ctx.translate(-o.base[0], -o.base[1]); }
889
+ o.fn(); ctx.restore();
890
+ }
891
+
892
+ // small floating debris cubes under the island
893
+ for (const [x, y, z, sz] of [[6.5, 13.4, -2.9, 0.45], [12.6, 12.0, -3.2, 0.35], [13.2, 6.5, -2.4, 0.42]]) {
894
+ box(ctx, P, x, y, z + Math.sin(t + x) * 0.15 - (1 - rise) * 2, sz, sz, sz, shade('#cfc6ea'), false);
895
+ }
896
+
897
+ // ---------- satellite mini-tile (lower left) with data link ----------
898
+ {
899
+ const b2 = Math.sin(t * 1.2 + 1.3) * 5;
900
+ const mt = H.ease.back(ph(1.3, 1.95));
901
+ const Q = makeIso(250, 452 + b2 + (1 - mt) * 140, 35);
902
+ ctx.save(); ctx.globalAlpha = ph(1.3, 1.6);
903
+ // its soft shadow
904
+ const L = H.layer(W, H_), lc = L.getContext('2d');
905
+ const G = makeIso(250 + 30, 452 + 70, 35);
906
+ lc.filter = 'blur(12px)';
907
+ poly(lc, [G(0, 0, 0), G(3, 0, 0), G(4, 3, 0), G(1, 3, 0)], 'rgb(90,80,150)');
908
+ ctx.globalAlpha = 0.14 * ph(1.3, 1.9); ctx.drawImage(L, 0, 0); ctx.globalAlpha = ph(1.3, 1.6);
909
+ box(ctx, Q, 0.15, 0.15, -1.2, 2.7, 2.7, 0.5, shade('#b9addf'), false);
910
+ box(ctx, Q, 0, 0, -0.7, 3, 3, 0.7, shade('#cfc6ea'), false);
911
+ poly(ctx, [Q(0, 0, 0), Q(3, 0, 0), Q(3, 3, 0), Q(0, 3, 0)], '#fbf8f3');
912
+ ctx.strokeStyle = 'rgba(255,255,255,0.95)'; ctx.lineWidth = 1.5;
913
+ ctx.beginPath(); { const a = Q(0, 3, 0), b = Q(3, 3, 0), c = Q(3, 0, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(c[0], c[1]); } ctx.stroke();
914
+ // shadow of dish base
915
+ poly(ctx, [Q(1.0, 1.0, 0), Q(2.6, 1.1, 0), Q(2.6, 2.0, 0), Q(1.0, 2.0, 0)], 'rgba(110,96,170,0.16)');
916
+ box(ctx, Q, 1.0, 1.0, 0, 1.0, 1.0, 0.55, shade('#dbe3f6'));
917
+ { const tb = Q(2.4, 0.55, 0), tt = Q(2.4, 0.55, 0.9); const rr = 11;
918
+ poly(ctx, [Q(2.25, 0.4, 0), Q(2.95, 0.45, 0), Q(2.95, 0.75, 0), Q(2.25, 0.7, 0)], 'rgba(110,96,170,0.14)');
919
+ ctx.strokeStyle = '#b79a8a'; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.moveTo(tb[0], tb[1]); ctx.lineTo(tt[0], tt[1]); ctx.stroke();
920
+ ctx.fillStyle = '#7fc8a8'; ctx.beginPath(); ctx.arc(tt[0], tt[1] - 4, rr, 0, 7); ctx.fill();
921
+ ctx.fillStyle = '#a3dcc0'; ctx.beginPath(); ctx.arc(tt[0] - 2.5, tt[1] - 6.5, rr * 0.72, 0, 7); ctx.fill(); }
922
+ const pole = Q(1.5, 1.5, 0.55), poleTop = Q(1.5, 1.5, 1.4);
923
+ ctx.strokeStyle = '#8e9bd0'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(pole[0], pole[1]); ctx.lineTo(poleTop[0], poleTop[1]); ctx.stroke();
924
+ // dish (tilted toward the main island)
925
+ ctx.save(); ctx.translate(poleTop[0] + 4, poleTop[1] - 8); ctx.rotate(0.6);
926
+ const dg = ctx.createLinearGradient(-30, -30, 30, 30);
927
+ dg.addColorStop(0, '#ffffff'); dg.addColorStop(1, '#c9cfee');
928
+ ctx.fillStyle = '#a9b3e0'; ctx.beginPath(); ctx.ellipse(2, 3, 34, 15, 0, 0, 7); ctx.fill();
929
+ ctx.fillStyle = dg; ctx.beginPath(); ctx.ellipse(0, 0, 34, 15, 0, 0, 7); ctx.fill();
930
+ ctx.strokeStyle = 'rgba(140,150,200,0.5)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.ellipse(0, 0, 20, 8.5, 0, 0, 7); ctx.stroke();
931
+ ctx.strokeStyle = '#8e9bd0'; ctx.lineWidth = 1.6; ctx.beginPath(); ctx.moveTo(-20, 4); ctx.lineTo(0, -26); ctx.lineTo(20, 4); ctx.stroke();
932
+ ctx.fillStyle = '#ff6b5b'; ctx.beginPath(); ctx.arc(0, -27, 3.5, 0, 7); ctx.fill();
933
+ ctx.restore();
934
+ // dashed data arc to the hall roof
935
+ const from = [poleTop[0] + 4 + Math.sin(0.6) * 27, poleTop[1] - 8 - Math.cos(0.6) * 27];
936
+ const to = P(3.3, 5.75, 2.75);
937
+ const cx = (from[0] + to[0]) / 2 - 50, cy = (from[1] + to[1]) / 2 - 110;
938
+ ctx.restore();
939
+ const arcP = H.ease.inOut(ph(2.1, 2.9));
940
+ const qb = (u) => [(1 - u) * (1 - u) * from[0] + 2 * u * (1 - u) * cx + u * u * to[0], (1 - u) * (1 - u) * from[1] + 2 * u * (1 - u) * cy + u * u * to[1]];
941
+ ctx.strokeStyle = 'rgba(255,107,91,0.7)'; ctx.lineWidth = 1.6; ctx.setLineDash([4, 6]); ctx.lineDashOffset = -t * 30;
942
+ ctx.beginPath(); ctx.moveTo(from[0], from[1]); for (let k = 1; k <= 40; k++) { const q = qb(arcP * k / 40); ctx.lineTo(q[0], q[1]); } if (arcP > 0) ctx.stroke(); ctx.setLineDash([]);
943
+ for (let i = 0; i < 3; i++) {
944
+ if (t < 2.9 + i * 0.5) continue;
945
+ const u = ((t - 2.9 - i * 0.5) * 0.3 + i / 3) % 1;
946
+ const x = (1 - u) * (1 - u) * from[0] + 2 * u * (1 - u) * cx + u * u * to[0];
947
+ const y = (1 - u) * (1 - u) * from[1] + 2 * u * (1 - u) * cy + u * u * to[1];
948
+ ctx.fillStyle = 'rgba(255,107,91,0.25)'; ctx.beginPath(); ctx.arc(x, y, 7, 0, 7); ctx.fill();
949
+ ctx.fillStyle = '#ff6b5b'; ctx.beginPath(); ctx.arc(x, y, 3, 0, 7); ctx.fill();
950
+ }
951
+ }
952
+
953
+ // ---------- label tag ----------
954
+ {
955
+ const anchor = P(acc.x + acc.w / 2, acc.y + acc.d / 2, acc.h + 0.1);
956
+ const tx = anchor[0] + 120, ty = anchor[1] - 150;
957
+ const lp = H.ease.out(ph(2.75, 3.15)), tp = H.ease.back(ph(3.05, 3.45));
958
+ ctx.save(); if (lp <= 0) ctx.globalAlpha = 0;
959
+ ctx.strokeStyle = '#4a4f78'; ctx.lineWidth = 1.3;
960
+ const l1 = anchor[1] - (ty + 30), l2 = tx - 4 - anchor[0], dl = (l1 + l2) * lp;
961
+ ctx.beginPath(); ctx.moveTo(anchor[0], anchor[1]); ctx.lineTo(anchor[0], anchor[1] - Math.min(dl, l1)); if (dl > l1) ctx.lineTo(anchor[0] + (dl - l1), ty + 30); ctx.stroke();
962
+ ctx.fillStyle = '#4a4f78'; ctx.beginPath(); ctx.arc(anchor[0], anchor[1], 3.5, 0, 7); ctx.fill();
963
+ ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(anchor[0], anchor[1], 1.5, 0, 7); ctx.fill();
964
+ ctx.font = '600 13px "Inter"';
965
+ const label = 'GPU CLUSTER', sub = '1.2 PFLOPS · 74°F';
966
+ const wbox = Math.max(ctx.measureText(label).width, 0) + 54;
967
+ ctx.globalAlpha = tp > 0 ? Math.min(1, ph(3.05, 3.25)) : 0;
968
+ ctx.translate(tx, ty + 31); ctx.scale(Math.max(0.001, tp), Math.max(0.001, tp)); ctx.translate(-tx, -(ty + 31));
969
+ ctx.save(); ctx.shadowColor = 'rgba(80,70,140,0.18)'; ctx.shadowBlur = 18; ctx.shadowOffsetY = 6;
970
+ ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.roundRect(tx, ty + 6, wbox + 40, 50, 12); ctx.fill(); ctx.restore();
971
+ ctx.fillStyle = '#ff6b5b'; ctx.beginPath(); ctx.roundRect(tx + 12, ty + 18, 26, 26, 7); ctx.fill();
972
+ // tiny chip glyph
973
+ ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.6; ctx.strokeRect(tx + 19, ty + 25, 12, 12);
974
+ ctx.beginPath(); for (let k = 0; k < 3; k++) { ctx.moveTo(tx + 22 + k * 3, ty + 22); ctx.lineTo(tx + 22 + k * 3, ty + 25); ctx.moveTo(tx + 22 + k * 3, ty + 37); ctx.lineTo(tx + 22 + k * 3, ty + 40); } ctx.stroke();
975
+ ctx.fillStyle = '#2d3058'; ctx.fillText(label, tx + 48, ty + 29);
976
+ ctx.font = '400 12px "Inter"'; ctx.fillStyle = '#8a88ad'; ctx.fillText(sub, tx + 48, ty + 46);
977
+ ctx.restore();
978
+ }
979
+ ctx.restore(); // end camera push
980
+
981
+ // ---------- title ----------
982
+ const bw = 34 * H.ease.out(ph(0.25, 0.7));
983
+ if (bw > 0.5) { ctx.fillStyle = '#ff6b5b'; ctx.beginPath(); ctx.roundRect(72, 92, bw, 6, 3); ctx.fill(); }
984
+ const t1 = H.ease.out(ph(0.4, 0.95)), t2 = H.ease.out(ph(0.65, 1.2));
985
+ ctx.save(); ctx.globalAlpha = t1; ctx.font = '800 46px "Inter"'; ctx.fillStyle = '#2d3058';
986
+ ctx.fillText('Where AI lives', 70, 152 + (1 - t1) * 18); ctx.restore();
987
+ ctx.save(); ctx.globalAlpha = t2; ctx.font = '400 18px "Inter"'; ctx.fillStyle = '#7b7aa3';
988
+ ctx.fillText('Inside a modern data center', 72, 186 + (1 - t2) * 12); ctx.restore();
989
+ },
990
+ });
991
+ }