@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,687 @@
1
+ {
2
+ // ---------- holographic foil trading card ----------
3
+ const hsl = (h, s, l) => {
4
+ h = ((h % 1) + 1) % 1;
5
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
6
+ const f = (t) => { t = (t + 1) % 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < 0.5 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; };
7
+ return [f(h + 1 / 3), f(h), f(h - 1 / 3)];
8
+ };
9
+
10
+ // holo palette LUT: the clean magenta -> violet -> blue -> cyan -> mint -> gold -> peach -> pink cycle real
11
+ // diffraction foil shows (instead of a plain HSL wheel, whose yellow-green region mixes to mud with the silver)
12
+ const HOLO = (() => {
13
+ const st = [[0, 255, 70, 205], [0.13, 150, 100, 255], [0.27, 70, 150, 255], [0.41, 50, 235, 255], [0.53, 110, 255, 200],
14
+ [0.65, 255, 228, 100], [0.79, 255, 150, 90], [0.9, 255, 95, 160], [1, 255, 70, 205]];
15
+ const lut = new Float32Array(256 * 3);
16
+ for (let i = 0; i < 256; i++) { const u = i / 256; let k = 0; while (st[k + 1][0] < u) k++;
17
+ const a = st[k], b = st[k + 1], f = (u - a[0]) / (b[0] - a[0]), g = f * f * (3 - 2 * f);
18
+ for (let j = 0; j < 3; j++) lut[i * 3 + j] = (a[j + 1] + (b[j + 1] - a[j + 1]) * g) / 255; }
19
+ return lut;
20
+ })();
21
+ // gradient noise whose lattice wraps every P cells, so a tile baked with it repeats with no seam
22
+ function makePNoise(seed) {
23
+ const r = H.rng(seed), perm = [...Array(256).keys()];
24
+ for (let i = 255; i > 0; i--) { const j = Math.floor(r() * (i + 1)); [perm[i], perm[j]] = [perm[j], perm[i]]; }
25
+ const GX = [1, 0.7071, 0, -0.7071, -1, -0.7071, 0, 0.7071], GY = [0, 0.7071, 1, 0.7071, 0, -0.7071, -1, -0.7071];
26
+ const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
27
+ const g = (X, Y, dx, dy) => { const hh = perm[(perm[X & 255] + Y) & 255] & 7; return GX[hh] * dx + GY[hh] * dy; };
28
+ return (x, y, P) => {
29
+ const xi = Math.floor(x), yi = Math.floor(y), fx = x - xi, fy = y - yi;
30
+ const X0 = ((xi % P) + P) % P, Y0 = ((yi % P) + P) % P, X1 = (X0 + 1) % P, Y1 = (Y0 + 1) % P;
31
+ const u = fade(fx), v = fade(fy);
32
+ const a = g(X0, Y0, fx, fy) + (g(X1, Y0, fx - 1, fy) - g(X0, Y0, fx, fy)) * u;
33
+ const b = g(X0, Y1, fx, fy - 1) + (g(X1, Y1, fx - 1, fy - 1) - g(X0, Y1, fx, fy - 1)) * u;
34
+ return (a + (b - a) * v) * 1.1;
35
+ };
36
+ }
37
+
38
+ // per-pixel foil shading. emboss: canvas (R = height). rays: optional sunburst centre within a rect.
39
+ // o.wrap = S: bake a seamless S x S tile (periodic noise, periodic sheen band and hue, wrapped normals)
40
+ const TAU = Math.PI * 2;
41
+ function shadeFoil(w, h, emboss, o) {
42
+ const nz = H.makeNoise(o.seed), R = H.rng(o.seed + 1), S = o.wrap || 0, PN = S ? makePNoise(o.seed + 7) : null;
43
+ const E = o.Ht ? null : emboss.getContext('2d').getImageData(0, 0, w, h).data;
44
+ const Ht = o.Ht || new Float32Array(w * h);
45
+ if (!o.Ht) for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
46
+ let r1, r2;
47
+ if (S) { r1 = 1 - Math.abs(PN(x / S * 5, y / S * 5, 5)); r2 = 1 - Math.abs(PN(x / S * 12 + 7, y / S * 12 + 3, 12)); }
48
+ else { const gx = (x + o.ox) * 0.015, gy = (y + o.oy) * 0.015;
49
+ // "cracked ice": ridged noise gives faceted creases
50
+ r1 = 1 - Math.abs(nz.n2(gx, gy)); r2 = 1 - Math.abs(nz.n2(gx * 2.3 + 7, gy * 2.3 + 3)); }
51
+ let ht = (r1 * r1 * 0.75 + r2 * r2 * 0.35) * o.crinkle;
52
+ ht += E[(y * w + x) * 4] / 255 * o.emboss;
53
+ if (o.rays) {
54
+ const [rx, ry, x0, y0, x1, y1] = o.rays;
55
+ if (x > x0 && x < x1 && y > y0 && y < y1) ht += Math.max(0, Math.sin(Math.atan2(y - ry, x - rx) * 36)) * 0.9;
56
+ }
57
+ Ht[y * w + x] = ht;
58
+ }
59
+ const c = H.layer(w, h), cx = c.getContext('2d'), img = cx.createImageData(w, h), d = img.data;
60
+ const [lx, ly, lz] = o.L, ll = Math.hypot(lx, ly, lz), Lx = lx / ll, Ly = ly / ll, Lz = lz / ll;
61
+ const hx0 = Lx, hy0 = Ly, hz0 = Lz + 1, hl = Math.hypot(hx0, hy0, hz0), Hx = hx0 / hl, Hy = hy0 / hl, Hz = hz0 / hl;
62
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
63
+ const i = y * w + x;
64
+ const xm = x > 0 ? i - 1 : S ? i + w - 1 : i, xp = x < w - 1 ? i + 1 : S ? i - w + 1 : i;
65
+ const ym = y > 0 ? i - w : S ? i + w * (h - 1) : i, yp = y < h - 1 ? i + w : S ? i - w * (h - 1) : i;
66
+ let nx = -(Ht[xp] - Ht[xm]) * 2.2, ny = -(Ht[yp] - Ht[ym]) * 2.2, nzz = 1;
67
+ const nl = Math.hypot(nx, ny, nzz); nx /= nl; ny /= nl; nzz /= nl;
68
+ const diff = 0.55 + 0.45 * Math.max(0, nx * Lx + ny * Ly + nzz * Lz);
69
+ const spec = Math.pow(Math.max(0, nx * Hx + ny * Hy + nzz * Hz), 60);
70
+ // broad mirror sheen band sweeping across as the card tilts; diffraction hue from position + normal + tilt
71
+ let band, hue;
72
+ if (S) { const u = (x + y) / S; let dd = (u - o.band) % 1; dd -= Math.round(dd); band = Math.exp(-(dd * dd) / 0.09);
73
+ hue = o.hue + u + nx * 0.38 + ny * 0.26 + (Math.sin(TAU * (x / S + 0.3)) * Math.cos(TAU * (y / S * 2 + 0.1)) + Math.sin(TAU * (y / S + x / S * 2))) * 0.08; }
74
+ else { const bandPos = (x * 0.62 + y * 0.78) / (w * 0.62 + h * 0.78); band = Math.exp(-Math.pow((bandPos - o.band) / 0.35, 2));
75
+ hue = o.hue + bandPos * 1.6 + nx * 0.38 + ny * 0.26 + nz.n2(x * 0.006, y * 0.006) * 0.15; }
76
+ const hi = (Math.floor((((hue % 1) + 1) % 1) * 256) & 255) * 3, rr = HOLO[hi], gg = HOLO[hi + 1], bb = HOLO[hi + 2];
77
+ const rainbow = 0.45 + 0.55 * band;
78
+ const silver = 0.35 + 0.25 * band + 0.08 * (S ? 0.5 * (Math.sin(TAU * (x / S * 3 + y / S)) + Math.cos(TAU * (y / S * 3 - x / S * 2 + 0.2))) : nz.n2(x * 0.01 + 3, y * 0.01));
79
+ let r = (silver * (1 - rainbow * 0.75) + rr * rainbow * 0.95) * diff + spec * 1.1;
80
+ let g = (silver * (1 - rainbow * 0.75) + gg * rainbow * 0.95) * diff + spec * 1.1;
81
+ let b = (silver * (1 - rainbow * 0.75) + bb * rainbow * 0.95) * diff + spec * 1.15;
82
+ // glitter flecks
83
+ if (R() < 0.0035) { const s2 = 0.6 + 0.6 * Math.max(0, Math.sin(o.t * 4 + i)); const [fr, fg, fb] = hsl(R(), 1, 0.75); r += fr * s2; g += fg * s2; b += fb * s2; }
84
+ const k = i * 4;
85
+ d[k] = r * 255; d[k + 1] = g * 255; d[k + 2] = b * 255; d[k + 3] = 255;
86
+ }
87
+ cx.putImageData(img, 0, 0);
88
+ return { canvas: c, Ht };
89
+ }
90
+
91
+ function robot(x, s) {
92
+ // robot figure in local art-window coords, centre bottom at (0,0); s = scale
93
+ x.save(); x.scale(s, s);
94
+ x.beginPath();
95
+ x.roundRect(-62, -250, 124, 100, 30); // head
96
+ x.moveTo(0, -250); x.lineTo(0, -282); // antenna stem drawn separately
97
+ x.roundRect(-76, -140, 152, 130, 26); // body
98
+ x.roundRect(-42, -10, 30, 22, 6); x.roundRect(12, -10, 30, 22, 6); // feet
99
+ x.restore();
100
+ }
101
+
102
+ // ================= EXPLAINER: told with holo trading cards and die-cut stickers on a desk =================
103
+ let foilTex = null, deskTex = null, raysTex = null;
104
+ const TS = 320;
105
+ // eight seamless cracked-ice tiles; phase k moves the sheen band and hue 1/8 of a cycle, so phase 7 -> 0 loops cleanly
106
+ function getFoilTex() {
107
+ if (foilTex) return foilTex;
108
+ const blank = H.layer(TS, TS); blank.getContext('2d').fillRect(0, 0, TS, TS);
109
+ foilTex = [];
110
+ let Ht = null; // the relief is the same for every phase: compute it once
111
+ for (let k = 0; k < 8; k++) { const r = shadeFoil(TS, TS, blank, { seed: 11, crinkle: 3.4, emboss: 0, hue: k / 8, band: k / 8, L: [-0.6, -0.8, 1.3], t: 0, ox: 0, oy: 0, wrap: TS, Ht });
112
+ Ht = r.Ht; foilTex.push(r.canvas); }
113
+ return foilTex;
114
+ }
115
+ // embossed sunburst relief (grey, 128 = flat) laid over the foil with 'hard-light' for the art windows, like the hero
116
+ function getRays() {
117
+ if (raysTex) return raysTex;
118
+ const S = 320, c = H.layer(S, S), x = c.getContext('2d'), im = x.createImageData(S, S), d = im.data;
119
+ const cx0 = S / 2, cy0 = S * 0.47, HG = new Float32Array((S + 2) * (S + 2));
120
+ for (let y = -1; y <= S; y++) for (let xx = -1; xx <= S; xx++) { const r = Math.hypot(xx - cx0, y - cy0);
121
+ HG[(y + 1) * (S + 2) + xx + 1] = Math.max(0, Math.sin(Math.atan2(y - cy0, xx - cx0) * 36)) * H.smooth(6, 40, r) + 0.0016 * r; }
122
+ const ht = (px, py) => HG[(py + 1) * (S + 2) + px + 1];
123
+ for (let y = 0; y < S; y++) for (let xx = 0; xx < S; xx++) {
124
+ const nx = -(ht(xx + 1, y) - ht(xx - 1, y)) * 2.6, ny = -(ht(xx, y + 1) - ht(xx, y - 1)) * 2.6, nl = Math.hypot(nx, ny, 1);
125
+ const lit = (nx * -0.45 + ny * -0.6 + 0.66) / nl - 0.66, k = (y * S + xx) * 4, v = H.clamp(0.5 + lit * (lit > 0 ? 1.5 : 0.8)) * 255;
126
+ d[k] = d[k + 1] = d[k + 2] = v; d[k + 3] = 255;
127
+ }
128
+ x.putImageData(im, 0, 0); return (raysTex = c);
129
+ }
130
+ // the sunburst relief as a plain alpha overlay (white highlights, dark shadows) for the big cards: cheaper than hard-light
131
+ let raysOv = null;
132
+ function getRaysOv() {
133
+ if (raysOv) return raysOv;
134
+ const src = getRays(), S = src.width, c = H.layer(S, S), x = c.getContext('2d'), sd = src.getContext('2d').getImageData(0, 0, S, S).data, im = x.createImageData(S, S), d = im.data;
135
+ for (let i = 0; i < d.length; i += 4) { const v = (sd[i] - 128) / 128; if (v > 0) { d[i] = d[i + 1] = d[i + 2] = 255; d[i + 3] = v * 170; } else { d[i] = 10; d[i + 1] = 12; d[i + 2] = 30; d[i + 3] = -v * 200; } }
136
+ x.putImageData(im, 0, 0); return (raysOv = c);
137
+ }
138
+ // art-window textures per phase: foil tile + sunburst relief (hard-light) + translucent navy/violet ink, composited once
139
+ const winTex = [];
140
+ function getWin(k) {
141
+ if (winTex[k]) return winTex[k];
142
+ const S = TS, c = H.layer(S, S), x = c.getContext('2d');
143
+ x.drawImage(getFoilTex()[k], 0, 0); x.globalCompositeOperation = 'hard-light'; x.globalAlpha = 0.6; x.drawImage(getRays(), 0, 0);
144
+ x.globalAlpha = 1; x.globalCompositeOperation = 'source-over';
145
+ const tg = x.createLinearGradient(0, 0, 0, S); tg.addColorStop(0, 'rgba(20,30,110,0.55)'); tg.addColorStop(1, 'rgba(70,10,90,0.5)');
146
+ x.fillStyle = tg; x.fillRect(0, 0, S, S);
147
+ return (winTex[k] = c);
148
+ }
149
+ function getDesk(W, Hh) {
150
+ if (deskTex) return deskTex;
151
+ const c = H.layer(W, Hh), x = c.getContext('2d'), R = H.rng(5151);
152
+ const bg = x.createRadialGradient(600, 330, 60, 640, 360, 900); bg.addColorStop(0, '#26282f'); bg.addColorStop(1, '#0a0b0f');
153
+ x.fillStyle = bg; x.fillRect(0, 0, W, Hh);
154
+ // faint rainbow light thrown onto the desk by the foil (as in the still)
155
+ x.save(); x.globalCompositeOperation = 'screen'; x.filter = 'blur(40px)';
156
+ const spill = x.createLinearGradient(820, 40, 1240, 300);
157
+ for (let k = 0; k <= 6; k++) { const hi = (Math.floor(((0.1 + k / 7) % 1) * 256)) * 3; spill.addColorStop(k / 6, `rgba(${HOLO[hi] * 255 | 0},${HOLO[hi + 1] * 255 | 0},${HOLO[hi + 2] * 255 | 0},0.11)`); }
158
+ x.fillStyle = spill; x.beginPath(); x.ellipse(1050, 150, 230, 70, -0.5, 0, 7); x.fill();
159
+ x.beginPath(); x.ellipse(170, 640, 200, 50, -0.4, 0, 7); x.fill();
160
+ x.restore();
161
+ const im = x.getImageData(0, 0, W, Hh), d = im.data;
162
+ for (let i = 0; i < d.length; i += 4) { const n = (R() - 0.5) * 9; d[i] += n; d[i + 1] += n; d[i + 2] += n * 1.1; }
163
+ x.putImageData(im, 0, 0); return (deskTex = c);
164
+ }
165
+ function holoCat(x, cx, cy, s) {
166
+ x.beginPath(); x.moveTo(cx - s * 0.95, cy - s * 0.1); x.lineTo(cx - s * 0.8, cy - s * 1.05); x.lineTo(cx - s * 0.35, cy - s * 0.72);
167
+ x.quadraticCurveTo(cx, cy - s * 0.82, cx + s * 0.35, cy - s * 0.72); x.lineTo(cx + s * 0.8, cy - s * 1.05); x.lineTo(cx + s * 0.95, cy - s * 0.1);
168
+ x.bezierCurveTo(cx + s * 1.0, cy + s * 0.75, cx - s * 1.0, cy + s * 0.75, cx - s * 0.95, cy - s * 0.1); x.closePath();
169
+ }
170
+ function holoDog(x, cx, cy, s) {
171
+ x.beginPath(); x.ellipse(cx, cy, s * 0.72, s * 0.86, 0, 0, 7);
172
+ x.moveTo(cx - s * 0.55, cy - s * 0.6); x.ellipse(cx - s * 0.82, cy - s * 0.05, s * 0.26, s * 0.62, 0.25, 0, 7);
173
+ x.moveTo(cx + s * 1.08, cy - s * 0.05); x.ellipse(cx + s * 0.82, cy - s * 0.05, s * 0.26, s * 0.62, -0.25, 0, 7);
174
+ }
175
+ const patCache = new WeakMap(), faceCache = new Map();
176
+
177
+ function holoExplainer(ctx, W, Hh, t) {
178
+ const sm = H.smooth, E = H.ease, tex = getFoilTex();
179
+ ctx.drawImage(getDesk(W, Hh), 0, 0);
180
+ const INK = 'rgba(14,18,40,0.86)', INK_BIG = 'rgba(14,18,40,0.88)';
181
+ let pats = patCache.get(ctx); if (!pats) { pats = tex.map((tc) => ctx.createPattern(tc, 'repeat')); patCache.set(ctx, pats); }
182
+ // paint with seamless foil; the phase crossfades between neighbouring tiles so the rainbow drifts smoothly
183
+ const foilPaint = (c, phase, paint) => {
184
+ const p = (((phase % 1) + 1) % 1) * 8, k = Math.floor(p) % 8, f = p - Math.floor(p);
185
+ c.fillStyle = c.strokeStyle = pats[k]; paint();
186
+ if (f > 0.03) { const a0 = c.globalAlpha; c.globalAlpha = a0 * f; c.fillStyle = c.strokeStyle = pats[(k + 1) % 8]; paint(); c.globalAlpha = a0; }
187
+ };
188
+ const foilFill = (c, x, y, w, h, phase) => foilPaint(c, phase, () => c.fillRect(x, y, w, h));
189
+ const emboss = (c, s, x, y, font, phase, align = 'center') => {
190
+ c.font = font; c.textAlign = align; c.textBaseline = 'alphabetic';
191
+ c.fillStyle = 'rgba(0,0,0,0.6)'; c.fillText(s, x + 2, y + 2); c.fillStyle = 'rgba(255,255,255,0.7)'; c.fillText(s, x - 1.2, y - 1.2);
192
+ foilPaint(c, phase, () => c.fillText(s, x, y));
193
+ };
194
+ // sunken art window: foil + embossed sunburst + translucent navy/violet ink, with a bevelled rim
195
+ const artWindow = (c, x, y, w, h, r, ph) => {
196
+ c.save(); c.beginPath(); c.roundRect(x, y, w, h, r); c.clip();
197
+ const p = (((ph % 1) + 1) % 1) * 8, k = Math.floor(p) % 8, f = p - Math.floor(p), s = Math.max(w, h) * 1.15;
198
+ const dx = x + w / 2 - s / 2, dy = y + h * 0.5 - s * 0.47, a0 = c.globalAlpha;
199
+ c.drawImage(getWin(k), dx, dy, s, s); if (f > 0.03) { c.globalAlpha = a0 * f; c.drawImage(getWin((k + 1) % 8), dx, dy, s, s); c.globalAlpha = a0; }
200
+ c.lineWidth = 3; c.strokeStyle = 'rgba(0,0,0,0.55)'; c.beginPath(); c.roundRect(x + 1, y + 1, w + 2, h + 2, r); c.stroke();
201
+ c.restore();
202
+ c.lineWidth = 1.5; c.strokeStyle = 'rgba(255,255,255,0.35)'; c.beginPath(); c.roundRect(x, y, w, h, r); c.stroke();
203
+ };
204
+ // printed ink frame with a knocked-out foil frame line, as on the hero card
205
+ const inkFrame = (c, w, h, ink, ph) => {
206
+ c.fillStyle = ink; c.fillRect(-w / 2, -h / 2, w, h);
207
+ c.lineWidth = 4; foilPaint(c, ph + 0.15, () => { c.beginPath(); c.roundRect(-w / 2 + 8, -h / 2 + 8, w - 16, h - 16, 9); c.stroke(); });
208
+ };
209
+ const shadowRect = (c, w, h, r, lift) => { for (let k = 3; k >= 1; k--) { c.fillStyle = `rgba(0,0,0,${0.16})`; c.beginPath(); c.roundRect(-w / 2 + lift * 0.5 + k * 3, -h / 2 + lift + k * 3, w, h, r + k * 2); c.fill(); } };
210
+ // light sweeping over foil: a diagonal prismatic band (screen) with a hot white core (color-dodge, so it flares on foil, not ink)
211
+ const glint = (c, w, h, g) => { if (g <= 0 || g >= 1) return; const x = -w / 2 + (w + 300) * g - 150;
212
+ const gr = c.createLinearGradient(x - 120, -60, x + 120, 60);
213
+ gr.addColorStop(0, 'rgba(255,70,205,0)'); gr.addColorStop(0.25, 'rgba(255,70,205,0.22)'); gr.addColorStop(0.4, 'rgba(70,170,255,0.3)');
214
+ gr.addColorStop(0.5, 'rgba(255,255,255,0.42)'); gr.addColorStop(0.6, 'rgba(255,228,100,0.3)'); gr.addColorStop(0.75, 'rgba(255,150,90,0.2)'); gr.addColorStop(1, 'rgba(255,150,90,0)');
215
+ c.save(); c.globalCompositeOperation = 'screen'; c.fillStyle = gr; c.fillRect(x - 300 - h * 0.5, -h / 2, 600 + h, h);
216
+ const g2 = c.createLinearGradient(x - 40, -20, x + 40, 20); g2.addColorStop(0, 'rgba(255,255,255,0)'); g2.addColorStop(0.5, 'rgba(255,255,255,0.5)'); g2.addColorStop(1, 'rgba(255,255,255,0)');
217
+ c.globalCompositeOperation = 'color-dodge'; c.fillStyle = g2; c.fillRect(x - 60 - h * 0.5, -h / 2, 120 + h, h); c.restore(); };
218
+ const faceImg = (face, w, h, k) => {
219
+ const key = face.key + '|' + w + '|' + k; let c = faceCache.get(key); if (c) return c;
220
+ c = H.layer(w, h); const x = c.getContext('2d'); x.translate(w / 2, h / 2); x.beginPath(); x.roundRect(-w / 2, -h / 2, w, h, 14); x.clip();
221
+ foilFill(x, -w / 2, -h / 2, w, h, k / 8); face(x, w, h, k / 8); faceCache.set(key, c); return c;
222
+ };
223
+ // a card: position, size, rotation, flip (1 = face, 0 = edge-on), face painter
224
+ const card = (cx, cy, w, h, rot, flip, phase, face, lift = 6, gl = 0) => {
225
+ ctx.save(); ctx.translate(cx, cy); ctx.rotate(rot);
226
+ ctx.scale(Math.max(0.02, Math.abs(flip)), 1);
227
+ shadowRect(ctx, w, h, 14, lift);
228
+ ctx.save(); ctx.beginPath(); ctx.roundRect(-w / 2, -h / 2, w, h, 14); ctx.clip();
229
+ if (flip > 0 && face.key) { // static faces: cached per phase bucket, crossfaded like foilPaint
230
+ const p = (((phase % 1) + 1) % 1) * 8, k = Math.floor(p) % 8, f = p - Math.floor(p);
231
+ ctx.drawImage(faceImg(face, w, h, k), -w / 2, -h / 2);
232
+ if (f > 0.03) { const a0 = ctx.globalAlpha; ctx.globalAlpha = a0 * f; ctx.drawImage(faceImg(face, w, h, (k + 1) % 8), -w / 2, -h / 2); ctx.globalAlpha = a0; }
233
+ } else { foilFill(ctx, -w / 2, -h / 2, w, h, phase); }
234
+ if (flip > 0) { if (!face.key) face(ctx, w, h, phase); } else { ctx.fillStyle = 'rgba(20,30,90,0.75)'; ctx.fillRect(-w / 2, -h / 2, w, h); ctx.strokeStyle = 'rgba(255,255,255,0.4)'; ctx.lineWidth = 3; ctx.beginPath(); ctx.ellipse(0, 0, w * 0.32, h * 0.32, 0.5, 0, 7); ctx.stroke(); }
235
+ glint(ctx, w, h, gl);
236
+ // gloss varnish edge light: brighter top-left rim, darker bottom-right (card thickness)
237
+ ctx.lineWidth = 3; ctx.strokeStyle = 'rgba(0,0,0,0.35)'; ctx.beginPath(); ctx.roundRect(-w / 2 + 1, -h / 2 + 1, w, h, 14); ctx.stroke();
238
+ ctx.restore();
239
+ ctx.strokeStyle = 'rgba(255,255,255,0.4)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.roundRect(-w / 2, -h / 2, w, h, 14); ctx.stroke();
240
+ ctx.restore();
241
+ };
242
+ // die-cut sticker: white border, foil, dark ink text; slapped down with an overshoot
243
+ const sticker = (cx, cy, w, h, rot, slap, phase, draw, shape = 'rect', tint = null) => {
244
+ if (slap <= 0) return; const s = 1 + (1 - E.back(Math.min(1, slap))) * 0.6;
245
+ ctx.save(); ctx.translate(cx, cy); ctx.rotate(rot); ctx.scale(s, s); ctx.globalAlpha = Math.min(1, slap * 3);
246
+ const path = (pad) => { ctx.beginPath(); if (shape === 'circle') ctx.arc(0, 0, w / 2 + pad, 0, 7); else if (shape === 'bubble') { ctx.roundRect(-w / 2 - pad, -h / 2 - pad, w + pad * 2, h + pad * 2, 26 + pad); ctx.moveTo(-w * 0.3, h / 2 + pad - 2); ctx.lineTo(-w * 0.42 - pad, h / 2 + 34 + pad); ctx.lineTo(-w * 0.12, h / 2 + pad - 2); }
247
+ else if (shape === 'arrow') { const hw = h / 2 + pad; ctx.moveTo(-w / 2 - pad, -hw * 0.55); ctx.lineTo(w / 2 - h * 0.7, -hw * 0.55); ctx.lineTo(w / 2 - h * 0.7, -hw * 1.15); ctx.lineTo(w / 2 + pad * 1.6, 0); ctx.lineTo(w / 2 - h * 0.7, hw * 1.15); ctx.lineTo(w / 2 - h * 0.7, hw * 0.55); ctx.lineTo(-w / 2 - pad, hw * 0.55); ctx.closePath(); }
248
+ else ctx.roundRect(-w / 2 - pad, -h / 2 - pad, w + pad * 2, h + pad * 2, 14 + pad); };
249
+ ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.save(); ctx.translate(5, 7); path(8); ctx.fill(); ctx.restore();
250
+ // white vinyl border, lit from the top-left
251
+ const wg = ctx.createLinearGradient(-w / 2, -h / 2, w / 2, h / 2); wg.addColorStop(0, '#ffffff'); wg.addColorStop(1, '#dcd9d2');
252
+ ctx.fillStyle = wg; path(8); ctx.fill();
253
+ ctx.save(); path(0); ctx.clip(); foilFill(ctx, -w / 2 - 40, -h / 2 - 40, w + 80, h + 80, phase); if (tint) { ctx.fillStyle = tint; ctx.fillRect(-w, -h, w * 2, h * 2); }
254
+ ctx.lineWidth = 3; ctx.strokeStyle = 'rgba(0,0,0,0.25)'; ctx.translate(1.5, 1.5); path(0); ctx.stroke(); ctx.restore();
255
+ draw(ctx); ctx.restore();
256
+ };
257
+ const inkText = (c, s, x, y, size, col = '#141838', font = 'Russo One') => { c.font = `400 ${size}px "${font}"`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillStyle = col; c.fillText(s, x, y); };
258
+ // bold ✓ / ✗ drawn as strokes (the glyphs come out hairline-thin): dark emboss shadow, then white
259
+ const mark = (c, ok) => { const path = (o) => { c.beginPath(); if (ok) { c.moveTo(-24 + o, 2 + o); c.lineTo(-7 + o, 20 + o); c.lineTo(26 + o, -20 + o); }
260
+ else { c.moveTo(-20 + o, -20 + o); c.lineTo(20 + o, 20 + o); c.moveTo(20 + o, -20 + o); c.lineTo(-20 + o, 20 + o); } };
261
+ c.lineCap = 'round'; c.lineJoin = 'round'; c.lineWidth = 13; c.strokeStyle = 'rgba(0,0,0,0.45)'; path(2.5); c.stroke(); c.strokeStyle = '#fff'; path(0); c.stroke(); };
262
+ const tilt = t * 0.11;
263
+ const STK = 'rgba(244,240,230,0.45)';
264
+ // foil robot figure with ink line art (visor, chest dial), centre bottom at the current origin
265
+ const robotFig = (c, s, ph) => {
266
+ c.save(); foilPaint(c, ph, () => { robot(c, s); c.fill(); });
267
+ c.strokeStyle = 'rgba(12,14,34,0.95)'; c.lineWidth = 6 * s / 0.62; c.lineJoin = 'round'; robot(c, s); c.stroke();
268
+ c.scale(s, s); c.lineWidth = 6;
269
+ c.beginPath(); c.moveTo(0, -250); c.lineTo(0, -280); c.stroke();
270
+ c.beginPath(); c.arc(0, -290, 11, 0, 7); foilPaint(c, ph + 0.3, () => c.fill()); c.strokeStyle = 'rgba(12,14,34,0.95)'; c.stroke();
271
+ c.beginPath(); c.roundRect(-44, -222, 88, 44, 18); c.fillStyle = 'rgba(12,14,34,0.8)'; c.fill();
272
+ c.beginPath(); c.arc(-20, -200, 9, 0, 7); c.arc(20, -200, 9, 0, 7); foilPaint(c, ph + 0.5, () => c.fill());
273
+ c.beginPath(); c.arc(0, -78, 28, 0, 7); c.strokeStyle = 'rgba(12,14,34,0.9)'; c.lineWidth = 5; c.stroke();
274
+ c.restore();
275
+ };
276
+
277
+ // ---------- faces ----------
278
+ const faceTitle = (c, w, h, ph) => {
279
+ inkFrame(c, w, h, INK, ph);
280
+ artWindow(c, -w / 2 + 24, -h * 0.05, w - 48, h * 0.5, 10, ph + 0.35);
281
+ c.save(); c.translate(0, h * 0.42); robotFig(c, 0.75, ph + 0.5); c.restore();
282
+ emboss(c, 'How does', 0, -h * 0.3, '400 58px "Russo One"', ph); emboss(c, 'AI learn?', 0, -h * 0.15, '400 68px "Russo One"', ph + 0.2);
283
+ };
284
+ const faceExample = (kind) => faceEx[kind];
285
+ const faceEx = {}; ['cat', 'dog'].forEach((kind) => { faceEx[kind] = (c, w, h, ph) => {
286
+ inkFrame(c, w, h, INK, ph);
287
+ artWindow(c, -w / 2 + 12, -h / 2 + 52, w - 24, h * 0.56, 6, ph + 0.15);
288
+ (kind === 'cat' ? holoCat : holoDog)(c, 0, -h / 2 + 52 + h * 0.3, w * 0.24);
289
+ foilPaint(c, ph + 0.4, () => c.fill()); c.strokeStyle = 'rgba(12,14,34,0.95)'; c.lineWidth = 4; c.stroke();
290
+ emboss(c, kind.toUpperCase(), 0, -h / 2 + 42, `400 ${Math.round(w * 0.2)}px "Russo One"`, ph);
291
+ c.fillStyle = 'rgba(236,232,220,0.94)'; c.beginPath(); c.roundRect(-w / 2 + 12, h * 0.2, w - 24, h * 0.24, 5); c.fill();
292
+ inkText(c, kind === 'cat' ? 'whiskers · ears' : 'snout · floppy ears', 0, h * 0.32, Math.round(w * 0.08), '#2a2e50', 'Inter');
293
+ }; faceEx[kind].key = kind; });
294
+ const faceModel = (bars) => (c, w, h, ph) => {
295
+ inkFrame(c, w, h, INK, ph);
296
+ artWindow(c, -w / 2 + 18, -h / 2 + 70, w - 36, h * 0.42, 8, ph + 0.25);
297
+ c.save(); c.translate(0, -h / 2 + 70 + h * 0.4); robotFig(c, 0.62, ph + 0.5); c.restore();
298
+ emboss(c, 'MODEL', 0, -h / 2 + 56, '400 46px "Russo One"', ph);
299
+ c.fillStyle = 'rgba(236,232,220,0.95)'; c.beginPath(); c.roundRect(-w / 2 + 18, h * 0.04, w - 36, h * 0.42, 6); c.fill();
300
+ c.font = '800 28px "Inter"'; const lw = Math.max(...['ears', 'whiskers', 'snout'].map((lb) => c.measureText(lb).width));
301
+ const bx0 = -w / 2 + 30 + lw + 12, bw = w / 2 - 46 - bx0;
302
+ ['ears', 'whiskers', 'snout'].forEach((lb, i) => { const y = h * 0.1 + 20 + i * 44;
303
+ c.font = '800 28px "Inter"'; c.textAlign = 'left'; c.textBaseline = 'middle'; c.fillStyle = '#141838'; c.fillText(lb, -w / 2 + 30, y);
304
+ c.fillStyle = 'rgba(20,24,56,0.15)'; c.fillRect(bx0, y - 9, bw, 18);
305
+ foilPaint(c, ph + i * 0.2, () => c.fillRect(bx0, y - 9, bw * bars[i], 18)); c.strokeStyle = '#141838'; c.lineWidth = 1.5; c.strokeRect(bx0, y - 9, bw * bars[i], 18); });
306
+ c.font = '800 28px "Inter"'; c.textAlign = 'center'; c.fillStyle = 'rgba(20,24,56,0.8)'; c.fillText('weights', 0, h * 0.425);
307
+ };
308
+ // big cards: lighter ink so the foil breathes, with the hero's sunburst embossed faintly behind the text
309
+ const bigFace = (c, w, h, ph, rx) => {
310
+ c.save(); c.globalAlpha = 0.24; const s = w * 1.25; c.drawImage(getRaysOv(), rx - s / 2, -s * 0.47, s, s); c.restore();
311
+ inkFrame(c, w, h, INK_BIG, ph);
312
+ const vg = c.createRadialGradient(rx, 0, 80, rx, 0, w * 0.75); vg.addColorStop(0, 'rgba(14,18,40,0)'); vg.addColorStop(1, 'rgba(14,18,40,0.45)');
313
+ c.fillStyle = vg; c.fillRect(-w / 2, -h / 2, w, h);
314
+ };
315
+ const faceStats = (pct, lp) => (c, w, h, ph) => {
316
+ bigFace(c, w, h, ph, -w * 0.22);
317
+ emboss(c, 'ACCURACY', -w * 0.22, -h * 0.28, '400 52px "Russo One"', ph);
318
+ emboss(c, pct + '%', -w * 0.22, h * 0.16, '400 170px "Russo One"', ph + 0.3);
319
+ const bx = w * 0.05, bw = w * 0.4, by = h * 0.32;
320
+ c.fillStyle = 'rgba(8,10,26,0.55)'; c.beginPath(); c.roundRect(bx - 16, -h * 0.4, bw + 40, h * 0.4 + by + 60, 10); c.fill();
321
+ c.strokeStyle = 'rgba(255,255,255,0.6)'; c.lineWidth = 3; c.beginPath(); c.moveTo(bx, -h * 0.36); c.lineTo(bx, by); c.lineTo(bx + bw, by); c.stroke();
322
+ for (let r = 0; r < 8; r++) { const a = 0.97 - 0.36 * Math.exp(-r / 2.3), g = H.clamp(lp * 8 - r), bh = (a - 0.45) / 0.55 * h * 0.62 * E.out(g);
323
+ if (g <= 0) continue; const x0 = bx + 14 + r * bw / 8, bww = bw / 8 - 10;
324
+ foilPaint(c, ph + r * 0.08, () => c.fillRect(x0, by - bh, bww, bh));
325
+ c.fillStyle = 'rgba(255,255,255,0.35)'; c.fillRect(x0, by - bh, bww, 2.5);
326
+ c.strokeStyle = 'rgba(12,14,34,0.9)'; c.lineWidth = 2; c.strokeRect(x0, by - bh, bww, bh); }
327
+ c.font = '800 28px "Inter"'; c.textAlign = 'center'; c.textBaseline = 'alphabetic'; c.fillStyle = 'rgba(236,232,255,0.92)'; c.fillText('training rounds →', bx + bw / 2, by + 36);
328
+ };
329
+ const faceEnd = (c, w, h, ph) => {
330
+ bigFace(c, w, h, ph, 0);
331
+ emboss(c, 'More examples', 0, -h * 0.16, '400 76px "Russo One"', ph);
332
+ // fat foil arrow (Russo One has no '↓' glyph), embossed like the lettering
333
+ const ay = h * 0.02 - 12, arrow = (ox, oy) => { c.beginPath(); c.moveTo(ox - 10, oy - 38); c.lineTo(ox + 10, oy - 38); c.lineTo(ox + 10, oy + 2);
334
+ c.lineTo(ox + 28, oy + 2); c.lineTo(ox, oy + 34); c.lineTo(ox - 28, oy + 2); c.lineTo(ox - 10, oy + 2); c.closePath(); };
335
+ arrow(2, ay + 2); c.fillStyle = 'rgba(0,0,0,0.6)'; c.fill(); arrow(-1.2, ay - 1.2); c.fillStyle = 'rgba(255,255,255,0.7)'; c.fill();
336
+ arrow(0, ay); foilPaint(c, ph + 0.3, () => c.fill());
337
+ emboss(c, 'better guesses', 0, h * 0.26, '400 76px "Russo One"', ph + 0.5);
338
+ };
339
+
340
+ // ---------- timeline ----------
341
+ // HOOK: title card flips over, then is swept off while its question sticks to the corner
342
+ if (t < 2.4) {
343
+ const fl = E.out(sm(0.1, 0.6, t)), out = E.in(sm(1.75, 2.3, t));
344
+ card(640 - out * 900, 370 - out * 400, 420, 600, -0.04 - out * 0.5 + (1 - fl) * 0.3, fl, tilt + 0.2, faceTitle, 6 + (1 - fl) * 20, sm(0.9, 1.6, t));
345
+ }
346
+ const header = sm(1.95, 2.2, t);
347
+ // DATA: labelled example cards are dealt onto a pile and fed to the model card
348
+ const sweep1 = E.in(sm(5.95, 6.3, t));
349
+ if (t > 2.3 && t < 6.4) {
350
+ for (let k = 0; k < 12; k++) {
351
+ const t0 = 2.45 + k * 0.26, p = E.out(sm(t0, t0 + 0.32, t)); if (p <= 0) continue;
352
+ const px = 330 + Math.sin(k * 2.3) * 14, py = 400 - k * 2 + Math.cos(k * 1.7) * 8;
353
+ card(H.lerp(-200, px, p) - sweep1 * 900, H.lerp(820, py, p), 210, 294, H.lerp(-1.2, Math.sin(k * 3.1) * 0.12, p), 1, tilt + k * 0.07 + 0.1, faceExample(k % 3 === 1 ? 'dog' : 'cat'), 6 + (1 - p) * 30);
354
+ }
355
+ const mp = E.out(sm(2.5, 2.95, t));
356
+ if (t < 6.0) card(H.lerp(1500, 960, mp), 360, 340, 476, 0.03, 1, tilt + 0.45, faceModel([0.3, 0.7, 0.5]), 8);
357
+ sticker(645, 330 - sweep1 * 600, 150, 60, -0.03, sm(3.0, 3.25, t), tilt + 0.3, (c) => inkText(c, 'train', -10, 2, 30), 'arrow', STK);
358
+ const p = sm(2.9, 5.5, t), v = p >= 1 ? 1000000 : Math.round(Math.pow(10, 6 * p));
359
+ sticker(380 - sweep1 * 900, 615, 420, 112, -0.02, sm(2.8, 3.05, t), tilt + 0.6, (c) => { c.fillStyle = 'rgba(14,18,40,0.82)'; c.beginPath(); c.roundRect(-210, -56, 420, 112, 14); c.fill();
360
+ emboss(c, v.toLocaleString('en-US'), -16, 22, '400 66px "Russo One"', tilt + 0.1); c.font = '800 28px "Inter"'; c.fillStyle = '#e8e4ff'; c.textAlign = 'right'; c.fillText('examples', 196, 40); });
361
+ }
362
+ // LOOP: guess -> check -> adjust, with stickers slapped on the desk
363
+ const mpos = E.inOut(sm(6.0, 6.45, t)), flipB = sm(10.3, 10.75, t);
364
+ if (t >= 6.0 && t < 10.75) {
365
+ const adj = E.inOut(sm(8.7, 9.3, t)), bars = [H.lerp(0.3, 0.85, adj), H.lerp(0.7, 0.95, adj), H.lerp(0.5, 0.1, adj)];
366
+ const fl = flipB < 0.5 ? 1 - flipB * 2 : 0;
367
+ const sweep2 = E.in(sm(10.3, 10.6, t));
368
+ const nf = sm(6.5, 6.8, t), dealt = E.out(sm(6.3, 6.5, t));
369
+ card(H.lerp(-150, 175, dealt) - sweep2 * 600, 390, 260, 364, -0.05, nf < 0.5 ? -(1 - nf * 2) : (nf - 0.5) * 2, tilt + 0.1, faceExample('cat'), 8);
370
+ if (fl > 0) card(H.lerp(960, 585, mpos), 375, 370, 518, 0.02, fl, tilt + 0.45, faceModel(bars), 8, sm(9.2, 9.7, t));
371
+ sticker(352 - sweep2 * 600, 215, 150, 58, -0.04, sm(6.85, 7.05, t), tilt + 0.2, (c) => inkText(c, 'Guess', -10, 2, 30), 'arrow', STK);
372
+ const right = t >= 9.5, sw = sweep2 * 900;
373
+ sticker(1010 + sw, 220, 270, 120, 0.04, sm(7.2, 7.4, t), tilt + 0.35, (c) => inkText(c, 'dog?', 0, 4, 66), 'bubble', STK);
374
+ sticker(1010 + sw, 220, 270, 120, -0.03, sm(9.5, 9.7, t), tilt + 0.55, (c) => inkText(c, 'cat', 0, 4, 66, '#0b3d1a'), 'bubble', 'rgba(60,255,120,0.28)');
375
+ sticker(1010 + sw, 92, 160, 50, -0.02, sm(7.75, 7.95, t), tilt + 0.15, (c) => inkText(c, 'Check', 0, 2, 30), 'rect', STK);
376
+ const shake = t > 7.65 && t < 8.0 ? Math.sin(t * 90) * 6 : 0;
377
+ if (!right) sticker(1185 + sw + shake, 205, 100, 100, 0.1, sm(7.65, 7.8, t), tilt, (c) => mark(c, false), 'circle', 'rgba(255,30,30,0.7)');
378
+ else sticker(1185 + sw, 205, 100, 100, 0.1, sm(9.75, 9.9, t), tilt, (c) => mark(c, true), 'circle', 'rgba(30,200,80,0.75)');
379
+ sticker(960 + sw, 470, 220, 62, Math.PI - 0.25, sm(8.2, 8.45, t), tilt + 0.25, (c) => { c.rotate(Math.PI); inkText(c, 'Adjust', 14, 2, 30); }, 'arrow', STK);
380
+ }
381
+ // PROGRESS + TAKEAWAY: the model card flips over to its stats side, then flips once more for the moral
382
+ if (t >= 10.5) {
383
+ const fl = flipB > 0.5 ? (flipB - 0.5) * 2 : 0, fl2 = sm(12.95, 13.4, t);
384
+ const lp = sm(10.9, 12.3, t), pct = Math.round(61 + 36 * E.out(lp));
385
+ const f2 = fl2 < 0.5 ? 1 - fl2 * 2 : (fl2 - 0.5) * 2;
386
+ const face = fl2 < 0.5 ? faceStats(pct, lp) : faceEnd;
387
+ card(640, 370, 1000, 540, -0.015, fl * f2, tilt + 0.3, face, 8, fl2 < 0.5 ? sm(12.35, 12.9, t) : sm(13.6, 14.3, t));
388
+ if (fl2 < 0.5 && t > 12.3) {
389
+ const sp = sm(12.3, 12.9, t);
390
+ for (let k = 0; k < 14; k++) { const a = k / 14 * Math.PI * 2, r = 150 + sp * 120, x = 420 + Math.cos(a) * r * 1.4, y = 400 + Math.sin(a) * r * 0.8, s = 12 * (1 - sp);
391
+ ctx.fillStyle = `rgba(255,255,255,${1 - sp})`; ctx.beginPath(); ctx.moveTo(x, y - s); ctx.lineTo(x + s * 0.25, y); ctx.lineTo(x, y + s); ctx.lineTo(x - s * 0.25, y); ctx.fill(); ctx.beginPath(); ctx.moveTo(x - s, y); ctx.lineTo(x, y + s * 0.25); ctx.lineTo(x + s, y); ctx.lineTo(x, y - s * 0.25); ctx.fill(); }
392
+ }
393
+ }
394
+ // persistent header sticker
395
+ sticker(248, 48, 456, 62, -0.02, header, tilt + 0.7, (c) => inkText(c, 'How does AI learn?', 0, 2, 44), 'rect', STK);
396
+ H.vignette(ctx, W, Hh, 0.45);
397
+ H.grain(ctx, W, Hh, 0.025, 77 + Math.floor(t * 30), true);
398
+ }
399
+
400
+ // Sound: trading cards on a desk. Cards are flicked out and land (thwip + soft card-on-desk tap), flipped (bend snap,
401
+ // a puff of air, land), slid and swept across the desk (felt friction); die-cut stickers are peeled (a short sticky
402
+ // crackle) and slapped down at the frame their overshoot touches the desk; foil glints get a quiet low shimmer and
403
+ // the 97% emphasis a bigger "shing" chord. A quiet room underneath, no music.
404
+ // All times are copied from holoExplainer() above (same deal/flip/sticker windows and E.out/E.in/E.back easing).
405
+ // Voiceover mode (k.vo): the room drops out; dealt-card thwips/taps, counter ticks, peels and glints are minor.
406
+ function holoSound(k) {
407
+ const sm = H.smooth, E = H.ease, D = 15;
408
+ const when = (f, v, a, b) => { for (let t = a; t <= b; t += 0.001) if (f(t) >= v) return t; return b; };
409
+ // sticker scale = 1 + (1 - E.back(slap))*0.6 first reaches 1 (touches the desk) at slap = 1 - c/(c+1) ≈ 0.37
410
+ const touch = (a, b) => a + (1 - 1.70158 / 2.70158) * (b - a);
411
+ const thwip = (t, g = 1, pan = 0, minor = false) => k.noise(t, { dur: 0.06, filter: 'bandpass', f: [[0, 2400], [0.06, 1100]], q: 1.2, gain: 0.2 * g, a: 0.004, pan, minor });
412
+ const tap = (t, g = 1, pan = 0, minor = false) => { k.noise(t, { dur: 0.035, filter: 'bandpass', f: 1000, q: 1, gain: 0.3 * g, a: 0.001, pan, minor }); k.tone(t, { f: [[0, 190], [0.05, 90]], dur: 0.06, gain: 0.16 * g, pan, minor }); };
413
+ const slide = (a, d, g = 1, pan = 0, minor = false) => k.noise(a, { dur: d, color: 'pink', filter: 'bandpass', f: [[0, 900], [d, 1400]], q: 0.8, gain: 0.08 * g, a: d * 0.4, pan, minor });
414
+ // a card flip: bend snap at a, puff of air through the turn, lands at b
415
+ const flip = (a, b, g = 1, pan = 0, minor = false) => { k.noise(a, { dur: 0.04, filter: 'bandpass', f: 1700, q: 1.5, gain: 0.22 * g, a: 0.002, pan, minor });
416
+ k.noise(a + 0.02, { dur: b - a, color: 'pink', filter: 'bandpass', f: [[0, 500], [(b - a) / 2, 1100], [b - a, 500]], q: 0.9, gain: 0.1 * g, a: (b - a) * 0.4, pan, minor }); tap(b, g, pan, minor); };
417
+ // sticker: peeled off the backing just before, slapped down where it touches
418
+ const sticker = (a, b, g = 1, pan = 0, minor = false) => { const ht = touch(a, b), r = H.rng(Math.floor(a * 1000));
419
+ for (let i = 0; i < 4; i++) k.noise(a - 0.14 + i * 0.02 + r() * 0.01, { dur: 0.008, filter: 'bandpass', f: 1300 + r() * 900, q: 1.5, gain: 0.12 * g, a: 0.001, pan, minor: true });
420
+ k.play('slap', ht, { gain: 0.5 * g, pan, minor }); k.noise(ht + 0.01, { dur: 0.12, color: 'brown', filter: 'lowpass', f: 600, gain: 0.12 * g, pan, minor }); return ht; };
421
+ // foil glint sweep: a quiet low shimmer (filtered noise sweep + a soft detuned triangle dyad), no high pings
422
+ const glint = (a, b, g = 1, pan = 0, notes = ['E4', 'B4']) => { const d = b - a;
423
+ k.noise(a, { dur: d, color: 'pink', filter: 'bandpass', f: [[0, 800], [d * 0.5, 2000], [d, 1000]], q: 3, gain: 0.05 * g, a: d * 0.45, pan, minor: true });
424
+ notes.forEach((n, i) => [-6, 6].forEach((det) => k.tone(a + d * 0.2, { f: n, type: 'triangle', detune: det, dur: d * 0.9, gain: 0.018 * g, a: d * 0.35, filter: 'lowpass', f2: 1600, verb: 0.3, pan: pan + (i - 0.5) * 0.3, minor: true }))); };
425
+ const counter = (val, a, b, gap, pan, avoid = []) => { let last = -1, prev = val(a);
426
+ for (let t = a; t <= b; t += 0.002) { const v = val(t);
427
+ if (v !== prev && t - last >= gap && !avoid.some((x) => t > x - 0.03 && t < x + 0.1)) { k.play('tick', t, { f: 2800, gain: 0.9, pan, minor: true }); last = t; }
428
+ prev = v; } };
429
+
430
+ k.bed('room', 0, D, { gain: 0.6 });
431
+
432
+ // ---- HOOK: title card flips face up (E.out(sm(0.1, 0.6)): fast start, lands ~0.45), glint 0.9–1.6
433
+ flip(0.1, 0.45, 0.75, 0);
434
+ glint(0.9, 1.6, 1.0, 0);
435
+ // card swept off E.in(sm(1.75, 2.3)) — accelerating slide; header sticker 1.95–2.2
436
+ slide(1.75, 0.55, 1.4, -0.4);
437
+ sticker(1.95, 2.2, 1.0, -0.7);
438
+
439
+ // ---- DATA: model card slides in E.out(sm(2.5, 2.95)), settled ~2.77
440
+ slide(2.5, 0.3, 1.0, 0.6); tap(2.77, 0.9, 0.6);
441
+ // twelve example cards dealt at t0 = 2.45 + k*0.26, landing ~t0 + 0.19 (E.out over 0.32)
442
+ const lands = [];
443
+ for (let c = 0; c < 12; c++) { const t0 = 2.45 + c * 0.26; thwip(t0, 0.7, -0.7, true); tap(t0 + 0.19, 0.6, -0.4, true); lands.push(t0, t0 + 0.19); }
444
+ const tCount = sticker(2.8, 3.05, 0.9, -0.3); // counter sticker
445
+ const tTrain = sticker(3.0, 3.25, 0.8, 0.0); // "train" arrow
446
+ // foil digits count 10^(6p), p = sm(2.9, 5.5), lock at 1,000,000
447
+ counter((t) => { const p = sm(2.9, 5.5, t); return p >= 1 ? 1e6 : Math.round(Math.pow(10, 6 * p)); }, 3.1, 5.45, 0.09, -0.3, [...lands, tCount, tTrain]);
448
+ k.play('tock', 5.5, { f: 520, gain: 0.5, pan: -0.3 }); glint(5.5, 6.0, 0.6, -0.3, ['G4', 'D5']);
449
+
450
+ // ---- 5.95–6.3 everything swept off the desk (E.in), model card slides left E.inOut(6.0–6.45)
451
+ slide(5.95, 0.38, 1.8, -0.3); k.noise(5.98, { dur: 0.32, color: 'pink', filter: 'bandpass', f: [[0, 600], [0.32, 1800]], q: 0.7, gain: 0.12, a: 0.25, pan: -0.5 });
452
+ slide(6.02, 0.43, 0.8, 0.3); tap(6.45, 0.6, 0.1, true);
453
+ // cat card dealt E.out(sm(6.3, 6.5)) (lands ~6.42), flipped sm(6.5, 6.8)
454
+ thwip(6.3, 0.9, -0.6); flip(6.5, 6.8, 0.8, -0.5);
455
+
456
+ // ---- LOOP
457
+ sticker(6.85, 7.05, 0.8, -0.3); // Guess arrow
458
+ sticker(7.2, 7.4, 1.1, 0.5); // "dog?" bubble (the guess)
459
+ // WRONG: red ✗ slapped sm(7.65, 7.8) and shaken 7.65–8.0 (sin(t*90): ~14 Hz rattle on the desk)
460
+ const tx = sticker(7.65, 7.8, 1.6, 0.8);
461
+ k.play('thud', tx, { f: 120, gain: 0.6, pan: 0.8 });
462
+ k.noise(tx, { dur: 8.0 - tx, filter: 'bandpass', f: 600, q: 1.2, gain: 0.2, a: 0.01, hold: (8.0 - tx) * 0.6, pan: 0.8 });
463
+ for (let x = tx; x < 7.98; x += 1 / 14.3) tap(x, 0.45, 0.8);
464
+ k.tone(tx + 0.02, { f: 'Bb2', type: 'sawtooth', dur: 0.35, gain: 0.06, a: 0.01, hold: 0.2, filter: 'lowpass', f2: 600, pan: 0.8 });
465
+ sticker(7.75, 7.95, 0.6, 0.5, true); // "Check"
466
+ sticker(8.2, 8.45, 0.8, 0.4); // Adjust arrow
467
+ // weights: stat bars slide to new lengths E.inOut(sm(8.7, 9.3)), then the model card glints 9.2–9.7
468
+ k.noise(8.7, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 700], [0.3, 1500], [0.6, 800]], q: 1.5, gain: 0.07, a: 0.3, pan: -0.1 });
469
+ k.tone(8.7, { f: [[0, 220], [0.6, 330]], type: 'triangle', dur: 0.6, gain: 0.04, a: 0.3, pan: -0.1 });
470
+ glint(9.2, 9.7, 0.7, -0.1);
471
+ // RIGHT: "cat" bubble slapped over the old one sm(9.5, 9.7), ✓ sm(9.75, 9.9)
472
+ sticker(9.5, 9.7, 1.1, 0.5);
473
+ const tok = sticker(9.75, 9.9, 1.0, 0.8);
474
+ k.play('success', tok + 0.02, { f: 'G4', gain: 0.7, pan: 0.6 });
475
+
476
+ // ---- 10.3: everything swept right/off E.in(sm(10.3, 10.6)); model card flips to its stats side sm(10.3, 10.75)
477
+ slide(10.3, 0.32, 1.6, 0.6);
478
+ flip(10.32, 10.75, 1.2, 0);
479
+ // stats: eight foil bars rise one by one (bar r starts at lp = r/8, lp = sm(10.9, 12.3)); % counts 61 -> 97
480
+ const lp = (t) => sm(10.9, 12.3, t), bars = [];
481
+ for (let r = 0; r < 8; r++) { const tb = when(lp, r / 8 + 1e-4, 10.9, 12.3); bars.push(tb); k.play('tock', tb, { f: 330 + r * 22, gain: 0.3, pan: 0.2 + r * 0.06, minor: true }); }
482
+ const pct = (t) => Math.round(61 + 36 * E.out(lp(t)));
483
+ const t97 = when(pct, 97, 10.9, 12.3);
484
+ counter(pct, 10.92, t97 - 0.04, 0.085, -0.3, bars);
485
+ k.play('tock', t97, { f: 560, gain: 0.55, pan: -0.3 });
486
+ // THE HIT 12.3: foil stars burst out (12.3–12.9) and a glint sweeps the card (12.35–12.9): a big low "shing"
487
+ k.play('thud', 12.3, { f: 90, gain: 1.1 });
488
+ k.noise(12.3, { dur: 0.7, color: 'pink', filter: 'bandpass', f: [[0, 700], [0.25, 2300], [0.7, 900]], q: 1.6, gain: 0.22, a: 0.02, verb: 0.4 });
489
+ ['C4', 'G4', 'C5', 'E5'].forEach((n, i) => [-8, 8].forEach((det) => k.tone(12.3, { f: n, type: 'triangle', detune: det, dur: 1.0, gain: 0.045, a: 0.01, hold: 0.15, filter: 'lowpass', f2: 2200, verb: 0.4, pan: (i - 1.5) * 0.3 })));
490
+
491
+ // ---- 12.95–13.4: final flip to the takeaway face; glint 13.6–14.3 as the resolve
492
+ flip(12.95, 13.4, 1.1, 0);
493
+ ['F3', 'C4', 'A4', 'E5'].forEach((n, i) => k.tone(13.55 + i * 0.04, { f: n, type: 'triangle', dur: 1.4, gain: 0.05 - i * 0.006, a: 0.08, hold: 0.4, filter: 'lowpass', f2: 1800, verb: 0.4, pan: (i - 1.5) * 0.25 }));
494
+ glint(13.6, 14.3, 1.1, 0, ['C4', 'G4']);
495
+ }
496
+
497
+ registerStyle({
498
+ id: 'holographic-foil',
499
+ name: 'Holographic Foil Card',
500
+ category: 'Light & Imaging',
501
+ description: 'A holo-rare trading card and die-cut sticker: cracked-ice foil whose rainbow diffraction shifts with angle and surface normals, embossed robot, sunburst and lettering catching specular glints, printed ink sitting on top, glitter flecks and rainbow spill on the desk.',
502
+ motion: 'The card is dealt in and settles onto the desk, the sticker slaps down and holo confetti scatters, then the card slowly tilts toward the light so the rainbow band and specular glints sweep across the cracked-ice foil and embossed lettering.',
503
+ fonts: ['400 54px "Russo One"', '800 15px "Inter"', '600 13px "Inter"', '400 30px "Russo One"', '800 24px "Inter"', '600 18px "Inter"'],
504
+ frameT: 4.6,
505
+ duration: 5,
506
+ readsPixels: true,
507
+ explainerDuration: 15,
508
+ explainerNote: 'Told with holo trading cards on a desk: examples are dealt as CAT/DOG cards onto a pile, arrows and answers are die-cut foil stickers slapped down with an overshoot, the model card\u2019s stat bars are its weights, scene changes are card flips to the stats side and back, and numbers are foil-embossed digits with a glint sweep for emphasis.',
509
+ explainer(ctx, W, Hh, t) { holoExplainer(ctx, W, Hh, t); },
510
+ explainerSound(k) { holoSound(k); },
511
+ draw(ctx, W, Hh, t) {
512
+ const R = H.rng(5150), nz = H.makeNoise(515);
513
+ const tilt = H.lerp(-0.9, 0.984, H.ease.inOut(H.smooth(0.5, 4.2, t))) + 0.015 * Math.sin(t * 2.2) * H.smooth(4.0, 4.6, t);
514
+ const deal = H.ease.back(H.smooth(0, 0.85, t)), land = H.smooth(0.45, 1.0, t);
515
+ const stP = H.smooth(0.85, 1.25, t), stLand = H.ease.out(stP);
516
+
517
+ // ---------- desk ----------
518
+ const bg = ctx.createRadialGradient(560, 330, 60, 640, 360, 900);
519
+ bg.addColorStop(0, '#24262e'); bg.addColorStop(1, '#0a0b0f');
520
+ ctx.fillStyle = bg; ctx.fillRect(0, 0, W, Hh);
521
+ // fine matte texture
522
+ const tex = ctx.getImageData(0, 0, W, Hh), td = tex.data;
523
+ for (let i = 0; i < td.length; i += 4) { const n = (R() - 0.5) * 9; td[i] += n; td[i + 1] += n; td[i + 2] += n * 1.1; }
524
+ ctx.putImageData(tex, 0, 0);
525
+
526
+ // ---------- card ----------
527
+ const CW = 420, CH = 588;
528
+ const emb = H.layer(CW, CH), ex = emb.getContext('2d');
529
+ ex.fillStyle = '#000'; ex.fillRect(0, 0, CW, CH);
530
+ ex.fillStyle = '#fff'; ex.strokeStyle = '#fff';
531
+ // raised frame ridge
532
+ ex.lineWidth = 5; ex.beginPath(); ex.roundRect(14, 14, CW - 28, CH - 28, 14); ex.stroke();
533
+ ex.lineWidth = 3; ex.beginPath(); ex.roundRect(26, 78, CW - 52, 280, 8); ex.stroke();
534
+ // name lettering (embossed)
535
+ ex.font = '400 46px "Russo One"'; ex.textBaseline = 'alphabetic'; ex.fillText('DREAMBOT', 30, 62);
536
+ ex.font = '400 30px "Russo One"'; ex.textAlign = 'right'; ex.fillText('120', CW - 54, 60); ex.textAlign = 'left';
537
+ // robot in art window (embossed)
538
+ ex.save(); ex.translate(CW / 2, 350); ex.scale(0.9, 0.9);
539
+ ex.fillStyle = 'rgb(200,200,200)'; robot(ex, 1); ex.fill();
540
+ ex.fillStyle = '#fff'; ex.beginPath(); ex.arc(0, -290, 12, 0, 7); ex.fill();
541
+ ex.lineWidth = 6; ex.beginPath(); ex.moveTo(0, -250); ex.lineTo(0, -282); ex.stroke();
542
+ ex.lineCap = 'round'; ex.lineWidth = 22;
543
+ ex.beginPath(); ex.moveTo(-76, -120); ex.quadraticCurveTo(-120, -150, -128, -200); ex.stroke(); // waving arm
544
+ ex.beginPath(); ex.moveTo(76, -110); ex.quadraticCurveTo(112, -80, 118, -40); ex.stroke();
545
+ ex.fillStyle = '#000'; ex.beginPath(); ex.roundRect(-44, -222, 88, 44, 18); ex.fill(); // visor recessed
546
+ ex.fillStyle = '#fff'; ex.beginPath(); ex.arc(-20, -200, 10, 0, 7); ex.arc(20, -200, 10, 0, 7); ex.fill(); // eyes raised
547
+ ex.fillStyle = '#000'; ex.beginPath(); ex.arc(0, -78, 30, 0, 7); ex.fill();
548
+ ex.fillStyle = '#fff'; ex.beginPath(); // heart on chest
549
+ ex.moveTo(0, -62); ex.bezierCurveTo(-30, -82, -18, -104, 0, -92); ex.bezierCurveTo(18, -104, 30, -82, 0, -62); ex.fill();
550
+ ex.restore();
551
+ // energy symbols
552
+ [[60, 392], [84, 392]].forEach(([x, y]) => { ex.beginPath(); ex.arc(x, y, 9, 0, 7); ex.fill(); });
553
+ const embB = H.layer(CW, CH), ebx = embB.getContext('2d'); ebx.filter = 'blur(2.2px)'; ebx.drawImage(emb, 0, 0);
554
+
555
+ const band = 0.6365 + (tilt - 0.984) * 0.45;
556
+ const foilCard = shadeFoil(CW, CH, embB, { seed: 11, crinkle: 3.6, emboss: 7, hue: 0.6976 + (tilt - 0.984) * 0.3, band, L: [-0.6 + tilt * 0.3, -0.8, 1.3], t, ox: 0, oy: 0, rays: [CW / 2, 200, 28, 80, CW - 28, 356] });
557
+
558
+ // printed ink on top of the foil
559
+ const card = H.layer(CW, CH), cc = card.getContext('2d');
560
+ cc.drawImage(foilCard.canvas, 0, 0);
561
+ // dark ink frame with windows (foil shows through windows and knock-out lettering)
562
+ const ink = H.layer(CW, CH), ik = ink.getContext('2d');
563
+ ik.fillStyle = 'rgba(14,18,40,0.86)'; ik.fillRect(0, 0, CW, CH);
564
+ ik.globalCompositeOperation = 'destination-out';
565
+ ik.fillStyle = '#000';
566
+ ik.beginPath(); ik.roundRect(28, 80, CW - 56, 276, 7); ik.fill(); // art window
567
+ ik.font = '400 46px "Russo One"'; ik.fillText('DREAMBOT', 30, 62);
568
+ ik.font = '400 30px "Russo One"'; ik.textAlign = 'right'; ik.fillText('120', CW - 54, 60); ik.textAlign = 'left';
569
+ ik.lineWidth = 5; ik.strokeStyle = '#000'; ik.beginPath(); ik.roundRect(14, 14, CW - 28, CH - 28, 14); ik.stroke(); // foil frame line
570
+ [[60, 392], [84, 392]].forEach(([x, y]) => { ik.beginPath(); ik.arc(x, y, 9, 0, 7); ik.fill(); });
571
+ ik.globalCompositeOperation = 'source-over';
572
+ // translucent ink tint over the art window (cyan/magenta wash, foil glows through)
573
+ const tint = H.layer(CW, CH), tk = tint.getContext('2d');
574
+ const tg = tk.createLinearGradient(0, 80, 0, 356); tg.addColorStop(0, 'rgba(20,30,110,0.62)'); tg.addColorStop(1, 'rgba(70,10,90,0.55)');
575
+ tk.fillStyle = tg; tk.fillRect(28, 80, CW - 56, 276);
576
+ tk.globalCompositeOperation = 'destination-out'; tk.save(); tk.translate(CW / 2, 350); tk.scale(0.9, 0.9); tk.fillStyle = '#000'; robot(tk, 1); tk.fill();
577
+ tk.lineCap = 'round'; tk.lineWidth = 22; tk.strokeStyle = '#000';
578
+ tk.beginPath(); tk.moveTo(-76, -120); tk.quadraticCurveTo(-120, -150, -128, -200); tk.stroke();
579
+ tk.beginPath(); tk.moveTo(76, -110); tk.quadraticCurveTo(112, -80, 118, -40); tk.stroke();
580
+ tk.beginPath(); tk.arc(0, -290, 12, 0, 7); tk.fill(); tk.restore();
581
+ ik.drawImage(tint, 0, 0);
582
+ // robot line art in ink
583
+ ik.save(); ik.translate(CW / 2, 350); ik.scale(0.9, 0.9); ik.strokeStyle = 'rgba(12,14,34,0.92)'; ik.lineWidth = 4; ik.lineJoin = 'round';
584
+ robot(ik, 1); ik.stroke();
585
+ ik.beginPath(); ik.arc(0, -290, 12, 0, 7); ik.stroke();
586
+ ik.beginPath(); ik.roundRect(-44, -222, 88, 44, 18); ik.stroke();
587
+ ik.fillStyle = 'rgba(12,14,34,0.55)'; ik.fill();
588
+ ik.beginPath(); ik.arc(0, -78, 30, 0, 7); ik.stroke();
589
+ ik.lineCap = 'round'; ik.lineWidth = 26; ik.strokeStyle = 'rgba(12,14,34,0.92)';
590
+ ik.beginPath(); ik.moveTo(-76, -120); ik.quadraticCurveTo(-120, -150, -128, -200); ik.stroke();
591
+ ik.beginPath(); ik.moveTo(76, -110); ik.quadraticCurveTo(112, -80, 118, -40); ik.stroke();
592
+ ik.globalCompositeOperation = 'destination-out'; ik.lineWidth = 18;
593
+ ik.beginPath(); ik.moveTo(-76, -120); ik.quadraticCurveTo(-120, -150, -128, -200); ik.stroke();
594
+ ik.beginPath(); ik.moveTo(76, -110); ik.quadraticCurveTo(112, -80, 118, -40); ik.stroke();
595
+ ik.globalCompositeOperation = 'source-over';
596
+ ik.restore();
597
+ // text box (cream print)
598
+ ik.fillStyle = 'rgba(236,232,220,0.94)'; ik.beginPath(); ik.roundRect(28, 372, CW - 56, 150, 8); ik.fill();
599
+ ik.fillStyle = 'rgba(14,18,40,0.9)';
600
+ [[60, 392], [84, 392]].forEach(([x, y]) => { ik.beginPath(); ik.arc(x, y, 10.5, 0, 7); ik.fill(); });
601
+ ik.fillStyle = '#e8e4ff'; [[60, 392], [84, 392]].forEach(([x, y]) => { ik.beginPath(); ik.moveTo(x - 4, y + 5); ik.lineTo(x + 1, y - 6); ik.lineTo(x + 1, y); ik.lineTo(x + 5, y - 2); ik.lineTo(x - 1, y + 7); ik.fill(); });
602
+ ik.fillStyle = '#141838'; ik.font = '800 19px "Inter"'; ik.fillText('Neural Spark', 104, 399); ik.textAlign = 'right'; ik.fillText('90', CW - 46, 399); ik.textAlign = 'left';
603
+ ik.fillStyle = 'rgba(20,24,56,0.25)'; ik.fillRect(46, 414, CW - 92, 1.5);
604
+ ik.fillStyle = '#2a2e50'; ik.font = '600 13px "Inter"';
605
+ ['Flip a coin. If heads, this robot', 'dreams up one brand-new idea and', 'adds it to your hand.'].forEach((l, k) => ik.fillText(l, 46, 440 + k * 19));
606
+ ik.font = 'italic 600 11px "Inter"'; ik.fillStyle = 'rgba(42,46,80,0.8)';
607
+ ik.fillText('It hums lullabies in binary.', 46, 507);
608
+ // footer
609
+ ik.fillStyle = 'rgba(220,222,240,0.85)'; ik.font = '600 11px "Inter"';
610
+ ik.fillText('★ HOLO RARE', 34, CH - 34); ik.textAlign = 'right'; ik.fillText('001/150', CW - 34, CH - 34); ik.textAlign = 'left';
611
+ cc.drawImage(ink, 0, 0);
612
+ // gloss varnish: speculars still sparkle faintly on top of ink, plus emboss shading through ink
613
+ const gloss = H.layer(CW, CH), gl = gloss.getContext('2d');
614
+ const ggr = gl.createLinearGradient(0, 0, CW, CH);
615
+ ggr.addColorStop(H.clamp(band - 0.2), 'rgba(255,255,255,0)'); ggr.addColorStop(H.clamp(band), 'rgba(255,255,255,0.13)'); ggr.addColorStop(H.clamp(band + 0.2), 'rgba(255,255,255,0)');
616
+ gl.fillStyle = ggr; gl.fillRect(0, 0, CW, CH);
617
+ cc.globalCompositeOperation = 'screen'; cc.drawImage(gloss, 0, 0); cc.globalCompositeOperation = 'source-over';
618
+ // card stock edge
619
+ cc.globalCompositeOperation = 'destination-in'; cc.fillStyle = '#000'; cc.beginPath(); cc.roundRect(0, 0, CW, CH, 18); cc.fill();
620
+ cc.globalCompositeOperation = 'source-over';
621
+ cc.strokeStyle = 'rgba(255,255,255,0.35)'; cc.lineWidth = 2; cc.beginPath(); cc.roundRect(1, 1, CW - 2, CH - 2, 17); cc.stroke();
622
+
623
+ // ---------- sticker (hexagon badge) ----------
624
+ const SW = 230;
625
+ const hex = (x, cx0, cy0, r) => { x.beginPath(); for (let k = 0; k < 6; k++) { const a = Math.PI / 6 + k * Math.PI / 3; x.lineTo(cx0 + Math.cos(a) * r, cy0 + Math.sin(a) * r); } x.closePath(); };
626
+ const se = H.layer(SW, SW), sex = se.getContext('2d');
627
+ sex.fillStyle = '#000'; sex.fillRect(0, 0, SW, SW);
628
+ sex.strokeStyle = '#fff'; sex.lineWidth = 4; hex(sex, SW / 2, SW / 2, 88); sex.stroke();
629
+ sex.fillStyle = '#fff'; sex.font = '400 78px "Russo One"'; sex.textAlign = 'center'; sex.fillText('AI', SW / 2, SW / 2 + 28);
630
+ const seB = H.layer(SW, SW), sbx = seB.getContext('2d'); sbx.filter = 'blur(2px)'; sbx.drawImage(se, 0, 0);
631
+ const foilS = shadeFoil(SW, SW, seB, { seed: 23, crinkle: 3.4, emboss: 9, hue: 0.1 + tilt * 0.2, band: 0.5 - tilt * 0.2, L: [-0.5, -0.9, 1.2], t, ox: 300, oy: 100 });
632
+ { const fx = foilS.canvas.getContext('2d'); const ki = H.layer(SW, SW), kx = ki.getContext('2d');
633
+ kx.fillStyle = 'rgba(16,14,48,0.88)'; kx.fillRect(0, 0, SW, SW); kx.globalCompositeOperation = 'destination-out';
634
+ kx.font = '400 78px "Russo One"'; kx.textAlign = 'center'; kx.fillText('AI', SW / 2, SW / 2 + 28);
635
+ kx.lineWidth = 6; hex(kx, SW / 2, SW / 2, 88); kx.stroke();
636
+ kx.globalCompositeOperation = 'source-over'; kx.fillStyle = 'rgba(230,228,255,0.75)'; kx.font = '600 11px "Inter"'; kx.textAlign = 'center';
637
+ kx.fillText('\u2605 \u2605 \u2605', SW / 2, SW / 2 + 52);
638
+ fx.drawImage(ki, 0, 0); }
639
+ const st = H.layer(SW + 20, SW + 20), stx = st.getContext('2d');
640
+ stx.fillStyle = '#f4f2ee'; hex(stx, (SW + 20) / 2, (SW + 20) / 2, 108); stx.fill(); // white die-cut border
641
+ stx.save(); hex(stx, (SW + 20) / 2, (SW + 20) / 2, 98); stx.clip(); stx.drawImage(foilS.canvas, 10, 10); stx.restore();
642
+ stx.strokeStyle = 'rgba(0,0,0,0.12)'; stx.lineWidth = 1; hex(stx, (SW + 20) / 2, (SW + 20) / 2, 108); stx.stroke();
643
+
644
+ // ---------- holo confetti flakes on desk ----------
645
+ const flake = (x, y, s, a, hue) => {
646
+ ctx.save(); ctx.translate(x, y); ctx.rotate(a);
647
+ const [r, g, b] = hsl(hue, 0.9, 0.62);
648
+ ctx.fillStyle = `rgb(${r * 255 | 0},${g * 255 | 0},${b * 255 | 0})`; ctx.fillRect(-s / 2, -s / 3, s, s * 0.66);
649
+ ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fillRect(-s / 2, -s / 3, s, s * 0.15);
650
+ ctx.restore();
651
+ };
652
+
653
+ // ---------- compose ----------
654
+ const cardX = 520 - (1 - deal) * 120, cardY = 362 + (1 - deal) * 600, cardA = -0.08 + tilt * 0.01 + (1 - deal) * 0.3;
655
+ const sxTilt = 1 - 0.05 * (0.984 - tilt) / 1.884;
656
+ const stX = 948, stY = 470, stA = 0.22;
657
+ // rainbow light thrown onto the desk by the foil
658
+ ctx.save(); ctx.globalCompositeOperation = 'screen';
659
+ const spill = ctx.createLinearGradient(760, 120, 1180, 360);
660
+ for (let k = 0; k <= 6; k++) { const [r, g, b] = hsl(0.55 + k / 7 + tilt * 0.1, 1, 0.5); spill.addColorStop(k / 6, `rgba(${r * 255 | 0},${g * 255 | 0},${b * 255 | 0},0.16)`); }
661
+ ctx.fillStyle = spill; ctx.filter = 'blur(30px)';
662
+ ctx.beginPath(); ctx.ellipse(860, 250, 170, 60, -0.9, 0, 7); ctx.fill();
663
+ ctx.filter = 'none';
664
+ ctx.restore();
665
+ // shadows
666
+ ctx.save();
667
+ ctx.filter = 'blur(18px)'; ctx.fillStyle = 'rgba(0,0,0,0.65)';
668
+ const lift = 1 + (1 - land) * 1.6;
669
+ ctx.save(); ctx.translate(cardX + 18 * lift, cardY + 26 * lift); ctx.rotate(cardA); ctx.scale(sxTilt, 1); ctx.beginPath(); ctx.roundRect(-CW / 2, -CH / 2, CW, CH, 18); ctx.fill(); ctx.restore();
670
+ if (stP > 0) { const sl = 1 + (1 - stLand) * 2.5; ctx.globalAlpha = stP; ctx.save(); ctx.translate(stX + 10 * sl, stY + 14 * sl); ctx.rotate(stA); ctx.scale(1 + (1 - stLand) * 0.3, 1 + (1 - stLand) * 0.3); hex(ctx, 0, 0, 108); ctx.fill(); ctx.restore(); ctx.globalAlpha = 1; }
671
+ ctx.restore();
672
+ for (let k = 0; k < 26; k++) {
673
+ const x = R.range(760, 1240), y = R.range(80, 690);
674
+ if (Math.hypot(x - stX, y - stY) < 130) continue;
675
+ const fs = R.range(5, 12), fa = R() * 3, fh = R(), tk = 1.05 + k * 0.035, fp = H.smooth(tk - 0.4, tk, t);
676
+ if (fp <= 0) continue;
677
+ flake(x + (1 - fp) * 30, y - (1 - H.ease.out(fp)) * 160, fs, fa + (1 - fp) * 4, fh + tilt * 0.3);
678
+ }
679
+ if (stP > 0) { ctx.save(); ctx.globalAlpha = Math.min(1, stP * 3); ctx.translate(stX, stY); ctx.rotate(stA + (1 - stLand) * -0.4); const ss = 1 + (1 - stLand) * 0.3; ctx.scale(ss, ss); ctx.drawImage(st, -(SW + 20) / 2, -(SW + 20) / 2); ctx.restore(); }
680
+ ctx.save(); ctx.translate(cardX, cardY); ctx.rotate(cardA); ctx.scale(sxTilt, 1); ctx.drawImage(card, -CW / 2, -CH / 2); ctx.restore();
681
+
682
+ // photo finish
683
+ H.vignette(ctx, W, Hh, 0.45);
684
+ H.grain(ctx, W, Hh, 0.025, 77 + Math.floor(t * 30), true);
685
+ },
686
+ });
687
+ }