@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,181 @@
1
+ # Recipe: speak in
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 paragraph arrives one word at a time at roughly the pace you would say it: short words quickly, long words a beat longer, a full stop earns a pause. Ordinary words each get one of a handful of small entrances; words marked for emphasis are split further into letters and given a bigger one, and may be left with a finish: `broken` (each letter at its own weight and width on the variable axis) or `tilt`.
6
+
7
+ The framework skill's controller calls `speakIn` during intro at the chosen position in its sequence; this module never decides when. Keep the returned `revert` for as long as the finishes should persist, and call it on outro or unmount. Use for short display copy such as a hero subhead; leave ordinary reading text immediately readable.
8
+
9
+ Dependencies: `gsap`, `gsap/SplitText`. A variable font supporting the configured weight range for the `broken` finish (400-800 in the example). Adapt that range to the loaded face, or omit `broken` / use `tilt` for a static face; keep its existing font.
10
+
11
+ 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. Apply it only to persistent emphasis elements receiving inner character splits, not the entire paragraph. Verify the split-to-unsplit boundary with the [cleanup checks](../verification.md#splittext-cleanup-stability).
12
+
13
+ ```ts
14
+ import gsap from "gsap";
15
+ import { SplitText } from "gsap/SplitText";
16
+
17
+ gsap.registerPlugin(SplitText);
18
+
19
+ /* Swap for the project's helper if it has one. */
20
+ function prefersReducedMotion(): boolean {
21
+ if (typeof window === "undefined") return true;
22
+ const choice = document.documentElement.dataset.motion;
23
+ if (choice === "reduced") return true;
24
+ if (choice === "full") return false;
25
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
26
+ }
27
+
28
+ /** Seconds a word takes to arrive, before adding time per letter. */
29
+ const WORD_BASE = 0.06;
30
+ /** Extra seconds per letter, so long words take longer to say. */
31
+ const PER_LETTER = 0.008;
32
+ /** Breath after a sentence ends. */
33
+ const SENTENCE_PAUSE = 0.65;
34
+ /** Extra beat around an emphasized word. */
35
+ const EMPHASIS_HOLD = 0.1;
36
+ /** Seconds the settle into a finish takes, after the letters have landed. */
37
+ const FINISH_TIME = 0.22;
38
+
39
+ const rnd = gsap.utils.random;
40
+
41
+ type WordIn = (word: HTMLElement) => gsap.core.Tween;
42
+ type LettersIn = (chars: HTMLElement[]) => gsap.core.Tween;
43
+
44
+ const WORD_INS: WordIn[] = [
45
+ (w) => gsap.fromTo(w, { y: 16, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.4, ease: "power3.out" }),
46
+ (w) => gsap.fromTo(w, { y: -12, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.35, ease: "power2.out" }),
47
+ (w) => gsap.fromTo(w, { scale: 0.6, autoAlpha: 0, transformOrigin: "50% 100%" }, { scale: 1, autoAlpha: 1, duration: 0.45, ease: "back.out(2)" }),
48
+ (w) => gsap.fromTo(w, { filter: "blur(8px)", autoAlpha: 0 }, { filter: "blur(0px)", autoAlpha: 1, duration: 0.45, ease: "power2.out" }),
49
+ (w) => gsap.fromTo(w, { x: -14, autoAlpha: 0 }, { x: 0, autoAlpha: 1, duration: 0.35, ease: "power3.out" }),
50
+ (w) => gsap.fromTo(w, { rotationX: -80, autoAlpha: 0, transformOrigin: "50% 100%" }, { rotationX: 0, autoAlpha: 1, duration: 0.45, ease: "power3.out" }),
51
+ ];
52
+
53
+ const LETTERS_INS: LettersIn[] = [
54
+ // Tumble: letters drop from above and bounce into place.
55
+ (chars) => gsap.fromTo(chars, { y: -40, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.6, ease: "bounce.out", stagger: 0.04 }),
56
+ // Stamp: letters shrink down from oversized.
57
+ (chars) => gsap.fromTo(chars, { scale: 2.2, autoAlpha: 0 }, { scale: 1, autoAlpha: 1, duration: 0.35, ease: "power4.out", stagger: 0.035 }),
58
+ // Pop: letters spring in from nothing with a twist.
59
+ (chars) =>
60
+ gsap.fromTo(
61
+ chars,
62
+ { scale: 0, rotation: () => rnd(-30, 30), autoAlpha: 0 },
63
+ { scale: 1, rotation: 0, autoAlpha: 1, duration: 0.7, ease: "elastic.out(1, 0.45)", stagger: 0.05 },
64
+ ),
65
+ // Cascade: letters rise and straighten one after another.
66
+ (chars) => gsap.fromTo(chars, { y: 24, rotation: -12, autoAlpha: 0 }, { y: 0, rotation: 0, autoAlpha: 1, duration: 0.5, ease: "back.out(1.7)", stagger: 0.035 }),
67
+ ];
68
+
69
+ const normalize = (text: string) => text.toLowerCase().replace(/[^\p{L}\p{N}]/gu, "");
70
+
71
+ export type Finish = "broken" | "tilt";
72
+ export type Emphasis = string | { word: string; finish?: Finish; /** Degrees for `tilt`, clockwise positive. Random when omitted. */ angle?: number };
73
+ export type SpeakOptions = {
74
+ /** Words that get the letter-level entrance, matched ignoring case and punctuation. */
75
+ emphasis?: Emphasis[];
76
+ /** Seconds of silence before the first word. */
77
+ delay?: number;
78
+ };
79
+
80
+ function settle(timeline: gsap.core.Timeline, word: HTMLElement, chars: HTMLElement[], finish: Finish, angle?: number) {
81
+ if (finish === "broken") {
82
+ // Each letter lands somewhere on the 400 to 800 axis and stretches or narrows a little to match.
83
+ timeline.to(
84
+ chars,
85
+ { fontWeight: () => rnd(400, 800, 10), scaleX: () => rnd(0.85, 1.2), transformOrigin: "50% 100%", duration: FINISH_TIME, ease: "power2.out", stagger: 0.015 },
86
+ ">",
87
+ );
88
+ } else {
89
+ timeline.to(
90
+ word,
91
+ { rotation: angle ?? rnd(2.5, 5) * (Math.random() < 0.5 ? -1 : 1), transformOrigin: "50% 100%", duration: FINISH_TIME, ease: "power2.out" },
92
+ ">",
93
+ );
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Builds the entrance and returns it with a revert that puts the paragraph's
99
+ * markup back. The element should start hidden; the timeline reveals it as
100
+ * the first word lands. `words` are the split words, for the outro to throw.
101
+ */
102
+ export function speakIn(
103
+ el: HTMLElement,
104
+ { emphasis = [], delay = 0 }: SpeakOptions = {},
105
+ ): { timeline: gsap.core.Timeline; words: HTMLElement[]; revert: () => void } {
106
+ const timeline = gsap.timeline();
107
+ if (prefersReducedMotion()) {
108
+ timeline.set(el, { autoAlpha: 1 });
109
+ return { timeline, words: [], revert: () => {} };
110
+ }
111
+
112
+ const wanted = new Map<string, { finish?: Finish; angle?: number }>(
113
+ emphasis.map((e) =>
114
+ typeof e === "string"
115
+ ? [normalize(e), {}]
116
+ : [normalize(e.word), { ...(e.finish && { finish: e.finish }), ...(e.angle !== undefined && { angle: e.angle }) }],
117
+ ),
118
+ );
119
+ const split = SplitText.create(el, { type: "words" });
120
+ const words = split.words as HTMLElement[];
121
+ const letterSplits: SplitText[] = [];
122
+
123
+ gsap.set(words, { autoAlpha: 0 });
124
+ timeline.set(el, { autoAlpha: 1 }, 0);
125
+
126
+ let t = delay;
127
+ let wordIndex = 0;
128
+ let lettersIndex = Math.floor(Math.random() * LETTERS_INS.length);
129
+ let lastWordIn = -1;
130
+ for (const word of words) {
131
+ const text = word.textContent ?? "";
132
+ const letters = normalize(text).length;
133
+
134
+ if (wanted.has(normalize(text))) {
135
+ const inner = SplitText.create(word, { type: "chars" });
136
+ letterSplits.push(inner);
137
+ const chars = inner.chars as HTMLElement[];
138
+ gsap.set(chars, { autoAlpha: 0 });
139
+ const enter = LETTERS_INS[lettersIndex % LETTERS_INS.length];
140
+ lettersIndex++;
141
+ timeline.set(word, { autoAlpha: 1 }, t);
142
+ if (enter) timeline.add(enter(chars), t);
143
+ const spec = wanted.get(normalize(text));
144
+ if (spec?.finish) settle(timeline, word, chars, spec.finish, spec.angle);
145
+ t += WORD_BASE + PER_LETTER * letters + EMPHASIS_HOLD;
146
+ } else {
147
+ // Never the same entrance twice in a row.
148
+ let pick = Math.floor(Math.random() * WORD_INS.length);
149
+ if (pick === lastWordIn) pick = (pick + 1) % WORD_INS.length;
150
+ lastWordIn = pick;
151
+ const enter = WORD_INS[pick];
152
+ if (enter) timeline.add(enter(word), t);
153
+ t += WORD_BASE + PER_LETTER * letters;
154
+ }
155
+ wordIndex++;
156
+ if (/[.!?]$/.test(text.trim()) && wordIndex < words.length) t += SENTENCE_PAUSE;
157
+ }
158
+
159
+ return {
160
+ timeline,
161
+ words,
162
+ revert: () => {
163
+ for (const inner of letterSplits) inner.revert();
164
+ split.revert();
165
+ },
166
+ };
167
+ }
168
+ ```
169
+
170
+ ## Wiring
171
+
172
+ ```ts
173
+ // Hero subhead, marked data-speak-intro and hidden by the pre-paint rule:
174
+ const EMPHASIS = ["low", "rizz", { word: "cooked", finish: "broken" }, "negative", "aura", "agents", "animate"];
175
+ const spoken = speakIn(subhead, { emphasis: EMPHASIS, delay: 0.3 }); // starts after the letters land
176
+ // Keep `spoken` alive while the page idles so the finishes persist.
177
+ // On outro: blastOff({ ..., words: spoken.words, ... }) then spoken.revert() once the outro is done with.
178
+ // On unmount: spoken.timeline.kill(); spoken.revert();
179
+ ```
180
+
181
+ A `set(el, { autoAlpha: 0 })` before calling `speakIn` is the initial state when the pre-paint CSS rule is not in use.
@@ -0,0 +1,345 @@
1
+ # Recipe: split entrances
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 these during intro and outro. this module never decides when. Each builder returns a timeline the controller can compose, kill, or await. Display type only.
6
+
7
+ Dependencies: `gsap`, `gsap/SplitText` (free because 3.13). `scrambleIn`/`Out` also need `gsap/ScrambleTextPlugin`. `charsWeightWave` needs a variable face covering its configured `WEIGHT` endpoints (400-800 in the example). adjust those and the midpoint choice to the loaded axis, or select a transform-only recipe. No font family is prescribed.
8
+
9
+ Setup: use [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. For confirmed clipped ink, use the optional `charMaskClass` with the [targeted mask CSS](../text-stability.md#apparent-weight-change-from-clipped-glyph-ink). verify both hidden endpoints after expanding masks. 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 DURATION = { micro: 0.14, component: 0.2, page: 0.28 } as const;
27
+ const EASE = { entrance: "power2.out", exit: "power2.in", shift: "power2.inOut" } as const;
28
+ const STAGGER = { tight: 0.03, loose: 0.05 } as const;
29
+ const WEIGHT = { rest: 400, display: 800 } as const;
30
+
31
+ export type MotionOptions = {
32
+ delay?: number;
33
+ onComplete?: () => void;
34
+ /** One CSS class token for confirmed character-mask clipping; no default padding. */
35
+ charMaskClass?: string;
36
+ };
37
+ export type SplitRunner = (target: HTMLElement | null, options?: MotionOptions) => gsap.core.Timeline;
38
+
39
+ function build(options: MotionOptions): gsap.core.Timeline {
40
+ const timeline = gsap.timeline({ delay: options.delay ?? 0, defaults: { overwrite: "auto" } });
41
+ if (options.onComplete) timeline.eventCallback("onComplete", options.onComplete);
42
+ return timeline;
43
+ }
44
+
45
+ /**
46
+ * Splits, runs `choreograph`, and puts the element back together afterwards.
47
+ * The settled state is applied first so an interrupted run cannot leave text
48
+ * stranded mid-flight. `aria: "auto"` labels the element with the original
49
+ * string and hides the pieces, so a screen reader hears the sentence.
50
+ */
51
+ function withSplit(
52
+ element: HTMLElement | null,
53
+ options: MotionOptions,
54
+ config: SplitText.Vars,
55
+ choreograph: (split: SplitText, tl: gsap.core.Timeline) => void,
56
+ settled: gsap.TweenVars = { autoAlpha: 1 },
57
+ ): gsap.core.Timeline {
58
+ const tl = build(options);
59
+ if (!element) return tl;
60
+ if (prefersReducedMotion()) return tl.set(element, settled);
61
+
62
+ const split = SplitText.create(element, { aria: "auto", ...config });
63
+ if (config.mask === "chars" && options.charMaskClass) {
64
+ for (const mask of split.masks) mask.classList.add(options.charMaskClass);
65
+ }
66
+ tl.set(element, { autoAlpha: 1 });
67
+ choreograph(split, tl);
68
+ tl.eventCallback("onComplete", () => {
69
+ const previous = options.onComplete;
70
+ split.revert();
71
+ previous?.();
72
+ });
73
+ return tl;
74
+ }
75
+
76
+ /** Pins each character to the width it needs at its heaviest, so the weight axis can move without reflow. */
77
+ function pinWidths(chars: Element[], atWeight: number): void {
78
+ for (const char of chars) {
79
+ const element = char as HTMLElement;
80
+ const previous = element.style.fontWeight;
81
+ element.style.fontWeight = String(atWeight);
82
+ const { width } = element.getBoundingClientRect();
83
+ element.style.fontWeight = previous;
84
+ element.style.display = "inline-block";
85
+ element.style.width = `${width}px`;
86
+ element.style.textAlign = "center";
87
+ }
88
+ }
89
+
90
+ /** Characters rise behind masks. Requires persistent target CSS from the stable typography reference. */
91
+ export const charsRiseIn: SplitRunner = (element, options = {}) =>
92
+ withSplit(element, options, { type: "chars", mask: "chars", smartWrap: true }, (split, tl) => {
93
+ tl.from(split.chars, { yPercent: 115, duration: 0.5, ease: "power3.out", stagger: STAGGER.tight });
94
+ });
95
+
96
+ /** Characters spring up with an elastic settle. Unmasked: the overshoot would clip. */
97
+ export const charsSpringIn: SplitRunner = (element, options = {}) =>
98
+ withSplit(element, options, { type: "chars", smartWrap: true }, (split, tl) => {
99
+ tl.from(split.chars, {
100
+ yPercent: 115,
101
+ autoAlpha: 0,
102
+ duration: 1.1,
103
+ ease: "elastic.out(1, 0.5)",
104
+ stagger: STAGGER.tight,
105
+ });
106
+ });
107
+
108
+ /** And back down, in the same order. */
109
+ export const charsFallOut: SplitRunner = (element, options = {}) =>
110
+ withSplit(
111
+ element,
112
+ options,
113
+ { type: "chars", mask: "chars", smartWrap: true },
114
+ (split, tl) => {
115
+ tl.to(split.chars, { yPercent: -115, duration: DURATION.component, ease: "power2.in", stagger: STAGGER.tight }).set(
116
+ element,
117
+ { autoAlpha: 0 },
118
+ );
119
+ },
120
+ { autoAlpha: 0 },
121
+ );
122
+
123
+ /** Characters arrive out of order, like a dealer flicking cards. */
124
+ export const charsCascadeIn: SplitRunner = (element, options = {}) =>
125
+ withSplit(element, options, { type: "chars", smartWrap: true }, (split, tl) => {
126
+ tl.from(split.chars, {
127
+ autoAlpha: 0,
128
+ y: -18,
129
+ rotation: () => gsap.utils.random(-14, 14),
130
+ duration: 0.45,
131
+ ease: "back.out(1.8)",
132
+ stagger: { each: 0.02, from: "random" },
133
+ });
134
+ });
135
+
136
+ export const charsCascadeOut: SplitRunner = (element, options = {}) =>
137
+ withSplit(
138
+ element,
139
+ options,
140
+ { type: "chars", smartWrap: true },
141
+ (split, tl) => {
142
+ tl.to(split.chars, {
143
+ autoAlpha: 0,
144
+ y: 18,
145
+ rotation: () => gsap.utils.random(-14, 14),
146
+ duration: DURATION.component,
147
+ ease: "power2.in",
148
+ stagger: { each: 0.015, from: "random" },
149
+ }).set(element, { autoAlpha: 0 });
150
+ },
151
+ { autoAlpha: 0 },
152
+ );
153
+
154
+ /** Each character tips over its own top edge. */
155
+ export const charsFlipIn: SplitRunner = (element, options = {}) =>
156
+ withSplit(element, options, { type: "chars", smartWrap: true }, (split, tl) => {
157
+ tl.from(split.chars, {
158
+ autoAlpha: 0,
159
+ rotationX: -90,
160
+ transformOrigin: "50% 0%",
161
+ transformPerspective: 600,
162
+ duration: 0.5,
163
+ ease: "back.out(1.4)",
164
+ stagger: STAGGER.tight,
165
+ });
166
+ });
167
+
168
+ export const charsFlipOut: SplitRunner = (element, options = {}) =>
169
+ withSplit(
170
+ element,
171
+ options,
172
+ { type: "chars", smartWrap: true },
173
+ (split, tl) => {
174
+ tl.to(split.chars, {
175
+ autoAlpha: 0,
176
+ rotationX: 90,
177
+ transformOrigin: "50% 100%",
178
+ transformPerspective: 600,
179
+ duration: DURATION.component,
180
+ ease: "power2.in",
181
+ stagger: STAGGER.tight,
182
+ }).set(element, { autoAlpha: 0 });
183
+ },
184
+ { autoAlpha: 0 },
185
+ );
186
+
187
+ /** Characters converge from wherever they were thrown. */
188
+ export const charsScatterIn: SplitRunner = (element, options = {}) =>
189
+ withSplit(element, options, { type: "chars", smartWrap: true }, (split, tl) => {
190
+ tl.from(split.chars, {
191
+ autoAlpha: 0,
192
+ x: () => gsap.utils.random(-120, 120),
193
+ y: () => gsap.utils.random(-60, 60),
194
+ rotation: () => gsap.utils.random(-45, 45),
195
+ scale: 0.6,
196
+ duration: 0.6,
197
+ ease: "power3.out",
198
+ stagger: { each: 0.012, from: "center" },
199
+ });
200
+ });
201
+
202
+ export const charsScatterOut: SplitRunner = (element, options = {}) =>
203
+ withSplit(
204
+ element,
205
+ options,
206
+ { type: "chars", smartWrap: true },
207
+ (split, tl) => {
208
+ tl.to(split.chars, {
209
+ autoAlpha: 0,
210
+ x: () => gsap.utils.random(-120, 120),
211
+ y: () => gsap.utils.random(-60, 60),
212
+ rotation: () => gsap.utils.random(-45, 45),
213
+ scale: 0.6,
214
+ duration: DURATION.page,
215
+ ease: "power2.in",
216
+ stagger: { each: 0.012, from: "edges" },
217
+ }).set(element, { autoAlpha: 0 });
218
+ },
219
+ { autoAlpha: 0 },
220
+ );
221
+
222
+ /**
223
+ * A weight wave through the line: each character dips to the far end of the
224
+ * axis and comes back. Widths are pinned first so letters breathe in place.
225
+ */
226
+ export const charsWeightWave: SplitRunner = (element, options = {}) => {
227
+ const settledWeight = element ? Number(getComputedStyle(element).fontWeight) || WEIGHT.rest : WEIGHT.rest;
228
+ const farWeight = settledWeight >= 600 ? WEIGHT.rest : WEIGHT.display;
229
+ return withSplit(
230
+ element,
231
+ options,
232
+ { type: "chars", smartWrap: true },
233
+ (split, tl) => {
234
+ pinWidths(split.chars, Math.max(settledWeight, farWeight));
235
+ tl.fromTo(
236
+ split.chars,
237
+ { fontWeight: settledWeight },
238
+ { fontWeight: farWeight, duration: 0.3, ease: EASE.shift, stagger: { each: 0.03, from: "start" } },
239
+ ).to(
240
+ split.chars,
241
+ { fontWeight: settledWeight, duration: 0.4, ease: EASE.shift, stagger: { each: 0.03, from: "start" } },
242
+ 0.18,
243
+ );
244
+ },
245
+ { autoAlpha: 1 },
246
+ );
247
+ };
248
+
249
+ /** Words swing in from alternating sides. */
250
+ export const wordsSlideIn: SplitRunner = (element, options = {}) =>
251
+ withSplit(element, options, { type: "words" }, (split, tl) => {
252
+ tl.from(split.words, {
253
+ autoAlpha: 0,
254
+ x: (index: number) => (index % 2 === 0 ? -40 : 40),
255
+ duration: DURATION.page,
256
+ ease: EASE.entrance,
257
+ stagger: STAGGER.loose,
258
+ });
259
+ });
260
+
261
+ export const wordsSlideOut: SplitRunner = (element, options = {}) =>
262
+ withSplit(
263
+ element,
264
+ options,
265
+ { type: "words" },
266
+ (split, tl) => {
267
+ tl.to(split.words, {
268
+ autoAlpha: 0,
269
+ x: (index: number) => (index % 2 === 0 ? 40 : -40),
270
+ duration: DURATION.component,
271
+ ease: EASE.exit,
272
+ stagger: STAGGER.tight,
273
+ }).set(element, { autoAlpha: 0 });
274
+ },
275
+ { autoAlpha: 0 },
276
+ );
277
+
278
+ /** Whole lines wiped up behind masks. The most editorial of the family. */
279
+ export const linesMaskIn: SplitRunner = (element, options = {}) =>
280
+ withSplit(element, options, { type: "lines", mask: "lines" }, (split, tl) => {
281
+ tl.from(split.lines, { yPercent: 110, duration: DURATION.page, ease: "power3.out", stagger: STAGGER.loose });
282
+ });
283
+
284
+ export const linesMaskOut: SplitRunner = (element, options = {}) =>
285
+ withSplit(
286
+ element,
287
+ options,
288
+ { type: "lines", mask: "lines" },
289
+ (split, tl) => {
290
+ tl.to(split.lines, { yPercent: -110, duration: DURATION.component, ease: "power2.in", stagger: STAGGER.tight }).set(
291
+ element,
292
+ { autoAlpha: 0 },
293
+ );
294
+ },
295
+ { autoAlpha: 0 },
296
+ );
297
+ ```
298
+
299
+ ## Scramble
300
+
301
+ Needs `ScrambleTextPlugin` registered. Display only: reading text must never look like it types.
302
+
303
+ ```ts
304
+ export const scrambleIn: SplitRunner = (element, options = {}) => {
305
+ const tl = build(options);
306
+ if (!element) return tl;
307
+ const text = element.textContent ?? "";
308
+ if (prefersReducedMotion()) return tl.set(element, { autoAlpha: 1 });
309
+ return tl.set(element, { autoAlpha: 1 }).to(element, {
310
+ duration: 0.9,
311
+ ease: "none",
312
+ scrambleText: { text, chars: "01{}/<>()=;", speed: 0.6, revealDelay: 0.15 },
313
+ });
314
+ };
315
+
316
+ export const scrambleOut: SplitRunner = (element, options = {}) => {
317
+ const tl = build(options);
318
+ if (!element) return tl;
319
+ if (prefersReducedMotion()) return tl.set(element, { autoAlpha: 0 });
320
+ const text = element.textContent ?? "";
321
+ return tl
322
+ .to(element, { duration: 0.5, ease: "none", scrambleText: { text: text.replace(/\S/g, "0"), chars: "01{}/<>()=;", speed: 0.8 } })
323
+ .to(element, { autoAlpha: 0, duration: DURATION.micro, ease: EASE.exit })
324
+ .call(() => { element.textContent = text; });
325
+ };
326
+ ```
327
+
328
+ ## Wiring
329
+
330
+ ```ts
331
+ // In the framework skill's page controller, after required fonts are ready:
332
+ // heading has the persistent character-headline class from the typography reference.
333
+ // Keep that CSS when withSplit calls split.revert(); smartWrap/mask do not fix kerning.
334
+ const intro = gsap.timeline();
335
+ // Omit charMaskClass unless clipping is confirmed; CSS lives in the typography reference.
336
+ // A CSS Modules caller can pass styles.titleCharMask as the class token.
337
+ const headlineOptions = { charMaskClass: "title-char-mask" };
338
+ intro.add(charsRiseIn(heading, headlineOptions), 0);
339
+ intro.add(linesMaskIn(lede), 0.2);
340
+ // The outro is the paired exit, in reverse order:
341
+ const outro = gsap.timeline();
342
+ outro.add(linesMaskOut(lede), 0).add(charsFallOut(heading, headlineOptions), 0.05);
343
+ ```
344
+
345
+ Give a split heading its own pre-paint hiding rule rather than marking it as a page item too, or the page's stagger and the split is rise will fight over one element.
@@ -0,0 +1,144 @@
1
+ # Recipe: the wave
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
+ Splits a heading into letters and, every few seconds, ripples a small move across them left to right, one letter at a time, the way a crowd does the wave. Each pass uses a different move, and every letter ends exactly where it started, so the text never drifts. An optional ambient effect for display text.
6
+
7
+ The framework skill's controller calls `startWave` at settled (on `idle`) and the returned stop function on outro and unmount; this module never decides when.
8
+
9
+ Dependencies: `gsap`, `gsap/SplitText`. A variable font supporting the configured weight range for weight moves; no particular font family is required. The example rests at 800 and dips through 400-500. Adapt those weights to the target's actual resting weight and axis, or omit the three weight moves for a static face; keep its existing font.
10
+
11
+ 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).
12
+
13
+ ```ts
14
+ import gsap from "gsap";
15
+ import { SplitText } from "gsap/SplitText";
16
+
17
+ gsap.registerPlugin(SplitText);
18
+
19
+ /* Swap for the project's helper if it has one. */
20
+ function prefersReducedMotion(): boolean {
21
+ if (typeof window === "undefined") return true;
22
+ const choice = document.documentElement.dataset.motion;
23
+ if (choice === "reduced") return true;
24
+ if (choice === "full") return false;
25
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
26
+ }
27
+
28
+ /** Delay between neighbouring letters starting their move. */
29
+ const RIPPLE = 0.03;
30
+ /** Each letter is back at rest this many seconds after it starts. */
31
+ const LETTER_TIME = 0.25;
32
+
33
+ const rnd = gsap.utils.random;
34
+ type Move = (chars: HTMLElement[]) => gsap.core.Timeline;
35
+
36
+ /**
37
+ * One out-and-back tween per letter, staggered along the line. The repeat
38
+ * and yoyo live inside the stagger so each letter turns around on its own
39
+ * schedule; at the top level they would send the wave back in reverse.
40
+ */
41
+ function ripple(chars: HTMLElement[], vars: gsap.TweenVars, ease = "sine.inOut") {
42
+ return gsap
43
+ .timeline()
44
+ .to(chars, { ...vars, duration: LETTER_TIME / 2, ease, stagger: { each: RIPPLE, yoyo: true, repeat: 1 } })
45
+ .set(chars, { clearProps: "transform,opacity,fontWeight" });
46
+ }
47
+
48
+ /** A one-way tween per letter for moves that end where they began anyway. */
49
+ function sweep(chars: HTMLElement[], vars: gsap.TweenVars, ease = "power2.inOut") {
50
+ return gsap
51
+ .timeline()
52
+ .to(chars, { ...vars, duration: LETTER_TIME, ease, stagger: RIPPLE })
53
+ .set(chars, { clearProps: "transform,opacity,fontWeight" });
54
+ }
55
+
56
+ /** The heading sits at 800, so every weight move is a dip away from bold and back. */
57
+ const MOVES: Move[] = [
58
+ // Hop: each letter lifts and lands.
59
+ (chars) => ripple(chars, { y: -14 }, "power2.out"),
60
+ // Breathe: a swell from the baseline.
61
+ (chars) => ripple(chars, { scale: 1.07, transformOrigin: "50% 100%" }),
62
+ // Lean: a nod to the right and back.
63
+ (chars) => ripple(chars, { rotation: 7, transformOrigin: "50% 100%" }),
64
+ // Flip: a full turn about the vertical axis, no perspective, so the letter folds to a line and back.
65
+ (chars) => sweep(chars, { rotationY: 360, transformOrigin: "50% 50%" }),
66
+ // Lighten: the weight axis eases down to medium and back.
67
+ (chars) => ripple(chars, { fontWeight: 500 }),
68
+ // Hairline: weight drops to the floor while the letter stretches to keep its footprint.
69
+ (chars) => ripple(chars, { fontWeight: 400, scaleX: 1.14, transformOrigin: "50% 100%" }, "power2.inOut"),
70
+ // Ink: weight snaps to thin, then fills back in to bold.
71
+ (chars) =>
72
+ gsap
73
+ .timeline()
74
+ .fromTo(chars, { fontWeight: 400 }, { fontWeight: 800, duration: LETTER_TIME, ease: "power2.out", stagger: RIPPLE, immediateRender: false })
75
+ .set(chars, { clearProps: "fontWeight" }),
76
+ // Shear: a quick italic slant.
77
+ (chars) => ripple(chars, { skewX: 12 }, "power2.inOut"),
78
+ // Squash: pressed flat and released.
79
+ (chars) => ripple(chars, { scaleY: 0.82, transformOrigin: "50% 100%" }, "power2.inOut"),
80
+ // Twist: each letter swells with its own small twist, then back.
81
+ (chars) => ripple(chars, { scale: 1.1, rotation: () => rnd(-12, 12), transformOrigin: "50% 50%" }, "power2.inOut"),
82
+ ];
83
+
84
+ /** Pins each letter to its resting width so weight changes cannot reflow the line. */
85
+ function pinWidths(chars: HTMLElement[]): void {
86
+ const widths = chars.map((char) => char.getBoundingClientRect().width);
87
+ chars.forEach((char, i) => {
88
+ char.style.display = "inline-block";
89
+ char.style.width = `${widths[i] ?? 0}px`;
90
+ char.style.textAlign = "center";
91
+ });
92
+ }
93
+
94
+ export type WaveOptions = {
95
+ /** Seconds from the start of one wave to the start of the next. */
96
+ period?: number;
97
+ };
98
+
99
+ /**
100
+ * Starts waving the heading's letters. Returns a stop function; pass
101
+ * `keepSplit` when another animation is about to split the same element
102
+ * and needs the current markup left in place.
103
+ */
104
+ export function startWave(heading: HTMLElement, { period = 4 }: WaveOptions = {}): (keepSplit?: boolean) => void {
105
+ if (prefersReducedMotion()) return () => {};
106
+ const split = SplitText.create(heading, { type: "chars,words" });
107
+ const chars = split.chars as HTMLElement[];
108
+ chars.sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left);
109
+ pinWidths(chars);
110
+ gsap.set(chars, { willChange: "transform, opacity" });
111
+
112
+ // Shuffle so each cycle plays in a fresh order, never repeating one across the seam.
113
+ let deck = gsap.utils.shuffle([...MOVES]);
114
+ let index = 0;
115
+ let current: gsap.core.Timeline | undefined;
116
+
117
+ const wave = () => {
118
+ const move = deck[index];
119
+ index++;
120
+ if (index >= deck.length) {
121
+ deck = gsap.utils.shuffle([...MOVES]);
122
+ if (deck[0] === move && deck.length > 1) deck.push(deck.shift() as Move);
123
+ index = 0;
124
+ }
125
+ current?.kill();
126
+ current = move ? move(chars) : undefined;
127
+ };
128
+
129
+ const clock = gsap.delayedCall(period, () => {
130
+ wave();
131
+ clock.restart(true);
132
+ });
133
+
134
+ return (keepSplit = false) => {
135
+ clock.kill();
136
+ current?.kill();
137
+ if (!keepSplit) split.revert();
138
+ };
139
+ }
140
+ ```
141
+
142
+ ## Controller contract
143
+
144
+ `startWave(heading, { period: 1.5 })` returns a stop function. The owner invokes it only after other heading splits are released, and stops it before another effect takes the heading. Use the normal stop path to revert the split; `keepSplit` requires an explicit owner for the retained markup. The controller supplies visibility and resize signals.