@nerdev/skillset 0.2.1

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 (567) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +351 -0
  3. package/bin/skillset.mjs +205 -0
  4. package/curations/README.md +53 -0
  5. package/curations/TEMPLATE/WHY.md +23 -0
  6. package/curations/TEMPLATE/overlay/SKILL.md +6 -0
  7. package/curations/impeccable/WHY.md +34 -0
  8. package/curations/impeccable/overlay/SKILL.md +116 -0
  9. package/package.json +43 -0
  10. package/scripts/ste-gate.mjs +39 -0
  11. package/scripts/ste-lint.py +201 -0
  12. package/scripts/sync.mjs +174 -0
  13. package/skills/ai-agent/agent-docs-writer/SKILL.md +105 -0
  14. package/skills/ai-agent/agent-docs-writer/references/principles.md +73 -0
  15. package/skills/ai-agent/agent-docs-writer/templates/AGENTS.template.md +44 -0
  16. package/skills/ai-agent/agent-docs-writer/templates/README.template.md +19 -0
  17. package/skills/ai-agent/ultra-context-engine/SKILL.md +941 -0
  18. package/skills/architecture/blueprint/SKILL.md +191 -0
  19. package/skills/architecture/code-tour/SKILL.md +204 -0
  20. package/skills/architecture/progress-guard/SKILL.md +148 -0
  21. package/skills/architecture/search-first/SKILL.md +97 -0
  22. package/skills/backend/api-design/SKILL.md +456 -0
  23. package/skills/backend/auth/SKILL.md +596 -0
  24. package/skills/backend/database/SKILL.md +431 -0
  25. package/skills/backend/realtime/SKILL.md +670 -0
  26. package/skills/backend/security/SKILL.md +590 -0
  27. package/skills/business/brag/SKILL.md +177 -0
  28. package/skills/business/brag/assets/music/README.md +20 -0
  29. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-1-by-ende-dot-app.music-cues.json +3846 -0
  30. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-1-by-ende-dot-app.music-cues.md +35 -0
  31. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-10-by-ende-dot-app.music-cues.json +1826 -0
  32. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-10-by-ende-dot-app.music-cues.md +35 -0
  33. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-11-by-ende-dot-app.music-cues.json +2266 -0
  34. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-11-by-ende-dot-app.music-cues.md +35 -0
  35. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-12-by-ende-dot-app.music-cues.json +2806 -0
  36. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-12-by-ende-dot-app.music-cues.md +35 -0
  37. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-9-by-ende-dot-app.music-cues.json +2796 -0
  38. package/skills/business/brag/assets/music/cues/happy-beats-business-moves-vol-9-by-ende-dot-app.music-cues.md +35 -0
  39. package/skills/business/brag/assets/music/happy-beats-business-moves-vol-1-by-ende-dot-app.mp3 +0 -0
  40. package/skills/business/brag/assets/music/happy-beats-business-moves-vol-10-by-ende-dot-app.mp3 +0 -0
  41. package/skills/business/brag/assets/music/happy-beats-business-moves-vol-11-by-ende-dot-app.mp3 +0 -0
  42. package/skills/business/brag/assets/music/happy-beats-business-moves-vol-12-by-ende-dot-app.mp3 +0 -0
  43. package/skills/business/brag/assets/music/happy-beats-business-moves-vol-9-by-ende-dot-app.mp3 +0 -0
  44. package/skills/business/brag/assets/sfx/casino/card-fan-1.ogg +0 -0
  45. package/skills/business/brag/assets/sfx/casino/card-fan-2.ogg +0 -0
  46. package/skills/business/brag/assets/sfx/casino/card-place-1.ogg +0 -0
  47. package/skills/business/brag/assets/sfx/casino/card-place-2.ogg +0 -0
  48. package/skills/business/brag/assets/sfx/casino/card-place-3.ogg +0 -0
  49. package/skills/business/brag/assets/sfx/casino/card-place-4.ogg +0 -0
  50. package/skills/business/brag/assets/sfx/casino/card-shove-1.ogg +0 -0
  51. package/skills/business/brag/assets/sfx/casino/card-shove-2.ogg +0 -0
  52. package/skills/business/brag/assets/sfx/casino/card-shove-3.ogg +0 -0
  53. package/skills/business/brag/assets/sfx/casino/card-shove-4.ogg +0 -0
  54. package/skills/business/brag/assets/sfx/casino/card-shuffle.ogg +0 -0
  55. package/skills/business/brag/assets/sfx/casino/card-slide-1.ogg +0 -0
  56. package/skills/business/brag/assets/sfx/casino/card-slide-2.ogg +0 -0
  57. package/skills/business/brag/assets/sfx/casino/card-slide-3.ogg +0 -0
  58. package/skills/business/brag/assets/sfx/casino/card-slide-4.ogg +0 -0
  59. package/skills/business/brag/assets/sfx/casino/card-slide-5.ogg +0 -0
  60. package/skills/business/brag/assets/sfx/casino/card-slide-6.ogg +0 -0
  61. package/skills/business/brag/assets/sfx/casino/card-slide-7.ogg +0 -0
  62. package/skills/business/brag/assets/sfx/casino/card-slide-8.ogg +0 -0
  63. package/skills/business/brag/assets/sfx/casino/cards-pack-open-1.ogg +0 -0
  64. package/skills/business/brag/assets/sfx/casino/cards-pack-open-2.ogg +0 -0
  65. package/skills/business/brag/assets/sfx/casino/chip-lay-1.ogg +0 -0
  66. package/skills/business/brag/assets/sfx/casino/chip-lay-2.ogg +0 -0
  67. package/skills/business/brag/assets/sfx/casino/chip-lay-3.ogg +0 -0
  68. package/skills/business/brag/assets/sfx/casino/chips-collide-1.ogg +0 -0
  69. package/skills/business/brag/assets/sfx/casino/chips-collide-2.ogg +0 -0
  70. package/skills/business/brag/assets/sfx/casino/chips-collide-3.ogg +0 -0
  71. package/skills/business/brag/assets/sfx/casino/chips-collide-4.ogg +0 -0
  72. package/skills/business/brag/assets/sfx/casino/chips-handle-1.ogg +0 -0
  73. package/skills/business/brag/assets/sfx/casino/chips-handle-2.ogg +0 -0
  74. package/skills/business/brag/assets/sfx/casino/chips-handle-3.ogg +0 -0
  75. package/skills/business/brag/assets/sfx/casino/chips-handle-4.ogg +0 -0
  76. package/skills/business/brag/assets/sfx/casino/chips-handle-6.ogg +0 -0
  77. package/skills/business/brag/assets/sfx/casino/chips-stack-1.ogg +0 -0
  78. package/skills/business/brag/assets/sfx/casino/chips-stack-2.ogg +0 -0
  79. package/skills/business/brag/assets/sfx/casino/chips-stack-3.ogg +0 -0
  80. package/skills/business/brag/assets/sfx/casino/chips-stack-4.ogg +0 -0
  81. package/skills/business/brag/assets/sfx/casino/chips-stack-5.ogg +0 -0
  82. package/skills/business/brag/assets/sfx/casino/chips-stack-6.ogg +0 -0
  83. package/skills/business/brag/assets/sfx/casino/dice-grab-1.ogg +0 -0
  84. package/skills/business/brag/assets/sfx/casino/dice-grab-2.ogg +0 -0
  85. package/skills/business/brag/assets/sfx/casino/dice-shake-1.ogg +0 -0
  86. package/skills/business/brag/assets/sfx/casino/dice-shake-2.ogg +0 -0
  87. package/skills/business/brag/assets/sfx/casino/dice-shake-3.ogg +0 -0
  88. package/skills/business/brag/assets/sfx/casino/dice-throw-1.ogg +0 -0
  89. package/skills/business/brag/assets/sfx/casino/dice-throw-2.ogg +0 -0
  90. package/skills/business/brag/assets/sfx/casino/dice-throw-3.ogg +0 -0
  91. package/skills/business/brag/assets/sfx/casino/die-throw-1.ogg +0 -0
  92. package/skills/business/brag/assets/sfx/casino/die-throw-2.ogg +0 -0
  93. package/skills/business/brag/assets/sfx/casino/die-throw-3.ogg +0 -0
  94. package/skills/business/brag/assets/sfx/casino/die-throw-4.ogg +0 -0
  95. package/skills/business/brag/assets/sfx/impact/footstep_carpet_000.ogg +0 -0
  96. package/skills/business/brag/assets/sfx/impact/footstep_carpet_003.ogg +0 -0
  97. package/skills/business/brag/assets/sfx/impact/footstep_carpet_004.ogg +0 -0
  98. package/skills/business/brag/assets/sfx/impact/footstep_concrete_000.ogg +0 -0
  99. package/skills/business/brag/assets/sfx/impact/footstep_concrete_001.ogg +0 -0
  100. package/skills/business/brag/assets/sfx/impact/footstep_concrete_002.ogg +0 -0
  101. package/skills/business/brag/assets/sfx/impact/footstep_concrete_003.ogg +0 -0
  102. package/skills/business/brag/assets/sfx/impact/footstep_concrete_004.ogg +0 -0
  103. package/skills/business/brag/assets/sfx/impact/footstep_grass_000.ogg +0 -0
  104. package/skills/business/brag/assets/sfx/impact/footstep_grass_001.ogg +0 -0
  105. package/skills/business/brag/assets/sfx/impact/footstep_grass_002.ogg +0 -0
  106. package/skills/business/brag/assets/sfx/impact/footstep_grass_003.ogg +0 -0
  107. package/skills/business/brag/assets/sfx/impact/footstep_grass_004.ogg +0 -0
  108. package/skills/business/brag/assets/sfx/impact/footstep_snow_000.ogg +0 -0
  109. package/skills/business/brag/assets/sfx/impact/footstep_snow_001.ogg +0 -0
  110. package/skills/business/brag/assets/sfx/impact/footstep_snow_002.ogg +0 -0
  111. package/skills/business/brag/assets/sfx/impact/footstep_snow_003.ogg +0 -0
  112. package/skills/business/brag/assets/sfx/impact/footstep_snow_004.ogg +0 -0
  113. package/skills/business/brag/assets/sfx/impact/footstep_wood_000.ogg +0 -0
  114. package/skills/business/brag/assets/sfx/impact/footstep_wood_001.ogg +0 -0
  115. package/skills/business/brag/assets/sfx/impact/footstep_wood_002.ogg +0 -0
  116. package/skills/business/brag/assets/sfx/impact/footstep_wood_003.ogg +0 -0
  117. package/skills/business/brag/assets/sfx/impact/footstep_wood_004.ogg +0 -0
  118. package/skills/business/brag/assets/sfx/impact/impactBell_heavy_000.ogg +0 -0
  119. package/skills/business/brag/assets/sfx/impact/impactBell_heavy_003.ogg +0 -0
  120. package/skills/business/brag/assets/sfx/impact/impactBell_heavy_004.ogg +0 -0
  121. package/skills/business/brag/assets/sfx/impact/impactGeneric_light_000.ogg +0 -0
  122. package/skills/business/brag/assets/sfx/impact/impactGeneric_light_001.ogg +0 -0
  123. package/skills/business/brag/assets/sfx/impact/impactGeneric_light_002.ogg +0 -0
  124. package/skills/business/brag/assets/sfx/impact/impactGeneric_light_003.ogg +0 -0
  125. package/skills/business/brag/assets/sfx/impact/impactGeneric_light_004.ogg +0 -0
  126. package/skills/business/brag/assets/sfx/impact/impactGlass_heavy_002.ogg +0 -0
  127. package/skills/business/brag/assets/sfx/impact/impactGlass_light_001.ogg +0 -0
  128. package/skills/business/brag/assets/sfx/impact/impactGlass_light_002.ogg +0 -0
  129. package/skills/business/brag/assets/sfx/impact/impactGlass_light_003.ogg +0 -0
  130. package/skills/business/brag/assets/sfx/impact/impactGlass_medium_000.ogg +0 -0
  131. package/skills/business/brag/assets/sfx/impact/impactGlass_medium_002.ogg +0 -0
  132. package/skills/business/brag/assets/sfx/impact/impactGlass_medium_004.ogg +0 -0
  133. package/skills/business/brag/assets/sfx/impact/impactMetal_heavy_000.ogg +0 -0
  134. package/skills/business/brag/assets/sfx/impact/impactMetal_heavy_002.ogg +0 -0
  135. package/skills/business/brag/assets/sfx/impact/impactMetal_heavy_004.ogg +0 -0
  136. package/skills/business/brag/assets/sfx/impact/impactMetal_light_002.ogg +0 -0
  137. package/skills/business/brag/assets/sfx/impact/impactMetal_light_003.ogg +0 -0
  138. package/skills/business/brag/assets/sfx/impact/impactMetal_medium_000.ogg +0 -0
  139. package/skills/business/brag/assets/sfx/impact/impactMetal_medium_001.ogg +0 -0
  140. package/skills/business/brag/assets/sfx/impact/impactMetal_medium_002.ogg +0 -0
  141. package/skills/business/brag/assets/sfx/impact/impactMetal_medium_003.ogg +0 -0
  142. package/skills/business/brag/assets/sfx/impact/impactMetal_medium_004.ogg +0 -0
  143. package/skills/business/brag/assets/sfx/impact/impactMining_001.ogg +0 -0
  144. package/skills/business/brag/assets/sfx/impact/impactPlank_medium_000.ogg +0 -0
  145. package/skills/business/brag/assets/sfx/impact/impactPlank_medium_001.ogg +0 -0
  146. package/skills/business/brag/assets/sfx/impact/impactPlank_medium_002.ogg +0 -0
  147. package/skills/business/brag/assets/sfx/impact/impactPlank_medium_003.ogg +0 -0
  148. package/skills/business/brag/assets/sfx/impact/impactPlank_medium_004.ogg +0 -0
  149. package/skills/business/brag/assets/sfx/impact/impactPlate_heavy_000.ogg +0 -0
  150. package/skills/business/brag/assets/sfx/impact/impactPlate_heavy_001.ogg +0 -0
  151. package/skills/business/brag/assets/sfx/impact/impactPlate_heavy_002.ogg +0 -0
  152. package/skills/business/brag/assets/sfx/impact/impactPlate_heavy_003.ogg +0 -0
  153. package/skills/business/brag/assets/sfx/impact/impactPlate_heavy_004.ogg +0 -0
  154. package/skills/business/brag/assets/sfx/impact/impactPlate_light_000.ogg +0 -0
  155. package/skills/business/brag/assets/sfx/impact/impactPlate_light_001.ogg +0 -0
  156. package/skills/business/brag/assets/sfx/impact/impactPlate_light_002.ogg +0 -0
  157. package/skills/business/brag/assets/sfx/impact/impactPlate_light_003.ogg +0 -0
  158. package/skills/business/brag/assets/sfx/impact/impactPlate_light_004.ogg +0 -0
  159. package/skills/business/brag/assets/sfx/impact/impactPlate_medium_000.ogg +0 -0
  160. package/skills/business/brag/assets/sfx/impact/impactPlate_medium_001.ogg +0 -0
  161. package/skills/business/brag/assets/sfx/impact/impactPlate_medium_002.ogg +0 -0
  162. package/skills/business/brag/assets/sfx/impact/impactPlate_medium_003.ogg +0 -0
  163. package/skills/business/brag/assets/sfx/impact/impactPlate_medium_004.ogg +0 -0
  164. package/skills/business/brag/assets/sfx/impact/impactPunch_heavy_000.ogg +0 -0
  165. package/skills/business/brag/assets/sfx/impact/impactPunch_heavy_001.ogg +0 -0
  166. package/skills/business/brag/assets/sfx/impact/impactPunch_heavy_002.ogg +0 -0
  167. package/skills/business/brag/assets/sfx/impact/impactPunch_heavy_003.ogg +0 -0
  168. package/skills/business/brag/assets/sfx/impact/impactPunch_heavy_004.ogg +0 -0
  169. package/skills/business/brag/assets/sfx/impact/impactPunch_medium_000.ogg +0 -0
  170. package/skills/business/brag/assets/sfx/impact/impactPunch_medium_001.ogg +0 -0
  171. package/skills/business/brag/assets/sfx/impact/impactPunch_medium_002.ogg +0 -0
  172. package/skills/business/brag/assets/sfx/impact/impactPunch_medium_003.ogg +0 -0
  173. package/skills/business/brag/assets/sfx/impact/impactPunch_medium_004.ogg +0 -0
  174. package/skills/business/brag/assets/sfx/impact/impactSoft_heavy_000.ogg +0 -0
  175. package/skills/business/brag/assets/sfx/impact/impactSoft_heavy_001.ogg +0 -0
  176. package/skills/business/brag/assets/sfx/impact/impactSoft_heavy_002.ogg +0 -0
  177. package/skills/business/brag/assets/sfx/impact/impactSoft_heavy_003.ogg +0 -0
  178. package/skills/business/brag/assets/sfx/impact/impactSoft_heavy_004.ogg +0 -0
  179. package/skills/business/brag/assets/sfx/impact/impactSoft_medium_000.ogg +0 -0
  180. package/skills/business/brag/assets/sfx/impact/impactSoft_medium_001.ogg +0 -0
  181. package/skills/business/brag/assets/sfx/impact/impactSoft_medium_002.ogg +0 -0
  182. package/skills/business/brag/assets/sfx/impact/impactSoft_medium_003.ogg +0 -0
  183. package/skills/business/brag/assets/sfx/impact/impactSoft_medium_004.ogg +0 -0
  184. package/skills/business/brag/assets/sfx/impact/impactTin_medium_000.ogg +0 -0
  185. package/skills/business/brag/assets/sfx/impact/impactTin_medium_001.ogg +0 -0
  186. package/skills/business/brag/assets/sfx/impact/impactTin_medium_002.ogg +0 -0
  187. package/skills/business/brag/assets/sfx/impact/impactTin_medium_003.ogg +0 -0
  188. package/skills/business/brag/assets/sfx/impact/impactTin_medium_004.ogg +0 -0
  189. package/skills/business/brag/assets/sfx/impact/impactWood_heavy_000.ogg +0 -0
  190. package/skills/business/brag/assets/sfx/impact/impactWood_heavy_001.ogg +0 -0
  191. package/skills/business/brag/assets/sfx/impact/impactWood_heavy_002.ogg +0 -0
  192. package/skills/business/brag/assets/sfx/impact/impactWood_heavy_003.ogg +0 -0
  193. package/skills/business/brag/assets/sfx/impact/impactWood_heavy_004.ogg +0 -0
  194. package/skills/business/brag/assets/sfx/impact/impactWood_light_000.ogg +0 -0
  195. package/skills/business/brag/assets/sfx/impact/impactWood_light_001.ogg +0 -0
  196. package/skills/business/brag/assets/sfx/impact/impactWood_light_002.ogg +0 -0
  197. package/skills/business/brag/assets/sfx/impact/impactWood_light_003.ogg +0 -0
  198. package/skills/business/brag/assets/sfx/impact/impactWood_light_004.ogg +0 -0
  199. package/skills/business/brag/assets/sfx/impact/impactWood_medium_000.ogg +0 -0
  200. package/skills/business/brag/assets/sfx/impact/impactWood_medium_001.ogg +0 -0
  201. package/skills/business/brag/assets/sfx/impact/impactWood_medium_002.ogg +0 -0
  202. package/skills/business/brag/assets/sfx/impact/impactWood_medium_003.ogg +0 -0
  203. package/skills/business/brag/assets/sfx/impact/impactWood_medium_004.ogg +0 -0
  204. package/skills/business/brag/assets/sfx/interface/bong_001.ogg +0 -0
  205. package/skills/business/brag/assets/sfx/interface/click_001.ogg +0 -0
  206. package/skills/business/brag/assets/sfx/interface/click_002.ogg +0 -0
  207. package/skills/business/brag/assets/sfx/interface/click_003.ogg +0 -0
  208. package/skills/business/brag/assets/sfx/interface/click_004.ogg +0 -0
  209. package/skills/business/brag/assets/sfx/interface/click_005.ogg +0 -0
  210. package/skills/business/brag/assets/sfx/interface/drop_001.ogg +0 -0
  211. package/skills/business/brag/assets/sfx/interface/drop_002.ogg +0 -0
  212. package/skills/business/brag/assets/sfx/interface/drop_003.ogg +0 -0
  213. package/skills/business/brag/assets/sfx/interface/error_005.ogg +0 -0
  214. package/skills/business/brag/assets/sfx/interface/error_006.ogg +0 -0
  215. package/skills/business/brag/assets/sfx/interface/glitch_002.ogg +0 -0
  216. package/skills/business/brag/assets/sfx/interface/glitch_004.ogg +0 -0
  217. package/skills/business/brag/assets/sfx/interface/select_008.ogg +0 -0
  218. package/skills/business/brag/assets/sfx/interface/switch_001.ogg +0 -0
  219. package/skills/business/brag/assets/sfx/interface/switch_002.ogg +0 -0
  220. package/skills/business/brag/assets/sfx/interface/switch_004.ogg +0 -0
  221. package/skills/business/brag/assets/sfx/interface/switch_005.ogg +0 -0
  222. package/skills/business/brag/assets/sfx/interface/switch_006.ogg +0 -0
  223. package/skills/business/brag/assets/sfx/interface/switch_007.ogg +0 -0
  224. package/skills/business/brag/assets/sfx/keyboard/keypress-001.wav +0 -0
  225. package/skills/business/brag/assets/sfx/keyboard/keypress-002.wav +0 -0
  226. package/skills/business/brag/assets/sfx/keyboard/keypress-003.wav +0 -0
  227. package/skills/business/brag/assets/sfx/keyboard/keypress-004.wav +0 -0
  228. package/skills/business/brag/assets/sfx/keyboard/keypress-005.wav +0 -0
  229. package/skills/business/brag/assets/sfx/keyboard/keypress-006.wav +0 -0
  230. package/skills/business/brag/assets/sfx/keyboard/keypress-007.wav +0 -0
  231. package/skills/business/brag/assets/sfx/keyboard/keypress-008.wav +0 -0
  232. package/skills/business/brag/assets/sfx/keyboard/keypress-009.wav +0 -0
  233. package/skills/business/brag/assets/sfx/keyboard/keypress-010.wav +0 -0
  234. package/skills/business/brag/assets/sfx/keyboard/keypress-011.wav +0 -0
  235. package/skills/business/brag/assets/sfx/keyboard/keypress-012.wav +0 -0
  236. package/skills/business/brag/assets/sfx/keyboard/keypress-013.wav +0 -0
  237. package/skills/business/brag/assets/sfx/keyboard/keypress-014.wav +0 -0
  238. package/skills/business/brag/assets/sfx/keyboard/keypress-015.wav +0 -0
  239. package/skills/business/brag/assets/sfx/keyboard/keypress-016.wav +0 -0
  240. package/skills/business/brag/assets/sfx/keyboard/keypress-017.wav +0 -0
  241. package/skills/business/brag/assets/sfx/keyboard/keypress-018.wav +0 -0
  242. package/skills/business/brag/assets/sfx/keyboard/keypress-019.wav +0 -0
  243. package/skills/business/brag/assets/sfx/keyboard/keypress-020.wav +0 -0
  244. package/skills/business/brag/assets/sfx/keyboard/keypress-021.wav +0 -0
  245. package/skills/business/brag/assets/sfx/keyboard/keypress-022.wav +0 -0
  246. package/skills/business/brag/assets/sfx/keyboard/keypress-023.wav +0 -0
  247. package/skills/business/brag/assets/sfx/keyboard/keypress-024.wav +0 -0
  248. package/skills/business/brag/assets/sfx/keyboard/keypress-025.wav +0 -0
  249. package/skills/business/brag/assets/sfx/keyboard/keypress-026.wav +0 -0
  250. package/skills/business/brag/assets/sfx/keyboard/keypress-027.wav +0 -0
  251. package/skills/business/brag/assets/sfx/keyboard/keypress-028.wav +0 -0
  252. package/skills/business/brag/assets/sfx/keyboard/keypress-029.wav +0 -0
  253. package/skills/business/brag/assets/sfx/keyboard/keypress-030.wav +0 -0
  254. package/skills/business/brag/assets/sfx/keyboard/keypress-031.wav +0 -0
  255. package/skills/business/brag/assets/sfx/keyboard/keypress-032.wav +0 -0
  256. package/skills/business/brag/assets/sfx/sfx-analysis.json +15584 -0
  257. package/skills/business/brag/assets/sfx/sfx-analysis.md +75 -0
  258. package/skills/business/brag/assets/sfx/ui/click1.ogg +0 -0
  259. package/skills/business/brag/assets/sfx/ui/click2.ogg +0 -0
  260. package/skills/business/brag/assets/sfx/ui/click3.ogg +0 -0
  261. package/skills/business/brag/assets/sfx/ui/click4.ogg +0 -0
  262. package/skills/business/brag/assets/sfx/ui/click5.ogg +0 -0
  263. package/skills/business/brag/assets/sfx/ui/mouseclick1.ogg +0 -0
  264. package/skills/business/brag/assets/sfx/ui/rollover1.ogg +0 -0
  265. package/skills/business/brag/assets/sfx/ui/rollover2.ogg +0 -0
  266. package/skills/business/brag/assets/sfx/ui/rollover4.ogg +0 -0
  267. package/skills/business/brag/assets/sfx/ui/rollover5.ogg +0 -0
  268. package/skills/business/brag/assets/sfx/ui/switch1.ogg +0 -0
  269. package/skills/business/brag/assets/sfx/ui/switch10.ogg +0 -0
  270. package/skills/business/brag/assets/sfx/ui/switch11.ogg +0 -0
  271. package/skills/business/brag/assets/sfx/ui/switch12.ogg +0 -0
  272. package/skills/business/brag/assets/sfx/ui/switch13.ogg +0 -0
  273. package/skills/business/brag/assets/sfx/ui/switch14.ogg +0 -0
  274. package/skills/business/brag/assets/sfx/ui/switch15.ogg +0 -0
  275. package/skills/business/brag/assets/sfx/ui/switch16.ogg +0 -0
  276. package/skills/business/brag/assets/sfx/ui/switch17.ogg +0 -0
  277. package/skills/business/brag/assets/sfx/ui/switch18.ogg +0 -0
  278. package/skills/business/brag/assets/sfx/ui/switch19.ogg +0 -0
  279. package/skills/business/brag/assets/sfx/ui/switch2.ogg +0 -0
  280. package/skills/business/brag/assets/sfx/ui/switch20.ogg +0 -0
  281. package/skills/business/brag/assets/sfx/ui/switch21.ogg +0 -0
  282. package/skills/business/brag/assets/sfx/ui/switch22.ogg +0 -0
  283. package/skills/business/brag/assets/sfx/ui/switch23.ogg +0 -0
  284. package/skills/business/brag/assets/sfx/ui/switch24.ogg +0 -0
  285. package/skills/business/brag/assets/sfx/ui/switch25.ogg +0 -0
  286. package/skills/business/brag/assets/sfx/ui/switch26.ogg +0 -0
  287. package/skills/business/brag/assets/sfx/ui/switch27.ogg +0 -0
  288. package/skills/business/brag/assets/sfx/ui/switch28.ogg +0 -0
  289. package/skills/business/brag/assets/sfx/ui/switch29.ogg +0 -0
  290. package/skills/business/brag/assets/sfx/ui/switch3.ogg +0 -0
  291. package/skills/business/brag/assets/sfx/ui/switch30.ogg +0 -0
  292. package/skills/business/brag/assets/sfx/ui/switch31.ogg +0 -0
  293. package/skills/business/brag/assets/sfx/ui/switch32.ogg +0 -0
  294. package/skills/business/brag/assets/sfx/ui/switch33.ogg +0 -0
  295. package/skills/business/brag/assets/sfx/ui/switch34.ogg +0 -0
  296. package/skills/business/brag/assets/sfx/ui/switch35.ogg +0 -0
  297. package/skills/business/brag/assets/sfx/ui/switch36.ogg +0 -0
  298. package/skills/business/brag/assets/sfx/ui/switch37.ogg +0 -0
  299. package/skills/business/brag/assets/sfx/ui/switch38.ogg +0 -0
  300. package/skills/business/brag/assets/sfx/ui/switch4.ogg +0 -0
  301. package/skills/business/brag/assets/sfx/ui/switch5.ogg +0 -0
  302. package/skills/business/brag/assets/sfx/ui/switch6.ogg +0 -0
  303. package/skills/business/brag/assets/sfx/ui/switch7.ogg +0 -0
  304. package/skills/business/brag/assets/sfx/ui/switch8.ogg +0 -0
  305. package/skills/business/brag/assets/sfx/ui/switch9.ogg +0 -0
  306. package/skills/business/brag/references/audio.md +307 -0
  307. package/skills/business/brag/references/step-1-inspect.md +133 -0
  308. package/skills/business/brag/references/step-2-plan.md +234 -0
  309. package/skills/business/brag/references/step-3-compose.md +210 -0
  310. package/skills/business/brag/references/step-4-deliver.md +168 -0
  311. package/skills/business/brag/references/tones.md +231 -0
  312. package/skills/business/brag/scripts/analyze_music_cues.py +309 -0
  313. package/skills/business/brag/scripts/pyproject.toml +14 -0
  314. package/skills/business/brag/scripts/uv.lock +819 -0
  315. package/skills/business/copywriting/SKILL.md +178 -0
  316. package/skills/business/customer-research/SKILL.md +259 -0
  317. package/skills/business/freelancing/SKILL.md +679 -0
  318. package/skills/business/launch/SKILL.md +273 -0
  319. package/skills/business/marketing-psychology/SKILL.md +364 -0
  320. package/skills/business/pricing/SKILL.md +233 -0
  321. package/skills/business/resume-revamp-jake-ats/SKILL.md +64 -0
  322. package/skills/business/startup-founder/SKILL.md +400 -0
  323. package/skills/business/startup-founder/references/funding.md +48 -0
  324. package/skills/business/startup-founder/references/further-reading.md +132 -0
  325. package/skills/business/startup-founder/references/gtm-tools.md +57 -0
  326. package/skills/business/startup-founder/references/legal-india.md +36 -0
  327. package/skills/business/startup-founder/references/marketing/README.md +16 -0
  328. package/skills/business/startup-founder/references/marketing/cold-email.md +172 -0
  329. package/skills/business/startup-founder/references/marketing/competitors.md +245 -0
  330. package/skills/business/startup-founder/references/marketing/customer-research.md +276 -0
  331. package/skills/business/startup-founder/references/marketing/free-tools.md +168 -0
  332. package/skills/business/startup-founder/references/marketing/launch.md +343 -0
  333. package/skills/business/startup-founder/references/marketing/marketing-plan.md +270 -0
  334. package/skills/business/startup-founder/references/marketing/pricing.md +237 -0
  335. package/skills/business/startup-founder/references/marketing/product-marketing.md +250 -0
  336. package/skills/business/startup-founder/references/validation.md +267 -0
  337. package/skills/devops/deployments/SKILL.md +339 -0
  338. package/skills/devops/git-ci/SKILL.md +372 -0
  339. package/skills/devops/performance/SKILL.md +523 -0
  340. package/skills/devops/testing/SKILL.md +558 -0
  341. package/skills/engineering/code-review/SKILL.md +149 -0
  342. package/skills/engineering/coding-standards/SKILL.md +229 -0
  343. package/skills/engineering/product-engineering/SKILL.md +219 -0
  344. package/skills/engineering/senior-engineer-prompts/SKILL.md +145 -0
  345. package/skills/engineering/tdd-workflow/SKILL.md +178 -0
  346. package/skills/engineering/verification-loop/SKILL.md +199 -0
  347. package/skills/nerdev/nerdev-abstraction/SKILL.md +412 -0
  348. package/skills/nerdev/nerdev-docs/SKILL.md +527 -0
  349. package/skills/nerdev/nerdev-docs/templates/adr-template.md +39 -0
  350. package/skills/nerdev/nerdev-docs/templates/contributing-template.md +140 -0
  351. package/skills/nerdev/nerdev-docs/templates/deploy-template.md +426 -0
  352. package/skills/nerdev/nerdev-docs/templates/design-template.md +105 -0
  353. package/skills/nerdev/nerdev-docs/templates/incident-template.md +66 -0
  354. package/skills/nerdev/nerdev-monorepo/SKILL.md +522 -0
  355. package/skills/quality/output-skill/SKILL.md +49 -0
  356. package/skills/quality/unslop/SKILL.md +67 -0
  357. package/skills/ui/animaxxing/SKILL.md +58 -0
  358. package/skills/ui/animaxxing/agents/openai.yaml +7 -0
  359. package/skills/ui/animaxxing/references/effect-restoration.md +26 -0
  360. package/skills/ui/animaxxing/references/motion-vocabulary.md +142 -0
  361. package/skills/ui/animaxxing/references/recipes/blast-off.md +119 -0
  362. package/skills/ui/animaxxing/references/recipes/particle-effects.md +718 -0
  363. package/skills/ui/animaxxing/references/recipes/particle-field.md +428 -0
  364. package/skills/ui/animaxxing/references/recipes/route-letters.md +205 -0
  365. package/skills/ui/animaxxing/references/recipes/speak-in.md +181 -0
  366. package/skills/ui/animaxxing/references/recipes/split-entrances.md +345 -0
  367. package/skills/ui/animaxxing/references/recipes/wave.md +144 -0
  368. package/skills/ui/animaxxing/references/text-stability.md +68 -0
  369. package/skills/ui/animaxxing/references/verification.md +44 -0
  370. package/skills/ui/ask-animation/SKILL.md +178 -0
  371. package/skills/ui/ask-base-ui/SKILL.md +400 -0
  372. package/skills/ui/ask-cmdk/SKILL.md +315 -0
  373. package/skills/ui/ask-command-menu/SKILL.md +262 -0
  374. package/skills/ui/ask-drawer/SKILL.md +235 -0
  375. package/skills/ui/ask-form/SKILL.md +229 -0
  376. package/skills/ui/ask-gsap/SKILL.md +435 -0
  377. package/skills/ui/ask-modal/SKILL.md +145 -0
  378. package/skills/ui/ask-motion/SKILL.md +339 -0
  379. package/skills/ui/ask-rhf/SKILL.md +388 -0
  380. package/skills/ui/ask-sonner/API.md +64 -0
  381. package/skills/ui/ask-sonner/SKILL.md +88 -0
  382. package/skills/ui/ask-table/SKILL.md +253 -0
  383. package/skills/ui/ask-tanstack-table/SKILL.md +490 -0
  384. package/skills/ui/ask-toast/SKILL.md +163 -0
  385. package/skills/ui/ask-vaul/SKILL.md +291 -0
  386. package/skills/ui/ask-virtual-list/SKILL.md +304 -0
  387. package/skills/ui/ask-virtuoso/SKILL.md +332 -0
  388. package/skills/ui/email-craft/SKILL.md +258 -0
  389. package/skills/ui/frontend-craftsmanship/SKILL.md +462 -0
  390. package/skills/ui/gsap-astro/SKILL.md +56 -0
  391. package/skills/ui/gsap-astro/agents/openai.yaml +7 -0
  392. package/skills/ui/gsap-astro/references/client-router-navigation.md +139 -0
  393. package/skills/ui/gsap-astro/references/initialization.md +87 -0
  394. package/skills/ui/gsap-astro/references/motion-system.md +152 -0
  395. package/skills/ui/gsap-astro/references/scripts-and-islands.md +65 -0
  396. package/skills/ui/gsap-astro/references/verification.md +44 -0
  397. package/skills/ui/gsap-core/SKILL.md +254 -0
  398. package/skills/ui/gsap-frameworks/SKILL.md +266 -0
  399. package/skills/ui/gsap-nextjs/SKILL.md +51 -0
  400. package/skills/ui/gsap-nextjs/agents/openai.yaml +7 -0
  401. package/skills/ui/gsap-nextjs/references/app-router-navigation.md +189 -0
  402. package/skills/ui/gsap-nextjs/references/initialization.md +87 -0
  403. package/skills/ui/gsap-nextjs/references/motion-system.md +158 -0
  404. package/skills/ui/gsap-nextjs/references/verification.md +40 -0
  405. package/skills/ui/gsap-nuxt/SKILL.md +56 -0
  406. package/skills/ui/gsap-nuxt/agents/openai.yaml +7 -0
  407. package/skills/ui/gsap-nuxt/references/initialization.md +87 -0
  408. package/skills/ui/gsap-nuxt/references/motion-system.md +161 -0
  409. package/skills/ui/gsap-nuxt/references/navigation.md +145 -0
  410. package/skills/ui/gsap-nuxt/references/page-transitions.md +107 -0
  411. package/skills/ui/gsap-nuxt/references/verification.md +42 -0
  412. package/skills/ui/gsap-performance/SKILL.md +79 -0
  413. package/skills/ui/gsap-plugins/SKILL.md +433 -0
  414. package/skills/ui/gsap-react/SKILL.md +136 -0
  415. package/skills/ui/gsap-react-router/SKILL.md +55 -0
  416. package/skills/ui/gsap-react-router/agents/openai.yaml +7 -0
  417. package/skills/ui/gsap-react-router/references/initialization.md +87 -0
  418. package/skills/ui/gsap-react-router/references/motion-system.md +158 -0
  419. package/skills/ui/gsap-react-router/references/react-router-navigation.md +165 -0
  420. package/skills/ui/gsap-react-router/references/route-lifetime.md +74 -0
  421. package/skills/ui/gsap-react-router/references/verification.md +42 -0
  422. package/skills/ui/gsap-scrolltrigger/SKILL.md +296 -0
  423. package/skills/ui/gsap-sveltekit/SKILL.md +54 -0
  424. package/skills/ui/gsap-sveltekit/agents/openai.yaml +7 -0
  425. package/skills/ui/gsap-sveltekit/references/initialization.md +87 -0
  426. package/skills/ui/gsap-sveltekit/references/motion-system.md +156 -0
  427. package/skills/ui/gsap-sveltekit/references/page-lifetime.md +63 -0
  428. package/skills/ui/gsap-sveltekit/references/sveltekit-navigation.md +160 -0
  429. package/skills/ui/gsap-sveltekit/references/verification.md +41 -0
  430. package/skills/ui/gsap-tanstack-router/SKILL.md +56 -0
  431. package/skills/ui/gsap-tanstack-router/agents/openai.yaml +7 -0
  432. package/skills/ui/gsap-tanstack-router/references/initialization.md +87 -0
  433. package/skills/ui/gsap-tanstack-router/references/motion-system.md +160 -0
  434. package/skills/ui/gsap-tanstack-router/references/route-lifetime.md +66 -0
  435. package/skills/ui/gsap-tanstack-router/references/tanstack-navigation.md +156 -0
  436. package/skills/ui/gsap-tanstack-router/references/verification.md +41 -0
  437. package/skills/ui/gsap-timeline/SKILL.md +107 -0
  438. package/skills/ui/gsap-utils/SKILL.md +284 -0
  439. package/skills/ui/gsap-vanilla/SKILL.md +63 -0
  440. package/skills/ui/gsap-vanilla/agents/openai.yaml +7 -0
  441. package/skills/ui/gsap-vanilla/references/cross-document-navigation.md +54 -0
  442. package/skills/ui/gsap-vanilla/references/initialization.md +87 -0
  443. package/skills/ui/gsap-vanilla/references/motion-system.md +150 -0
  444. package/skills/ui/gsap-vanilla/references/page-load.md +96 -0
  445. package/skills/ui/gsap-vanilla/references/spa-navigation.md +70 -0
  446. package/skills/ui/gsap-vanilla/references/verification.md +41 -0
  447. package/skills/ui/house-style/SKILL.md +366 -0
  448. package/skills/ui/imagegen/SKILL.md +49 -0
  449. package/skills/ui/imagegen/references/brandkit.md +48 -0
  450. package/skills/ui/imagegen/references/image-to-code.md +56 -0
  451. package/skills/ui/imagegen/references/mobile.md +58 -0
  452. package/skills/ui/imagegen/references/web.md +63 -0
  453. package/skills/ui/impeccable/SKILL.md +116 -0
  454. package/skills/ui/impeccable/reference/adapt.md +318 -0
  455. package/skills/ui/impeccable/reference/adapt.native.md +58 -0
  456. package/skills/ui/impeccable/reference/android.md +46 -0
  457. package/skills/ui/impeccable/reference/animate.md +89 -0
  458. package/skills/ui/impeccable/reference/audit.md +137 -0
  459. package/skills/ui/impeccable/reference/audit.native.md +139 -0
  460. package/skills/ui/impeccable/reference/bolder.md +33 -0
  461. package/skills/ui/impeccable/reference/clarify.md +94 -0
  462. package/skills/ui/impeccable/reference/colorize.md +86 -0
  463. package/skills/ui/impeccable/reference/craft-floor.md +44 -0
  464. package/skills/ui/impeccable/reference/craft.md +5 -0
  465. package/skills/ui/impeccable/reference/critique.md +806 -0
  466. package/skills/ui/impeccable/reference/degraded/asset-producer.md +37 -0
  467. package/skills/ui/impeccable/reference/degraded/documenter.md +24 -0
  468. package/skills/ui/impeccable/reference/degraded/finish-reviewer.md +38 -0
  469. package/skills/ui/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  470. package/skills/ui/impeccable/reference/delight.md +70 -0
  471. package/skills/ui/impeccable/reference/distill.md +111 -0
  472. package/skills/ui/impeccable/reference/doctor.md +54 -0
  473. package/skills/ui/impeccable/reference/document.md +416 -0
  474. package/skills/ui/impeccable/reference/extract.md +69 -0
  475. package/skills/ui/impeccable/reference/generate.md +101 -0
  476. package/skills/ui/impeccable/reference/harden.md +345 -0
  477. package/skills/ui/impeccable/reference/hooks.md +111 -0
  478. package/skills/ui/impeccable/reference/init.md +131 -0
  479. package/skills/ui/impeccable/reference/ios.md +51 -0
  480. package/skills/ui/impeccable/reference/layout.md +84 -0
  481. package/skills/ui/impeccable/reference/live-setup.md +104 -0
  482. package/skills/ui/impeccable/reference/live.md +325 -0
  483. package/skills/ui/impeccable/reference/new-work.md +145 -0
  484. package/skills/ui/impeccable/reference/onboard.md +234 -0
  485. package/skills/ui/impeccable/reference/operate.md +61 -0
  486. package/skills/ui/impeccable/reference/optimize.md +258 -0
  487. package/skills/ui/impeccable/reference/overdrive.md +127 -0
  488. package/skills/ui/impeccable/reference/polish.md +105 -0
  489. package/skills/ui/impeccable/reference/quieter.md +99 -0
  490. package/skills/ui/impeccable/reference/routing.md +24 -0
  491. package/skills/ui/impeccable/reference/shape.md +59 -0
  492. package/skills/ui/impeccable/reference/typeset.md +80 -0
  493. package/skills/ui/impeccable/reference/visualize.md +46 -0
  494. package/skills/ui/impeccable/scripts/VERSION +1 -0
  495. package/skills/ui/impeccable/scripts/command-metadata.json +98 -0
  496. package/skills/ui/impeccable/scripts/data/font-index-failures.json +121 -0
  497. package/skills/ui/impeccable/scripts/data/font-index.json +1 -0
  498. package/skills/ui/impeccable/scripts/impeccable +206 -0
  499. package/skills/ui/impeccable/scripts/impeccable.cmd +214 -0
  500. package/skills/ui/impeccable/scripts/live-browser-dom.js +167 -0
  501. package/skills/ui/impeccable/scripts/live-browser-ignores.js +242 -0
  502. package/skills/ui/impeccable/scripts/live-browser-session.js +144 -0
  503. package/skills/ui/impeccable/scripts/live-browser.js +13510 -0
  504. package/skills/ui/impeccable/scripts/modern-screenshot.umd.js +14 -0
  505. package/skills/ui/motion/SKILL.md +207 -0
  506. package/skills/ui/pick-ui-library/SKILL.md +85 -0
  507. package/skills/ui/product-messaging/SKILL.md +163 -0
  508. package/skills/ui/product-site/SKILL.md +465 -0
  509. package/skills/ui/prototype/PICKER.md +197 -0
  510. package/skills/ui/prototype/SKILL.md +98 -0
  511. package/skills/ui/refero-design/SKILL.md +534 -0
  512. package/skills/ui/refero-design/agents/openai.yaml +4 -0
  513. package/skills/ui/refero-design/references/anti-ai-slop.md +292 -0
  514. package/skills/ui/refero-design/references/color.md +563 -0
  515. package/skills/ui/refero-design/references/copywriting.md +222 -0
  516. package/skills/ui/refero-design/references/craft-details.md +525 -0
  517. package/skills/ui/refero-design/references/example-workflow.md +264 -0
  518. package/skills/ui/refero-design/references/icons.md +166 -0
  519. package/skills/ui/refero-design/references/mcp-tools.md +258 -0
  520. package/skills/ui/refero-design/references/motion.md +504 -0
  521. package/skills/ui/refero-design/references/typography.md +737 -0
  522. package/skills/ui/refero-design/references/visual-workflow.md +105 -0
  523. package/skills/ui/style-animaxxing/SKILL.md +48 -0
  524. package/skills/ui/style-animaxxing/agents/openai.yaml +7 -0
  525. package/skills/ui/style-animaxxing/references/motion-vocabulary.md +51 -0
  526. package/skills/ui/style-animaxxing/references/tokens.md +288 -0
  527. package/skills/ui/style-animaxxing/references/typography-and-layout.md +149 -0
  528. package/skills/ui/style-animaxxing/references/verification.md +38 -0
  529. package/skills/ui/typography-picker/SKILL.md +305 -0
  530. package/skills/ui/ui-core/SKILL.md +345 -0
  531. package/skills/ui/ui-core/references/accessibility.md +85 -0
  532. package/skills/ui/ui-core/references/build.md +666 -0
  533. package/skills/ui/ui-core/references/copy.md +70 -0
  534. package/skills/ui/ui-core/references/dark-mode.md +76 -0
  535. package/skills/ui/ui-core/references/direction.md +99 -0
  536. package/skills/ui/ui-core/references/redesign.md +74 -0
  537. package/skills/ui/ui-core/references/responsive.md +77 -0
  538. package/skills/ui/ui-core/references/styles.md +42 -0
  539. package/skills/ui/ui-core/references/system.md +190 -0
  540. package/skills/ui/ui-core/references/verify.md +37 -0
  541. package/skills/ui/ui-engineering/SKILL.md +436 -0
  542. package/skills/ui/ui-engineering/references/avatars.md +74 -0
  543. package/skills/ui/ui-engineering/references/card-patterns.md +66 -0
  544. package/skills/ui/ui-engineering/references/floating-actions.md +65 -0
  545. package/skills/ui/ui-engineering/references/navigation.md +81 -0
  546. package/skills/ui/ui-engineering/references/overlays.md +74 -0
  547. package/skills/ui/ui-engineering/references/pagination.md +71 -0
  548. package/skills/ui/ui-engineering/references/progress-indicators.md +77 -0
  549. package/skills/ui/ui-engineering/references/quality-bar.md +293 -0
  550. package/skills/ui/ui-engineering/references/search.md +68 -0
  551. package/skills/ui/ui-engineering/references/tailwind-architecture.md +81 -0
  552. package/skills/ui/ui-theme-picker/SKILL.md +522 -0
  553. package/skills/ui/ui-theme-picker/references/component-sources.md +223 -0
  554. package/skills/ui/ui-theme-picker/references/styles.md +298 -0
  555. package/skills/ui/vitepress-docs/SKILL.md +439 -0
  556. package/skills/ui/vitepress-docs/references/components.md +333 -0
  557. package/skills/ui/vitepress-docs/references/layout.md +128 -0
  558. package/skills/ui/vitepress-docs/references/tokens.md +87 -0
  559. package/skills/ui/vitepress-docs/references/typography.md +76 -0
  560. package/skills/workflow/careful/SKILL.md +48 -0
  561. package/skills/workflow/context-restore/SKILL.md +69 -0
  562. package/skills/workflow/context-save/SKILL.md +96 -0
  563. package/skills/workflow/health/SKILL.md +99 -0
  564. package/skills/workflow/learn/SKILL.md +95 -0
  565. package/skills/workflow/office-hours/SKILL.md +136 -0
  566. package/skills/workflow/ship/SKILL.md +95 -0
  567. package/vendor.json +80 -0
@@ -0,0 +1,428 @@
1
+ # Recipe: particle field
2
+
3
+ Failure contract: apply [effect restoration](../effect-restoration.md) when adapting this module. The framework controller chooses recovery timing; this effect must undo even partial setup.
4
+
5
+ A canvas that bleeds out past a target element, with a list of particles stepped by the GSAP ticker. Behaviour lives in emitters (called each frame while attached) and in per-particle update hooks; GSAP tweens on the plain particle objects work too, since the ticker runs the core update before the field's own step. The field only ticks while it has something to draw and is on screen, so an idle button costs nothing.
6
+
7
+ `attachParticleEffect` wires a field to a target for the life of a component and returns the controls the framework skill's controller calls. The controller decides when; this module never does.
8
+
9
+ Dependencies: `gsap`.
10
+
11
+ ## Markup
12
+
13
+ The canvas sits in a wrapper with the target, offset by the effect's bleed, colored through the canvas's CSS `color`, out of the accessibility tree and pointer flow. `z-index` above the target for `layer: "over"` effects, below for `"under"`.
14
+
15
+ ```html
16
+ <div class="relative isolate" data-particle-button>
17
+ <canvas aria-hidden="true" class="pointer-events-none absolute z-10 text-inherit" style="left:-180px; top:-180px"></canvas>
18
+ <button class="relative z-20 …">Continue</button>
19
+ </div>
20
+ ```
21
+
22
+ Plain CSS equivalent for the positioning classes above (under-layer example):
23
+
24
+ ```css
25
+ [data-particle-button] { position: relative; isolation: isolate; }
26
+ [data-particle-button] canvas {
27
+ pointer-events: none; position: absolute; z-index: 10; color: inherit;
28
+ }
29
+ [data-particle-button] button { position: relative; z-index: 20; }
30
+ ```
31
+
32
+ The canvas inherits the wrapper's text color by default; set its `color` to an existing brand token when needed for contrast. No palette or token name is required. The framework controller handles any pre-paint hiding and wrapper reveal.
33
+
34
+ ## field.ts
35
+
36
+ ```ts
37
+ import gsap from "gsap";
38
+
39
+ export type Shape = "dot" | "spark" | "ring" | "square" | "star" | "outline" | "streak";
40
+
41
+ export type Particle = {
42
+ x: number;
43
+ y: number;
44
+ vx: number;
45
+ vy: number;
46
+ /** Radius for dots and rings, half-width for squares, stroke width for sparks, half-length for streaks, spread for outlines. */
47
+ size: number;
48
+ alpha: number;
49
+ shape: Shape;
50
+ rotation: number;
51
+ /** Radians per second. */
52
+ spin: number;
53
+ /** Fraction of velocity kept after one second. 1 keeps it all. */
54
+ drag: number;
55
+ /** Pixels per second squared, downward positive. */
56
+ gravity: number;
57
+ /** Total seconds to live, or Infinity while something else owns the particle. */
58
+ life: number;
59
+ age: number;
60
+ /** Alpha falls to zero over the particle's life. */
61
+ fade: boolean;
62
+ /** Size falls to zero over the particle's life. */
63
+ shrink: boolean;
64
+ /** Sideways sway: amplitude in px/s and frequency in Hz. */
65
+ wobble: number;
66
+ wobbleFreq: number;
67
+ phase: number;
68
+ update?: (p: Particle, dt: number, time: number) => void;
69
+ };
70
+
71
+ export type Box = { x: number; y: number; w: number; h: number };
72
+ export type Emitter = (dt: number, time: number) => void;
73
+
74
+ const DEFAULTS: Omit<Particle, "x" | "y"> = {
75
+ vx: 0, vy: 0, size: 2, alpha: 1, shape: "dot", rotation: 0, spin: 0, drag: 1, gravity: 0,
76
+ life: 1, age: 0, fade: true, shrink: false, wobble: 0, wobbleFreq: 0, phase: 0,
77
+ };
78
+
79
+ /** Longest step the simulation will take, so a stalled tab does not fling everything off screen. */
80
+ const MAX_STEP = 0.05;
81
+
82
+ export class ParticleField {
83
+ readonly canvas: HTMLCanvasElement;
84
+ readonly particles: Particle[] = [];
85
+ readonly emitters = new Set<Emitter>();
86
+ /** The target element's rectangle, in canvas CSS pixels. */
87
+ readonly box: Box = { x: 0, y: 0, w: 0, h: 0 };
88
+ /** Corner radius of the target, read from its computed style. */
89
+ radius = 0;
90
+ color = "#000";
91
+ time = 0;
92
+
93
+ private readonly ctx: CanvasRenderingContext2D;
94
+ private readonly target: HTMLElement;
95
+ private readonly bleed: number;
96
+ private width = 0;
97
+ private height = 0;
98
+ private running = false;
99
+ private onScreen = true;
100
+
101
+ constructor(canvas: HTMLCanvasElement, target: HTMLElement, bleed: number) {
102
+ const ctx = canvas.getContext("2d");
103
+ if (!ctx) throw new Error("ParticleField needs a 2d canvas context");
104
+ this.canvas = canvas;
105
+ this.ctx = ctx;
106
+ this.target = target;
107
+ this.bleed = bleed;
108
+ this.sync();
109
+ }
110
+
111
+ /** Re-measure the target and resize the canvas around it. */
112
+ sync(): void {
113
+ const { offsetWidth: w, offsetHeight: h } = this.target;
114
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
115
+ this.width = w + this.bleed * 2;
116
+ this.height = h + this.bleed * 2;
117
+ this.box.x = this.bleed;
118
+ this.box.y = this.bleed;
119
+ this.box.w = w;
120
+ this.box.h = h;
121
+ this.canvas.width = Math.round(this.width * dpr);
122
+ this.canvas.height = Math.round(this.height * dpr);
123
+ this.canvas.style.width = `${this.width}px`;
124
+ this.canvas.style.height = `${this.height}px`;
125
+ this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
126
+ this.radius = parseFloat(getComputedStyle(this.target).borderTopLeftRadius) || 0;
127
+ this.color = getComputedStyle(this.canvas).color;
128
+ }
129
+
130
+ spawn(init: Partial<Particle> & { x: number; y: number }): Particle {
131
+ const p: Particle = { ...DEFAULTS, ...init };
132
+ this.particles.push(p);
133
+ this.start();
134
+ return p;
135
+ }
136
+
137
+ /** Let every managed particle finish within `within` seconds. */
138
+ release(within = 0.3): void {
139
+ for (const p of this.particles) {
140
+ if (p.life === Infinity || p.life - p.age > within) {
141
+ p.life = p.age + within;
142
+ p.fade = true;
143
+ }
144
+ }
145
+ }
146
+
147
+ addEmitter(emitter: Emitter): void { this.emitters.add(emitter); this.start(); }
148
+ removeEmitter(emitter: Emitter): void { this.emitters.delete(emitter); }
149
+
150
+ setOnScreen(onScreen: boolean): void {
151
+ this.onScreen = onScreen;
152
+ if (onScreen) this.start(); else this.stop();
153
+ }
154
+
155
+ start(): void {
156
+ if (this.running || !this.onScreen) return;
157
+ if (this.particles.length === 0 && this.emitters.size === 0) return;
158
+ this.running = true;
159
+ gsap.ticker.add(this.tick);
160
+ }
161
+
162
+ stop(): void {
163
+ if (!this.running) return;
164
+ this.running = false;
165
+ gsap.ticker.remove(this.tick);
166
+ }
167
+
168
+ destroy(): void {
169
+ this.stop();
170
+ this.emitters.clear();
171
+ this.particles.length = 0;
172
+ this.ctx.clearRect(0, 0, this.width, this.height);
173
+ }
174
+
175
+ private readonly tick = (_time: number, deltaMs: number) => {
176
+ const dt = Math.min(deltaMs / 1000, MAX_STEP);
177
+ this.time += dt;
178
+ for (const emitter of this.emitters) emitter(dt, this.time);
179
+ this.step(dt);
180
+ this.draw();
181
+ if (this.particles.length === 0 && this.emitters.size === 0) this.stop();
182
+ };
183
+
184
+ private step(dt: number): void {
185
+ const list = this.particles;
186
+ let keep = 0;
187
+ for (let i = 0; i < list.length; i++) {
188
+ const p = list[i]!;
189
+ p.age += dt;
190
+ if (p.age >= p.life) continue;
191
+ if (p.drag !== 1) {
192
+ const keepVelocity = Math.pow(p.drag, dt);
193
+ p.vx *= keepVelocity;
194
+ p.vy *= keepVelocity;
195
+ }
196
+ p.vy += p.gravity * dt;
197
+ p.x += p.vx * dt;
198
+ p.y += p.vy * dt;
199
+ if (p.wobble !== 0) p.x += Math.sin(this.time * p.wobbleFreq * Math.PI * 2 + p.phase) * p.wobble * dt;
200
+ p.rotation += p.spin * dt;
201
+ p.update?.(p, dt, this.time);
202
+ list[keep++] = p;
203
+ }
204
+ list.length = keep;
205
+ }
206
+
207
+ private draw(): void {
208
+ const ctx = this.ctx;
209
+ ctx.clearRect(0, 0, this.width, this.height);
210
+ ctx.fillStyle = this.color;
211
+ ctx.strokeStyle = this.color;
212
+ ctx.lineCap = "round";
213
+ for (const p of this.particles) {
214
+ const progress = p.life === Infinity ? 0 : p.age / p.life;
215
+ const alpha = p.alpha * (p.fade ? 1 - progress : 1);
216
+ const size = p.size * (p.shrink ? 1 - progress : 1);
217
+ if (alpha <= 0.005 || size <= 0.05) continue;
218
+ ctx.globalAlpha = Math.min(alpha, 1);
219
+ switch (p.shape) {
220
+ case "dot":
221
+ ctx.beginPath(); ctx.arc(p.x, p.y, size, 0, Math.PI * 2); ctx.fill();
222
+ break;
223
+ case "ring":
224
+ ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(p.x, p.y, size, 0, Math.PI * 2); ctx.stroke();
225
+ break;
226
+ case "spark": {
227
+ // A streak trailing the direction of travel; a still spark is a dot.
228
+ const speed = Math.hypot(p.vx, p.vy);
229
+ if (speed < 1) { ctx.beginPath(); ctx.arc(p.x, p.y, size, 0, Math.PI * 2); ctx.fill(); break; }
230
+ const tail = Math.min(speed * 0.04, 18);
231
+ ctx.lineWidth = size;
232
+ ctx.beginPath();
233
+ ctx.moveTo(p.x, p.y);
234
+ ctx.lineTo(p.x - (p.vx / speed) * tail, p.y - (p.vy / speed) * tail);
235
+ ctx.stroke();
236
+ break;
237
+ }
238
+ case "streak":
239
+ // A level hairline, centred on the particle.
240
+ ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(p.x - size, p.y); ctx.lineTo(p.x + size, p.y); ctx.stroke();
241
+ break;
242
+ case "square":
243
+ ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rotation);
244
+ ctx.fillRect(-size, -size, size * 2, size * 2);
245
+ ctx.restore();
246
+ break;
247
+ case "star":
248
+ // Four thin rays crossing at the centre, like a lens glint.
249
+ ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rotation);
250
+ ctx.lineWidth = 1; ctx.beginPath();
251
+ ctx.moveTo(-size, 0); ctx.lineTo(size, 0); ctx.moveTo(0, -size); ctx.lineTo(0, size);
252
+ ctx.stroke(); ctx.restore();
253
+ break;
254
+ case "outline": {
255
+ // The target's rounded rectangle, pushed outward by `size`.
256
+ const { x, y, w, h } = this.box;
257
+ ctx.lineWidth = 1.5; ctx.beginPath();
258
+ ctx.roundRect(x - size, y - size, w + size * 2, h + size * 2, this.radius + size);
259
+ ctx.stroke();
260
+ break;
261
+ }
262
+ }
263
+ }
264
+ ctx.globalAlpha = 1;
265
+ }
266
+ }
267
+
268
+ export type EdgePoint = { x: number; y: number; nx: number; ny: number };
269
+
270
+ /** Length of a rounded rectangle's outline. */
271
+ export function perimeterLength(box: Box, radius: number): number {
272
+ const r = Math.min(radius, box.w / 2, box.h / 2);
273
+ return 2 * (box.w - 2 * r) + 2 * (box.h - 2 * r) + 2 * Math.PI * r;
274
+ }
275
+
276
+ /** A point on a rounded rectangle's outline at `t` in [0, 1), clockwise from the top-left corner, with the outward normal. */
277
+ export function perimeterPoint(box: Box, radius: number, t: number): EdgePoint {
278
+ const r = Math.min(radius, box.w / 2, box.h / 2);
279
+ const { x, y, w, h } = box;
280
+ const sw = w - 2 * r;
281
+ const sh = h - 2 * r;
282
+ const arc = (Math.PI * r) / 2;
283
+ const total = 2 * sw + 2 * sh + 4 * arc;
284
+ let d = (((t % 1) + 1) % 1) * total;
285
+
286
+ if (d < sw) return { x: x + r + d, y, nx: 0, ny: -1 };
287
+ d -= sw;
288
+ if (d < arc) { const a = -Math.PI / 2 + d / r; return { x: x + w - r + Math.cos(a) * r, y: y + r + Math.sin(a) * r, nx: Math.cos(a), ny: Math.sin(a) }; }
289
+ d -= arc;
290
+ if (d < sh) return { x: x + w, y: y + r + d, nx: 1, ny: 0 };
291
+ d -= sh;
292
+ if (d < arc) { const a = d / r; return { x: x + w - r + Math.cos(a) * r, y: y + h - r + Math.sin(a) * r, nx: Math.cos(a), ny: Math.sin(a) }; }
293
+ d -= arc;
294
+ if (d < sw) return { x: x + w - r - d, y: y + h, nx: 0, ny: 1 };
295
+ d -= sw;
296
+ if (d < arc) { const a = Math.PI / 2 + d / r; return { x: x + r + Math.cos(a) * r, y: y + h - r + Math.sin(a) * r, nx: Math.cos(a), ny: Math.sin(a) }; }
297
+ d -= arc;
298
+ if (d < sh) return { x, y: y + h - r - d, nx: -1, ny: 0 };
299
+ d -= sh;
300
+ const a = Math.PI + d / r;
301
+ return { x: x + r + Math.cos(a) * r, y: y + r + Math.sin(a) * r, nx: Math.cos(a), ny: Math.sin(a) };
302
+ }
303
+ ```
304
+
305
+ ## attach.ts
306
+
307
+ Keeps the field sized to the target, re-reads its colour when the theme changes, pauses it off screen, and treats pointer hover and keyboard focus as one state. Returns the controls and a `destroy` for unmount.
308
+
309
+ ```ts
310
+ import gsap from "gsap";
311
+ import { ParticleField } from "./field";
312
+
313
+ /* Swap for the project's helper if it has one. */
314
+ function prefersReducedMotion(): boolean {
315
+ if (typeof window === "undefined") return true;
316
+ const choice = document.documentElement.dataset.motion;
317
+ if (choice === "reduced") return true;
318
+ if (choice === "full") return false;
319
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
320
+ }
321
+
322
+ export type ParticleEffectInstance = {
323
+ /** Builds the entrance, starting after `delay` seconds. The timeline reveals the target itself. */
324
+ enter(delay: number): gsap.core.Timeline;
325
+ /** Stops the ambient loop and lets the particles die. */
326
+ exit(): void;
327
+ /** Everything at once: the biggest burst the effect has, then silence. */
328
+ blast(): void;
329
+ /** Restarts the ambient loop after an exit or blast. */
330
+ idle(): void;
331
+ hover(on: boolean): void;
332
+ destroy(): void;
333
+ };
334
+
335
+ export type ParticleEffectDefinition<Instance extends ParticleEffectInstance = ParticleEffectInstance> = {
336
+ /** Whether the canvas sits under or over the target. */
337
+ layer?: "under" | "over";
338
+ /** How far the canvas extends past the target on each side, in px. */
339
+ bleed: number;
340
+ create(field: ParticleField, target: HTMLElement): Instance;
341
+ };
342
+
343
+ export type ParticleEffectControls = {
344
+ enter(delay?: number): void;
345
+ exit(): void;
346
+ blast(): void;
347
+ idle(): void;
348
+ /** Tear everything down. Call on unmount. */
349
+ destroy(): void;
350
+ };
351
+
352
+ export function attachParticleEffect(
353
+ root: HTMLElement,
354
+ canvas: HTMLCanvasElement,
355
+ target: HTMLElement,
356
+ effect: ParticleEffectDefinition,
357
+ ): ParticleEffectControls {
358
+ const field = new ParticleField(canvas, target, effect.bleed);
359
+ const instance = effect.create(field, target);
360
+ let entrance: gsap.core.Timeline | null = null;
361
+ let ready = false;
362
+
363
+ const resize = new ResizeObserver(() => field.sync());
364
+ resize.observe(target);
365
+ // Theme changes land on <html>; re-read the particle colour when they do.
366
+ const theme = new MutationObserver(() => field.sync());
367
+ theme.observe(document.documentElement, { attributes: true });
368
+ const visibility = new IntersectionObserver(([entry]) => field.setOnScreen(entry?.isIntersecting ?? true));
369
+ visibility.observe(root);
370
+
371
+ const on = () => { if (ready) instance.hover(true); };
372
+ const off = () => { if (ready) instance.hover(false); };
373
+ target.addEventListener("pointerenter", on);
374
+ target.addEventListener("pointerleave", off);
375
+ target.addEventListener("focus", on);
376
+ target.addEventListener("blur", off);
377
+
378
+ return {
379
+ enter(delay = 0) {
380
+ entrance?.kill();
381
+ if (prefersReducedMotion()) {
382
+ gsap.set(target, { autoAlpha: 1, clearProps: "transform" });
383
+ ready = true;
384
+ return;
385
+ }
386
+ entrance = instance.enter(delay);
387
+ entrance.eventCallback("onComplete", () => { ready = true; });
388
+ },
389
+ exit() {
390
+ ready = false;
391
+ entrance?.kill();
392
+ entrance = null;
393
+ instance.exit();
394
+ gsap.to(target, { autoAlpha: 0, duration: prefersReducedMotion() ? 0 : 0.2, overwrite: "auto" });
395
+ },
396
+ blast() {
397
+ ready = false;
398
+ entrance?.kill();
399
+ entrance = null;
400
+ if (!prefersReducedMotion()) instance.blast();
401
+ },
402
+ idle() {
403
+ if (!prefersReducedMotion()) instance.idle();
404
+ ready = true;
405
+ },
406
+ destroy() {
407
+ target.removeEventListener("pointerenter", on);
408
+ target.removeEventListener("pointerleave", off);
409
+ target.removeEventListener("focus", on);
410
+ target.removeEventListener("blur", off);
411
+ resize.disconnect();
412
+ theme.disconnect();
413
+ visibility.disconnect();
414
+ entrance?.kill();
415
+ entrance = null;
416
+ instance.destroy();
417
+ field.destroy();
418
+ ready = false;
419
+ },
420
+ };
421
+ }
422
+ ```
423
+
424
+ ## Controller contract
425
+
426
+ `attachParticleEffect(wrapper, canvas, target, effect)` returns `enter`, `exit`, `blast`, `idle`, and `destroy` controls. These controls return void; they are surface effects, not navigation-completion promises. The framework controller owns their GSAP context and calls `destroy` to release observers, listeners, timelines, and the ticker.
427
+
428
+ The target is prepared by `enter`; reveal a hidden wrapper separately so its canvas can show while the target assembles. Use the framework skill for first-paint readiness and lifecycle subscriptions.
@@ -0,0 +1,205 @@
1
+ # Recipe: route intro and outro
2
+
3
+ Failure contract: apply [effect restoration](../effect-restoration.md) when adapting this module. The framework controller chooses recovery timing; this effect must undo even partial setup.
4
+
5
+ The framework skill's controller calls `buildPageIntro` during intro and `buildPageOutro` during outro; this module never decides when, never touches the router, and never listens for clicks. It reads `data-page-transition` on the page's items and returns a timeline. The controller owns phase state.
6
+
7
+ Dependencies: `gsap`, `gsap/SplitText`.
8
+
9
+ Setup: follow [stable typography for character animation](../text-stability.md#stable-typography-for-character-animation) before creating splits; keep that target CSS after revert and under reduced motion. Verify the split-to-unsplit boundary with the [cleanup checks](../verification.md#splittext-cleanup-stability).
10
+
11
+ ```ts
12
+ import gsap from "gsap";
13
+ import { SplitText } from "gsap/SplitText";
14
+
15
+ gsap.registerPlugin(SplitText);
16
+
17
+ /* Swap for the project's helper if it has one. */
18
+ function prefersReducedMotion(): boolean {
19
+ if (typeof window === "undefined") return true;
20
+ const choice = document.documentElement.dataset.motion;
21
+ if (choice === "reduced") return true;
22
+ if (choice === "full") return false;
23
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
24
+ }
25
+
26
+ const ITEM_SELECTOR = "[data-page-transition]";
27
+ const LETTERS = "letters";
28
+ const SIDE_LETTERS = "letters-sides";
29
+ const HORIZONTAL_SLIDE = "slide-horizontal";
30
+ /** Seconds the page holds before the headline letters begin to implode. */
31
+ const LETTERS_DELAY = 0.75;
32
+ /** How far a headline letter starts from its place. Scales with the viewport. */
33
+ const SPREAD_X = () => window.innerWidth * 0.6;
34
+ const SPREAD_Y = () => window.innerHeight * 0.6;
35
+ const SHIFT = { component: 8, page: 16 } as const;
36
+
37
+ type LetterSplit = ReturnType<typeof SplitText.create>;
38
+ const activeSplits = new WeakMap<HTMLElement, LetterSplit>();
39
+
40
+ function pageItems(container: HTMLElement): HTMLElement[] {
41
+ const items = Array.from(container.querySelectorAll<HTMLElement>(ITEM_SELECTOR));
42
+ return items.length > 0 ? items : [container];
43
+ }
44
+ const ofKind = (items: HTMLElement[], kind: string) => items.filter((item) => item.dataset.pageTransition === kind);
45
+ /** Alternate letters come from opposite sides, so a line zips together. */
46
+ const sideOffset = (index: number) => (index % 2 === 0 ? -1 : 1) * SPREAD_X();
47
+
48
+ function splitLetters(item: HTMLElement): LetterSplit {
49
+ revertLetters(item);
50
+ const split = SplitText.create(item, { type: "chars", smartWrap: true, aria: "auto" });
51
+ activeSplits.set(item, split);
52
+ return split;
53
+ }
54
+
55
+ function revertLetters(item: HTMLElement): void {
56
+ const split = activeSplits.get(item);
57
+ if (!split) return;
58
+ gsap.killTweensOf(split.chars);
59
+ split.revert();
60
+ activeSplits.delete(item);
61
+ }
62
+
63
+ export function buildPageIntro(container: HTMLElement, onComplete?: () => void): gsap.core.Timeline {
64
+ const items = pageItems(container);
65
+ const letters = ofKind(items, LETTERS);
66
+ const sideLetters = ofKind(items, SIDE_LETTERS);
67
+ const allLetters = [...letters, ...sideLetters];
68
+ const slide = ofKind(items, HORIZONTAL_SLIDE);
69
+ const standard = items.filter((item) => !allLetters.includes(item) && !slide.includes(item));
70
+ const timeline = gsap.timeline({ defaults: { overwrite: "auto" } });
71
+
72
+ const finish = () => {
73
+ allLetters.forEach(revertLetters);
74
+ if (allLetters.length > 0) gsap.set(allLetters, { autoAlpha: 1, clearProps: "transform,willChange" });
75
+ onComplete?.();
76
+ };
77
+ timeline.eventCallback("onComplete", finish);
78
+ timeline.eventCallback("onInterrupt", () => allLetters.forEach(revertLetters));
79
+
80
+ if (prefersReducedMotion()) {
81
+ if (slide.length > 0) gsap.set(slide, { transition: "none" });
82
+ timeline.set(items, { autoAlpha: 1, clearProps: "transform,willChange" });
83
+ if (slide.length > 0) timeline.set(slide, { clearProps: "transition" });
84
+ return timeline;
85
+ }
86
+
87
+ // Prepare the incoming DOM while the container is still behind the CSS
88
+ // waiting barrier. Only release the barrier after every item is hidden.
89
+ if (standard.length > 0) gsap.set(standard, { autoAlpha: 0, y: 16 });
90
+ if (slide.length > 0) gsap.set(slide, { autoAlpha: 0, x: -SHIFT.page, transition: "none" });
91
+ const splits = letters.map((item) => {
92
+ const split = splitLetters(item);
93
+ gsap.set(item, { autoAlpha: 1, y: 0 });
94
+ gsap.set(split.chars, {
95
+ autoAlpha: 0,
96
+ x: () => gsap.utils.random(-SPREAD_X(), SPREAD_X()),
97
+ y: () => gsap.utils.random(-SPREAD_Y(), SPREAD_Y()),
98
+ rotation: () => gsap.utils.random(-90, 90),
99
+ scale: 0.5,
100
+ });
101
+ return split;
102
+ });
103
+ const sideSplits = sideLetters.map((item) => {
104
+ const split = splitLetters(item);
105
+ gsap.set(item, { autoAlpha: 1, y: 0 });
106
+ gsap.set(split.chars, { autoAlpha: 0, x: (index: number) => sideOffset(index), y: 0 });
107
+ return split;
108
+ });
109
+
110
+ timeline.addLabel("enter", 0).set(items, { willChange: "transform, opacity" }, "enter");
111
+ for (const split of splits) {
112
+ timeline.to(
113
+ split.chars,
114
+ { autoAlpha: 1, x: 0, y: 0, rotation: 0, scale: 1, duration: 0.75, ease: "power4.out", stagger: { each: 0.02, from: "random" } },
115
+ `enter+=${LETTERS_DELAY}`,
116
+ );
117
+ }
118
+ for (const split of sideSplits) {
119
+ timeline.to(
120
+ split.chars,
121
+ { autoAlpha: 1, x: 0, duration: 0.6, ease: "power4.out", stagger: { each: 0.012, from: "center" } },
122
+ `enter+=${LETTERS_DELAY + 0.14}`,
123
+ );
124
+ }
125
+ if (standard.length > 0) {
126
+ timeline.to(
127
+ standard,
128
+ { autoAlpha: 1, y: 0, duration: 0.42, ease: "power3.out", stagger: 0.09 },
129
+ letters.length > 0 ? `enter+=${LETTERS_DELAY + 0.14}` : "enter",
130
+ );
131
+ }
132
+ if (slide.length > 0) {
133
+ timeline.to(
134
+ slide,
135
+ { autoAlpha: 1, x: 0, duration: 0.2, ease: "power2.out" },
136
+ letters.length > 0 ? `enter+=${LETTERS_DELAY + 0.23}` : "enter+=0.09",
137
+ );
138
+ }
139
+ return timeline.set(items, { clearProps: "transform,transition,willChange" }, timeline.duration());
140
+ }
141
+
142
+ export function buildPageOutro(container: HTMLElement, onComplete: () => void): gsap.core.Timeline {
143
+ const items = pageItems(container).reverse();
144
+ const letters = ofKind(items, LETTERS);
145
+ const sideLetters = ofKind(items, SIDE_LETTERS);
146
+ const allLetters = [...letters, ...sideLetters];
147
+ const slide = ofKind(items, HORIZONTAL_SLIDE);
148
+ const standard = items.filter((item) => !allLetters.includes(item) && !slide.includes(item));
149
+ allLetters.forEach(revertLetters);
150
+
151
+ const timeline = gsap.timeline({
152
+ defaults: { overwrite: "auto" },
153
+ onComplete: () => {
154
+ allLetters.forEach(revertLetters);
155
+ if (allLetters.length > 0) gsap.set(allLetters, { autoAlpha: 0 });
156
+ onComplete();
157
+ },
158
+ });
159
+ timeline.eventCallback("onInterrupt", () => allLetters.forEach(revertLetters));
160
+ if (slide.length > 0) gsap.set(slide, { transition: "none" });
161
+
162
+ if (prefersReducedMotion()) return timeline.set(items, { autoAlpha: 0 });
163
+
164
+ const splits = letters.map(splitLetters);
165
+ const sideSplits = sideLetters.map(splitLetters);
166
+ if (slide.length > 0) gsap.set(slide, { y: 0 });
167
+ timeline.addLabel("exit", 0).set(items, { willChange: "transform, opacity" }, "exit");
168
+ standard.forEach((item, index) => {
169
+ timeline.to(item, { autoAlpha: 0, y: -8, duration: 0.22, ease: "power2.in" }, `exit+=${index * 0.055}`);
170
+ });
171
+ if (slide.length > 0) {
172
+ timeline.to(slide, { autoAlpha: 0, x: SHIFT.component, duration: 0.14, ease: "power2.in" }, "exit");
173
+ }
174
+ for (const split of splits) {
175
+ timeline.to(
176
+ split.chars,
177
+ {
178
+ autoAlpha: 0,
179
+ x: () => gsap.utils.random(-SPREAD_X(), SPREAD_X()),
180
+ y: () => gsap.utils.random(-SPREAD_Y(), SPREAD_Y()),
181
+ rotation: () => gsap.utils.random(-90, 90),
182
+ scale: 1.6,
183
+ duration: 0.28,
184
+ ease: "power2.in",
185
+ stagger: { each: 0.012, from: "edges" },
186
+ },
187
+ standard.length > 0 ? "exit+=0.1" : "exit",
188
+ );
189
+ }
190
+ for (const split of sideSplits) {
191
+ timeline.to(
192
+ split.chars,
193
+ { autoAlpha: 0, x: (index: number) => sideOffset(index), duration: 0.24, ease: "power2.in", stagger: { each: 0.008, from: "center" } },
194
+ "exit",
195
+ );
196
+ }
197
+ return timeline;
198
+ }
199
+ ```
200
+
201
+ ## Controller contract
202
+
203
+ Call `buildPageIntro(container, onComplete)` with prepared targets and `buildPageOutro(container, onComplete)` for the requested exit. Both return timelines; killing them reverts their character splits but does not call successful completion. The controller must settle any wait on interruption separately.
204
+
205
+ The controller owns the barrier: prepare targets before revealing the intro, and keep the outgoing end state covered through cleanup if its router needs that. It also owns focus, phase signals to surface effects, and any [resize response](../motion-vocabulary.md#resize). Do not attach another entrance to an element already marked as a page item.