@mhmo91/schmancy 0.10.23 → 0.10.25

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 (438) hide show
  1. package/dist/agent/{overlay.confirm-body-xfOh5Q28.js → overlay.confirm-body-DozUyDYx.js} +242 -242
  2. package/dist/agent/{overlay.confirm-body-xfOh5Q28.js.map → overlay.confirm-body-DozUyDYx.js.map} +1 -1
  3. package/dist/agent/schmancy.agent.js +5670 -2939
  4. package/dist/agent/schmancy.agent.js.map +1 -1
  5. package/dist/{area-Ddk7P5wD.js → area-1EG1LrkX.js} +1 -1
  6. package/dist/{area-Ddk7P5wD.js.map → area-1EG1LrkX.js.map} +1 -1
  7. package/dist/{area-Cbkt0NX4.cjs → area-DrVE5pXW.cjs} +1 -1
  8. package/dist/{area-Cbkt0NX4.cjs.map → area-DrVE5pXW.cjs.map} +1 -1
  9. package/dist/area.cjs +1 -1
  10. package/dist/area.js +1 -1
  11. package/dist/{autocomplete-CfBFDSc3.cjs → autocomplete-6pdZxEab.cjs} +1 -1
  12. package/dist/{autocomplete-CfBFDSc3.cjs.map → autocomplete-6pdZxEab.cjs.map} +1 -1
  13. package/dist/{autocomplete-Ds3Q2cwR.js → autocomplete-nrIcCilw.js} +2 -2
  14. package/dist/{autocomplete-Ds3Q2cwR.js.map → autocomplete-nrIcCilw.js.map} +1 -1
  15. package/dist/autocomplete.cjs +1 -1
  16. package/dist/autocomplete.js +1 -1
  17. package/dist/avatar.cjs +2 -2
  18. package/dist/avatar.cjs.map +1 -1
  19. package/dist/avatar.js +3 -3
  20. package/dist/badge.cjs +1 -1
  21. package/dist/badge.js +1 -1
  22. package/dist/{boat-BF5P6p_f.js → boat--fLgbDAE.js} +3 -3
  23. package/dist/{boat-BF5P6p_f.js.map → boat--fLgbDAE.js.map} +1 -1
  24. package/dist/{boat-BPN8HLzZ.cjs → boat-BIB-gPqy.cjs} +1 -1
  25. package/dist/{boat-BPN8HLzZ.cjs.map → boat-BIB-gPqy.cjs.map} +1 -1
  26. package/dist/boat.cjs +1 -1
  27. package/dist/boat.js +1 -1
  28. package/dist/breadcrumb.cjs +1 -1
  29. package/dist/breadcrumb.js +1 -1
  30. package/dist/{busy-C7ejPa-Q.cjs → busy-DVCIxBVf.cjs} +1 -1
  31. package/dist/{busy-C7ejPa-Q.cjs.map → busy-DVCIxBVf.cjs.map} +1 -1
  32. package/dist/{busy-BuACDJy6.js → busy-DshZcVZ4.js} +1 -1
  33. package/dist/{busy-BuACDJy6.js.map → busy-DshZcVZ4.js.map} +1 -1
  34. package/dist/busy.cjs +1 -1
  35. package/dist/busy.js +1 -1
  36. package/dist/{button-C1IMGS6M.js → button-D9UJ7I6Z.js} +5 -5
  37. package/dist/{button-C1IMGS6M.js.map → button-D9UJ7I6Z.js.map} +1 -1
  38. package/dist/{button-CWNbPPq-.cjs → button-JrTMzwHY.cjs} +4 -4
  39. package/dist/{button-CWNbPPq-.cjs.map → button-JrTMzwHY.cjs.map} +1 -1
  40. package/dist/button.cjs +1 -1
  41. package/dist/button.js +1 -1
  42. package/dist/{card-CgQwXO8L.js → card-BvCFuX3J.js} +2 -2
  43. package/dist/{card-CgQwXO8L.js.map → card-BvCFuX3J.js.map} +1 -1
  44. package/dist/{card-BIzaLuEg.cjs → card-CTotavhH.cjs} +1 -1
  45. package/dist/{card-BIzaLuEg.cjs.map → card-CTotavhH.cjs.map} +1 -1
  46. package/dist/card.cjs +1 -1
  47. package/dist/card.js +1 -1
  48. package/dist/{checkbox-BAqE3sTx.cjs → checkbox-By4fFmjt.cjs} +1 -1
  49. package/dist/{checkbox-BAqE3sTx.cjs.map → checkbox-By4fFmjt.cjs.map} +1 -1
  50. package/dist/{checkbox-BNdg57Om.js → checkbox-GPsdCUbs.js} +1 -1
  51. package/dist/{checkbox-BNdg57Om.js.map → checkbox-GPsdCUbs.js.map} +1 -1
  52. package/dist/checkbox.cjs +1 -1
  53. package/dist/checkbox.js +1 -1
  54. package/dist/{chips-DnqLaOb1.js → chips-27umqnat.js} +4 -4
  55. package/dist/{chips-DnqLaOb1.js.map → chips-27umqnat.js.map} +1 -1
  56. package/dist/{chips-DS3y4Lbn.cjs → chips-BZf9sGA8.cjs} +1 -1
  57. package/dist/{chips-DS3y4Lbn.cjs.map → chips-BZf9sGA8.cjs.map} +1 -1
  58. package/dist/chips.cjs +1 -1
  59. package/dist/chips.js +2 -2
  60. package/dist/connectivity.cjs +1 -1
  61. package/dist/connectivity.js +1 -1
  62. package/dist/content-drawer.cjs +1 -1
  63. package/dist/content-drawer.js +1 -1
  64. package/dist/{cursor-glow-Cs2XLDB9.js → cursor-glow-Ah7VXSj7.js} +1 -1
  65. package/dist/{cursor-glow-Cs2XLDB9.js.map → cursor-glow-Ah7VXSj7.js.map} +1 -1
  66. package/dist/{cursor-glow-C8LgCxpI.cjs → cursor-glow-Bulq-38P.cjs} +1 -1
  67. package/dist/{cursor-glow-C8LgCxpI.cjs.map → cursor-glow-Bulq-38P.cjs.map} +1 -1
  68. package/dist/{date-range-VA1mi1N7.cjs → date-range-BJnLWCRF.cjs} +1 -1
  69. package/dist/{date-range-VA1mi1N7.cjs.map → date-range-BJnLWCRF.cjs.map} +1 -1
  70. package/dist/{date-range-inline-CAa0_4EI.cjs → date-range-inline-B6uKUliV.cjs} +1 -1
  71. package/dist/{date-range-inline-CAa0_4EI.cjs.map → date-range-inline-B6uKUliV.cjs.map} +1 -1
  72. package/dist/{date-range-inline-PeRt1iIF.js → date-range-inline-BNbbRfIA.js} +1 -1
  73. package/dist/{date-range-inline-PeRt1iIF.js.map → date-range-inline-BNbbRfIA.js.map} +1 -1
  74. package/dist/date-range-inline.cjs +1 -1
  75. package/dist/date-range-inline.js +1 -1
  76. package/dist/{date-range-CAqB-B0M.js → date-range-wDVHcr0u.js} +2 -2
  77. package/dist/{date-range-CAqB-B0M.js.map → date-range-wDVHcr0u.js.map} +1 -1
  78. package/dist/date-range.cjs +1 -1
  79. package/dist/date-range.js +1 -1
  80. package/dist/delay.cjs +1 -1
  81. package/dist/delay.js +2 -2
  82. package/dist/{details-BpFjVclg.js → details-Ckxpwacj.js} +4 -4
  83. package/dist/{details-BpFjVclg.js.map → details-Ckxpwacj.js.map} +1 -1
  84. package/dist/{details-BnXbDpt7.cjs → details-DNrWIes6.cjs} +1 -1
  85. package/dist/{details-BnXbDpt7.cjs.map → details-DNrWIes6.cjs.map} +1 -1
  86. package/dist/details.cjs +1 -1
  87. package/dist/details.js +1 -1
  88. package/dist/directives-BBMqe8x3.js +4082 -0
  89. package/dist/directives-BBMqe8x3.js.map +1 -0
  90. package/dist/directives-F15SJZUR.cjs +348 -0
  91. package/dist/directives-F15SJZUR.cjs.map +1 -0
  92. package/dist/directives.cjs +1 -99
  93. package/dist/directives.js +6 -1363
  94. package/dist/discovery.cjs +1 -1
  95. package/dist/discovery.js +2 -61
  96. package/dist/discovery.service-COmbHaoI.js +61 -0
  97. package/dist/discovery.service-COmbHaoI.js.map +1 -0
  98. package/dist/discovery.service-CVDXO9rH.cjs +1 -0
  99. package/dist/discovery.service-CVDXO9rH.cjs.map +1 -0
  100. package/dist/{divider-D8cBBkdG.js → divider-BzcZGo4S.js} +1 -1
  101. package/dist/{divider-D8cBBkdG.js.map → divider-BzcZGo4S.js.map} +1 -1
  102. package/dist/{divider-B84lt1A3.cjs → divider-Cde33ivs.cjs} +1 -1
  103. package/dist/{divider-B84lt1A3.cjs.map → divider-Cde33ivs.cjs.map} +1 -1
  104. package/dist/divider.cjs +1 -1
  105. package/dist/divider.js +1 -1
  106. package/dist/dropdown.cjs +1 -1
  107. package/dist/dropdown.js +1 -1
  108. package/dist/{expand-BJiKggfg.js → expand-DI144OzN.js} +3 -3
  109. package/dist/{expand-BJiKggfg.js.map → expand-DI144OzN.js.map} +1 -1
  110. package/dist/{expand-DK-O37-j.cjs → expand-Db4V0jj-.cjs} +1 -1
  111. package/dist/{expand-DK-O37-j.cjs.map → expand-Db4V0jj-.cjs.map} +1 -1
  112. package/dist/expand.cjs +1 -1
  113. package/dist/expand.js +1 -1
  114. package/dist/{float-RWR6Q1Hh.cjs → float--RScf9BZ.cjs} +1 -1
  115. package/dist/{float-RWR6Q1Hh.cjs.map → float--RScf9BZ.cjs.map} +1 -1
  116. package/dist/{float-B4FDN40h.js → float-DIyzy1c2.js} +1 -1
  117. package/dist/{float-B4FDN40h.js.map → float-DIyzy1c2.js.map} +1 -1
  118. package/dist/float.cjs +1 -1
  119. package/dist/float.js +1 -1
  120. package/dist/{form-ha3df3K7.cjs → form-DWNpOsIU.cjs} +1 -1
  121. package/dist/{form-ha3df3K7.cjs.map → form-DWNpOsIU.cjs.map} +1 -1
  122. package/dist/{form-B-Sm6u25.js → form-RtXH8UHQ.js} +8 -8
  123. package/dist/{form-B-Sm6u25.js.map → form-RtXH8UHQ.js.map} +1 -1
  124. package/dist/form.cjs +1 -1
  125. package/dist/form.js +6 -6
  126. package/dist/handover/agent-runtime-followups.md +1 -1
  127. package/dist/handover/agent-runtime-v1.md +3 -3
  128. package/dist/{hashContent-dJrI-9sc.js.map → hashContent-Dgmzc32o.js.map} +1 -1
  129. package/dist/{hashContent-Ck6laKlk.cjs.map → hashContent-Dh1VzIAb.cjs.map} +1 -1
  130. package/dist/icons-DXanGDZ_.js +52 -0
  131. package/dist/icons-DXanGDZ_.js.map +1 -0
  132. package/dist/icons-bNxlWLlk.cjs +24 -0
  133. package/dist/icons-bNxlWLlk.cjs.map +1 -0
  134. package/dist/icons.cjs +1 -1
  135. package/dist/icons.js +1 -1
  136. package/dist/{iframe-BXe1TPx1.cjs → iframe-B1XWRaLC.cjs} +1 -1
  137. package/dist/{iframe-BXe1TPx1.cjs.map → iframe-B1XWRaLC.cjs.map} +1 -1
  138. package/dist/{iframe-CByrVlZy.js → iframe-BlHK0cjy.js} +1 -1
  139. package/dist/{iframe-CByrVlZy.js.map → iframe-BlHK0cjy.js.map} +1 -1
  140. package/dist/iframe.cjs +1 -1
  141. package/dist/iframe.js +1 -1
  142. package/dist/index.cjs +1 -1
  143. package/dist/index.js +60 -60
  144. package/dist/{input-BY9OCQWr.cjs → input-C-_XU9AX.cjs} +1 -1
  145. package/dist/{input-BY9OCQWr.cjs.map → input-C-_XU9AX.cjs.map} +1 -1
  146. package/dist/{input-Q0fm34Co.js → input-CiGa8Dkl.js} +1 -1
  147. package/dist/{input-Q0fm34Co.js.map → input-CiGa8Dkl.js.map} +1 -1
  148. package/dist/{input-chip-BwNf3GD0.cjs → input-chip-5aYnuRZ_.cjs} +1 -1
  149. package/dist/{input-chip-BwNf3GD0.cjs.map → input-chip-5aYnuRZ_.cjs.map} +1 -1
  150. package/dist/{input-chip-CytUirVS.js → input-chip-l--zCMGR.js} +1 -1
  151. package/dist/{input-chip-CytUirVS.js.map → input-chip-l--zCMGR.js.map} +1 -1
  152. package/dist/input.cjs +1 -1
  153. package/dist/input.js +1 -1
  154. package/dist/json.cjs +2 -2
  155. package/dist/json.cjs.map +1 -1
  156. package/dist/json.js +3 -3
  157. package/dist/json.js.map +1 -1
  158. package/dist/kbd.cjs +1 -1
  159. package/dist/kbd.js +1 -1
  160. package/dist/{layout-Dq2oeOTS.js → layout-DSAjo92m.js} +1 -1
  161. package/dist/{layout-Dq2oeOTS.js.map → layout-DSAjo92m.js.map} +1 -1
  162. package/dist/{layout-BbCIfIgo.cjs → layout-eXb9wjDh.cjs} +1 -1
  163. package/dist/{layout-BbCIfIgo.cjs.map → layout-eXb9wjDh.cjs.map} +1 -1
  164. package/dist/layout.cjs +1 -1
  165. package/dist/layout.js +1 -1
  166. package/dist/{lightbox-p2E0oVR0.cjs → lightbox-CfRDkeeb.cjs} +2 -2
  167. package/dist/{lightbox-p2E0oVR0.cjs.map → lightbox-CfRDkeeb.cjs.map} +1 -1
  168. package/dist/{lightbox-Ckvn5YNF.js → lightbox-D9oiu1Nv.js} +2 -2
  169. package/dist/{lightbox-Ckvn5YNF.js.map → lightbox-D9oiu1Nv.js.map} +1 -1
  170. package/dist/lightbox.cjs +1 -1
  171. package/dist/lightbox.js +1 -1
  172. package/dist/{list-CsrPVvmm.js → list-BOlRka4v.js} +1 -1
  173. package/dist/{list-CsrPVvmm.js.map → list-BOlRka4v.js.map} +1 -1
  174. package/dist/{list-r57UFHu3.cjs → list-CDJi3_Ut.cjs} +1 -1
  175. package/dist/{list-r57UFHu3.cjs.map → list-CDJi3_Ut.cjs.map} +1 -1
  176. package/dist/list.cjs +1 -1
  177. package/dist/list.js +1 -1
  178. package/dist/{magnetic-Bgh7aHHI.cjs → magnetic-D-ph029G.cjs} +1 -1
  179. package/dist/{magnetic-Bgh7aHHI.cjs.map → magnetic-D-ph029G.cjs.map} +1 -1
  180. package/dist/{magnetic-DxvoEz8_.js → magnetic-mHXl54Z8.js} +1 -1
  181. package/dist/{magnetic-DxvoEz8_.js.map → magnetic-mHXl54Z8.js.map} +1 -1
  182. package/dist/{menu-DBuZiPyW.cjs → menu-CJaDL2cd.cjs} +1 -1
  183. package/dist/{menu-DBuZiPyW.cjs.map → menu-CJaDL2cd.cjs.map} +1 -1
  184. package/dist/{menu-Csm6Fg88.js → menu-XyrLmCi_.js} +2 -2
  185. package/dist/{menu-Csm6Fg88.js.map → menu-XyrLmCi_.js.map} +1 -1
  186. package/dist/menu.cjs +1 -1
  187. package/dist/menu.js +1 -1
  188. package/dist/mixins-CsYsIJOI.cjs +254 -0
  189. package/dist/mixins-CsYsIJOI.cjs.map +1 -0
  190. package/dist/mixins-DySzfmal.js +642 -0
  191. package/dist/mixins-DySzfmal.js.map +1 -0
  192. package/dist/mixins.cjs +1 -1
  193. package/dist/mixins.js +2 -2
  194. package/dist/nav-drawer.cjs +1 -1
  195. package/dist/nav-drawer.js +1 -1
  196. package/dist/navigation-bar.cjs +1 -1
  197. package/dist/navigation-bar.js +1 -1
  198. package/dist/navigation-rail.cjs +5 -9
  199. package/dist/navigation-rail.cjs.map +1 -1
  200. package/dist/navigation-rail.js +5 -11
  201. package/dist/navigation-rail.js.map +1 -1
  202. package/dist/{notification-CgTBiAdf.js → notification-CHrEY4u8.js} +2 -2
  203. package/dist/{notification-CgTBiAdf.js.map → notification-CHrEY4u8.js.map} +1 -1
  204. package/dist/{notification-58tkVys8.cjs → notification-DKp4tjaB.cjs} +1 -1
  205. package/dist/{notification-58tkVys8.cjs.map → notification-DKp4tjaB.cjs.map} +1 -1
  206. package/dist/notification.cjs +1 -1
  207. package/dist/notification.js +1 -1
  208. package/dist/{option-Bicf6xpI.js → option-Vpy4UQ-D.js} +1 -1
  209. package/dist/{option-Bicf6xpI.js.map → option-Vpy4UQ-D.js.map} +1 -1
  210. package/dist/{option-61YE3gub.cjs → option-nRk4MuXH.cjs} +1 -1
  211. package/dist/{option-61YE3gub.cjs.map → option-nRk4MuXH.cjs.map} +1 -1
  212. package/dist/option.cjs +1 -1
  213. package/dist/option.js +1 -1
  214. package/dist/{overlay-CpvmytrQ.cjs → overlay-HNrWZ4sB.cjs} +1 -1
  215. package/dist/{overlay-CpvmytrQ.cjs.map → overlay-HNrWZ4sB.cjs.map} +1 -1
  216. package/dist/{overlay-CAI2FAp7.js → overlay-jlkcrt8F.js} +5 -5
  217. package/dist/{overlay-CAI2FAp7.js.map → overlay-jlkcrt8F.js.map} +1 -1
  218. package/dist/{overlay-stack-Dk0xETTy.cjs.map → overlay-stack-Bdr9lOqi.cjs.map} +1 -1
  219. package/dist/{overlay-stack-BR4iYivO.js.map → overlay-stack-D2rgxQLh.js.map} +1 -1
  220. package/dist/overlay.cjs +1 -1
  221. package/dist/{overlay.confirm-body-QD-5cj3_.cjs → overlay.confirm-body-B8dFI3cj.cjs} +1 -1
  222. package/dist/{overlay.confirm-body-QD-5cj3_.cjs.map → overlay.confirm-body-B8dFI3cj.cjs.map} +1 -1
  223. package/dist/{overlay.confirm-body-Cq25CkTw.js → overlay.confirm-body-CYShkjI6.js} +1 -1
  224. package/dist/{overlay.confirm-body-Cq25CkTw.js.map → overlay.confirm-body-CYShkjI6.js.map} +1 -1
  225. package/dist/overlay.js +3 -3
  226. package/dist/{overlay.service-BG0bqPwJ.cjs → overlay.service-BTPn7Uv7.cjs} +1 -1
  227. package/dist/{overlay.service-BG0bqPwJ.cjs.map → overlay.service-BTPn7Uv7.cjs.map} +1 -1
  228. package/dist/{overlay.service-Bpjrhaxh.js → overlay.service-BqhhxVJp.js} +2 -2
  229. package/dist/{overlay.service-Bpjrhaxh.js.map → overlay.service-BqhhxVJp.js.map} +1 -1
  230. package/dist/{progress-Zqx-S9NZ.js → progress-8Bn88GK_.js} +1 -1
  231. package/dist/{progress-Zqx-S9NZ.js.map → progress-8Bn88GK_.js.map} +1 -1
  232. package/dist/{progress-D8XZJVl5.cjs → progress-CAp_4jtq.cjs} +1 -1
  233. package/dist/{progress-D8XZJVl5.cjs.map → progress-CAp_4jtq.cjs.map} +1 -1
  234. package/dist/progress.cjs +1 -1
  235. package/dist/progress.js +1 -1
  236. package/dist/{radio-group-bl8K4Gls.cjs → radio-group-CN44mAoc.cjs} +1 -1
  237. package/dist/{radio-group-bl8K4Gls.cjs.map → radio-group-CN44mAoc.cjs.map} +1 -1
  238. package/dist/{radio-group-D9MU1Mxz.js → radio-group-GNHA7qJR.js} +1 -1
  239. package/dist/{radio-group-D9MU1Mxz.js.map → radio-group-GNHA7qJR.js.map} +1 -1
  240. package/dist/radio-group.cjs +1 -1
  241. package/dist/radio-group.js +1 -1
  242. package/dist/range.cjs +1 -1
  243. package/dist/range.js +1 -1
  244. package/dist/{reduced-motion-D7LqTUMn.js.map → reduced-motion-D-L12p7G.js.map} +1 -1
  245. package/dist/{reduced-motion-Dzfp_w5x.cjs.map → reduced-motion-Ds-HjMzn.cjs.map} +1 -1
  246. package/dist/{rxjs-utils-BK8VMe3K.js.map → rxjs-utils-BXpvHN4-.js.map} +1 -1
  247. package/dist/{rxjs-utils-DhOKenkS.cjs.map → rxjs-utils-CaC-tdot.cjs.map} +1 -1
  248. package/dist/rxjs-utils.cjs +1 -1
  249. package/dist/rxjs-utils.js +1 -1
  250. package/dist/{select-CMwkl-D6.js → select-BnuXRHS4.js} +3 -3
  251. package/dist/{select-CMwkl-D6.js.map → select-BnuXRHS4.js.map} +1 -1
  252. package/dist/{select-COIfVtZl.cjs → select-DZNns5Pa.cjs} +2 -2
  253. package/dist/{select-COIfVtZl.cjs.map → select-DZNns5Pa.cjs.map} +1 -1
  254. package/dist/select.cjs +1 -1
  255. package/dist/select.js +1 -1
  256. package/dist/skeleton.cjs +1 -1
  257. package/dist/skeleton.js +1 -1
  258. package/dist/skills/INDEX.md +2 -1
  259. package/dist/skills/SKILL.md +33 -23
  260. package/dist/skills/area.md +5 -4
  261. package/dist/skills/connectivity.md +1 -3
  262. package/dist/skills/directives.md +36 -0
  263. package/dist/skills/icons.md +95 -31
  264. package/dist/skills/layout.md +36 -53
  265. package/dist/skills/mixins.md +26 -5
  266. package/dist/skills/schmancy/INDEX.md +2 -1
  267. package/dist/skills/schmancy/SKILL.md +33 -23
  268. package/dist/skills/schmancy/area.md +5 -4
  269. package/dist/skills/schmancy/connectivity.md +1 -3
  270. package/dist/skills/schmancy/directives.md +36 -0
  271. package/dist/skills/schmancy/icons.md +95 -31
  272. package/dist/skills/schmancy/layout.md +36 -53
  273. package/dist/skills/schmancy/mixins.md +26 -5
  274. package/dist/slider.cjs +1 -1
  275. package/dist/slider.js +1 -1
  276. package/dist/{splash-screen-xrMNpzkm.js → splash-screen-CUP_elaT.js} +1 -1
  277. package/dist/{splash-screen-xrMNpzkm.js.map → splash-screen-CUP_elaT.js.map} +1 -1
  278. package/dist/{splash-screen-2hxq8Sft.cjs → splash-screen-DeoPRrOu.cjs} +1 -1
  279. package/dist/{splash-screen-2hxq8Sft.cjs.map → splash-screen-DeoPRrOu.cjs.map} +1 -1
  280. package/dist/splash-screen.cjs +1 -1
  281. package/dist/splash-screen.js +1 -1
  282. package/dist/{src-B15R32Sp.js → src-B1VkLX3l.js} +159 -159
  283. package/dist/src-B1VkLX3l.js.map +1 -0
  284. package/dist/{src-BWQvtOOf.cjs → src-DQ4wr0qq.cjs} +13 -13
  285. package/dist/src-DQ4wr0qq.cjs.map +1 -0
  286. package/dist/steps.cjs +1 -1
  287. package/dist/steps.js +1 -1
  288. package/dist/{surface-BkQ44Wuo.cjs → surface-LkaZQXZn.cjs} +1 -1
  289. package/dist/{surface-BkQ44Wuo.cjs.map → surface-LkaZQXZn.cjs.map} +1 -1
  290. package/dist/{surface-3nnvlxeE.js → surface-hOvkrjGN.js} +1 -1
  291. package/dist/{surface-3nnvlxeE.js.map → surface-hOvkrjGN.js.map} +1 -1
  292. package/dist/surface.cjs +1 -1
  293. package/dist/surface.js +1 -1
  294. package/dist/switch.cjs +1 -1
  295. package/dist/switch.js +1 -1
  296. package/dist/table.cjs +2 -2
  297. package/dist/table.cjs.map +1 -1
  298. package/dist/table.js +2 -2
  299. package/dist/table.js.map +1 -1
  300. package/dist/{tabs-CnLIe8nE.js → tabs-CfwIHhHo.js} +1 -1
  301. package/dist/{tabs-CnLIe8nE.js.map → tabs-CfwIHhHo.js.map} +1 -1
  302. package/dist/{tabs-Dql0rcqZ.cjs → tabs-bplzstz6.cjs} +1 -1
  303. package/dist/{tabs-Dql0rcqZ.cjs.map → tabs-bplzstz6.cjs.map} +1 -1
  304. package/dist/tabs.cjs +1 -1
  305. package/dist/tabs.js +1 -1
  306. package/dist/teleport.cjs +1 -1
  307. package/dist/teleport.js +1 -1
  308. package/dist/{textarea-BAogS_Ff.js → textarea-C1A5xuw9.js} +1 -1
  309. package/dist/{textarea-BAogS_Ff.js.map → textarea-C1A5xuw9.js.map} +1 -1
  310. package/dist/{textarea-CGD6lAEe.cjs → textarea-hrDp5gQq.cjs} +1 -1
  311. package/dist/{textarea-CGD6lAEe.cjs.map → textarea-hrDp5gQq.cjs.map} +1 -1
  312. package/dist/textarea.cjs +1 -1
  313. package/dist/textarea.js +1 -1
  314. package/dist/{theme-CUK0HrS3.js → theme-BniFOMEo.js} +4 -4
  315. package/dist/{theme-CUK0HrS3.js.map → theme-BniFOMEo.js.map} +1 -1
  316. package/dist/{theme-DKrrQ-ic.cjs → theme-DmR6PKV8.cjs} +3 -3
  317. package/dist/{theme-DKrrQ-ic.cjs.map → theme-DmR6PKV8.cjs.map} +1 -1
  318. package/dist/{theme-button-Bb8qW2IH.js → theme-button--ruZIb0T.js} +1 -1
  319. package/dist/{theme-button-Bb8qW2IH.js.map → theme-button--ruZIb0T.js.map} +1 -1
  320. package/dist/{theme-button-CmTwFm3l.cjs → theme-button-a0LgZ7hQ.cjs} +1 -1
  321. package/dist/{theme-button-CmTwFm3l.cjs.map → theme-button-a0LgZ7hQ.cjs.map} +1 -1
  322. package/dist/theme-button.cjs +1 -1
  323. package/dist/theme-button.js +1 -1
  324. package/dist/theme.cjs +1 -1
  325. package/dist/{theme.interface-D4NeufQA.cjs.map → theme.interface-B5xjEk74.cjs.map} +1 -1
  326. package/dist/{theme.interface-C2XNgsLB.js.map → theme.interface-DVEw3s8m.js.map} +1 -1
  327. package/dist/theme.js +4 -4
  328. package/dist/{theme.service-CSzNkqBB.js.map → theme.service-Bh08uOSJ.js.map} +1 -1
  329. package/dist/{theme.service-CnFUmUpc.cjs.map → theme.service-Y-e8b331.cjs.map} +1 -1
  330. package/dist/tree.cjs +1 -1
  331. package/dist/tree.js +1 -1
  332. package/dist/typography.cjs +1 -1
  333. package/dist/typography.js +1 -1
  334. package/dist/{utils-Cxg0Kfy5.js → utils-578eFTx4.js} +1 -1
  335. package/dist/{utils-Cxg0Kfy5.js.map → utils-578eFTx4.js.map} +1 -1
  336. package/dist/{utils-aCJYAGUr.cjs → utils-CVWUrECT.cjs} +1 -1
  337. package/dist/{utils-aCJYAGUr.cjs.map → utils-CVWUrECT.cjs.map} +1 -1
  338. package/dist/utils.cjs +1 -1
  339. package/dist/utils.js +2 -2
  340. package/dist/visually-hidden.cjs +1 -1
  341. package/dist/visually-hidden.js +1 -1
  342. package/dist/{window-DuDAQa6y.js → window-BT9JecWy.js} +5 -5
  343. package/dist/{window-DuDAQa6y.js.map → window-BT9JecWy.js.map} +1 -1
  344. package/dist/{window-BbWlaPZv.cjs → window-Bp7zWZpu.cjs} +1 -1
  345. package/dist/{window-BbWlaPZv.cjs.map → window-Bp7zWZpu.cjs.map} +1 -1
  346. package/dist/window.cjs +1 -1
  347. package/dist/window.js +1 -1
  348. package/package.json +1 -1
  349. package/skills/schmancy/INDEX.md +2 -1
  350. package/skills/schmancy/SKILL.md +33 -23
  351. package/skills/schmancy/area.md +5 -4
  352. package/skills/schmancy/connectivity.md +1 -3
  353. package/skills/schmancy/directives.md +36 -0
  354. package/skills/schmancy/icons.md +95 -31
  355. package/skills/schmancy/layout.md +36 -53
  356. package/skills/schmancy/mixins.md +26 -5
  357. package/src/badge/badge.ts +7 -11
  358. package/src/button/icon-button.ts +3 -4
  359. package/src/directives/ai-badge.ts +95 -0
  360. package/src/directives/art/art.directive.ts +228 -0
  361. package/src/directives/art/effects/error.ts +192 -0
  362. package/src/directives/art/effects/funkhaus.ts +121 -0
  363. package/src/directives/art/effects/howl.ts +203 -0
  364. package/src/directives/art/effects/samwa.ts +228 -0
  365. package/src/directives/art/effects/snow.ts +207 -0
  366. package/src/directives/art/effects/starfield.ts +107 -0
  367. package/src/directives/art/index.ts +2 -0
  368. package/src/directives/art/particle-pool.ts +40 -0
  369. package/src/directives/art/types.ts +129 -0
  370. package/src/directives/art/utils.ts +35 -0
  371. package/src/directives/battery.ts +1014 -0
  372. package/src/directives/beta.ts +44 -0
  373. package/src/directives/fill.ts +32 -60
  374. package/src/directives/fyi.ts +551 -0
  375. package/src/directives/hummingbird.ts +1712 -0
  376. package/src/directives/index.ts +9 -0
  377. package/src/directives/missed-punch.ts +407 -0
  378. package/src/directives/urgent.ts +660 -0
  379. package/src/directives/working-snake.ts +294 -0
  380. package/src/icons/icon.ts +53 -30
  381. package/src/json/json.ts +1 -1
  382. package/src/navigation-rail/navigation-rail-item.ts +7 -12
  383. package/src/navigation-rail/navigation-rail.ts +0 -2
  384. package/src/table/table.ts +2 -2
  385. package/src/theme/theme-audio-player.ts +1 -1
  386. package/types/mixins/SchmancyElement.d.ts +31 -0
  387. package/types/src/badge/badge.d.ts +1 -1
  388. package/types/src/directives/ai-badge.d.ts +15 -0
  389. package/types/src/directives/art/art.directive.d.ts +19 -0
  390. package/types/src/directives/art/effects/error.d.ts +7 -0
  391. package/types/src/directives/art/effects/funkhaus.d.ts +7 -0
  392. package/types/src/directives/art/effects/howl.d.ts +7 -0
  393. package/types/src/directives/art/effects/samwa.d.ts +7 -0
  394. package/types/src/directives/art/effects/snow.d.ts +7 -0
  395. package/types/src/directives/art/effects/starfield.d.ts +10 -0
  396. package/types/src/directives/art/index.d.ts +2 -0
  397. package/types/src/directives/art/particle-pool.d.ts +16 -0
  398. package/types/src/directives/art/types.d.ts +126 -0
  399. package/types/src/directives/art/utils.d.ts +5 -0
  400. package/types/src/directives/battery.d.ts +96 -0
  401. package/types/src/directives/beta.d.ts +33 -0
  402. package/types/src/directives/fill.d.ts +4 -11
  403. package/types/src/directives/fyi.d.ts +76 -0
  404. package/types/src/directives/hummingbird.d.ts +180 -0
  405. package/types/src/directives/index.d.ts +9 -0
  406. package/types/src/directives/missed-punch.d.ts +28 -0
  407. package/types/src/directives/urgent.d.ts +88 -0
  408. package/types/src/directives/working-snake.d.ts +46 -0
  409. package/types/src/icons/icon.d.ts +22 -0
  410. package/dist/directives.cjs.map +0 -1
  411. package/dist/directives.js.map +0 -1
  412. package/dist/discovery.cjs.map +0 -1
  413. package/dist/discovery.js.map +0 -1
  414. package/dist/icons-DJuXwn8D.js +0 -48
  415. package/dist/icons-DJuXwn8D.js.map +0 -1
  416. package/dist/icons-oNRUCAEY.cjs +0 -33
  417. package/dist/icons-oNRUCAEY.cjs.map +0 -1
  418. package/dist/mixins-DTCHPEd4.cjs +0 -254
  419. package/dist/mixins-DTCHPEd4.cjs.map +0 -1
  420. package/dist/mixins-pU53qf6R.js +0 -636
  421. package/dist/mixins-pU53qf6R.js.map +0 -1
  422. package/dist/skills/page.md +0 -84
  423. package/dist/skills/schmancy/page.md +0 -84
  424. package/dist/src-B15R32Sp.js.map +0 -1
  425. package/dist/src-BWQvtOOf.cjs.map +0 -1
  426. package/skills/schmancy/page.md +0 -84
  427. /package/dist/{hashContent-dJrI-9sc.js → hashContent-Dgmzc32o.js} +0 -0
  428. /package/dist/{hashContent-Ck6laKlk.cjs → hashContent-Dh1VzIAb.cjs} +0 -0
  429. /package/dist/{overlay-stack-Dk0xETTy.cjs → overlay-stack-Bdr9lOqi.cjs} +0 -0
  430. /package/dist/{overlay-stack-BR4iYivO.js → overlay-stack-D2rgxQLh.js} +0 -0
  431. /package/dist/{reduced-motion-D7LqTUMn.js → reduced-motion-D-L12p7G.js} +0 -0
  432. /package/dist/{reduced-motion-Dzfp_w5x.cjs → reduced-motion-Ds-HjMzn.cjs} +0 -0
  433. /package/dist/{rxjs-utils-BK8VMe3K.js → rxjs-utils-BXpvHN4-.js} +0 -0
  434. /package/dist/{rxjs-utils-DhOKenkS.cjs → rxjs-utils-CaC-tdot.cjs} +0 -0
  435. /package/dist/{theme.interface-D4NeufQA.cjs → theme.interface-B5xjEk74.cjs} +0 -0
  436. /package/dist/{theme.interface-C2XNgsLB.js → theme.interface-DVEw3s8m.js} +0 -0
  437. /package/dist/{theme.service-CSzNkqBB.js → theme.service-Bh08uOSJ.js} +0 -0
  438. /package/dist/{theme.service-CnFUmUpc.cjs → theme.service-Y-e8b331.cjs} +0 -0
@@ -0,0 +1,1712 @@
1
+ /**
2
+ * Hummingbird Directive - Realistic hummingbird flight animation
3
+ *
4
+ * PERFORMANCE OPTIMIZATIONS:
5
+ * 1. Use CSS transforms only (GPU-accelerated, no layout/paint)
6
+ * 2. will-change hints for compositor optimization
7
+ * 3. Single RAF loop, no nested animations
8
+ * 4. Minimize DOM operations
9
+ * 5. Pre-calculate values where possible
10
+ * 6. Use CSS animations for constant motion (wing blur)
11
+ *
12
+ * Based on scientific research:
13
+ * - Wing beat ~43Hz (PMC3311889)
14
+ * - Forward speed 13 m/s / 30mph (Britannica)
15
+ * - Figure-8 hover pattern (Royal Society)
16
+ *
17
+ * ANIMATION STATES (State Machine):
18
+ * - idle: waiting for delay
19
+ * - flying: moving between waypoints
20
+ * - hovering: figure-8 pattern at waypoint
21
+ * - spiraling: approaching black hole
22
+ * - absorbed: being sucked into black hole
23
+ * - done: cleanup complete
24
+ */
25
+
26
+ import { noChange } from 'lit'
27
+ import { AsyncDirective, directive } from 'lit/async-directive.js'
28
+ import type { ElementPart } from 'lit'
29
+ import { BehaviorSubject, EMPTY, Observable, forkJoin, timer, fromEvent, combineLatest } from 'rxjs'
30
+ import { distinctUntilChanged, filter, map, switchMap, takeUntil, tap, take, debounceTime, startWith } from 'rxjs/operators'
31
+ import { discover } from '../discovery/discovery.service'
32
+ import { ThemeWhereAreYou, ThemeHereIAm } from '../theme/theme.events'
33
+
34
+ // Physics constants
35
+ const FORWARD_SPEED_PX_S = 200 // Pixels per second
36
+ const PAUSE_DURATION_MS = 600 // Brief pause at each waypoint
37
+
38
+ // Animation states
39
+ type AnimationState = 'idle' | 'flying' | 'hovering' | 'spiraling' | 'absorbed' | 'done'
40
+
41
+ /**
42
+ * Element reference for waypoints/target:
43
+ * - CSS selector (e.g., '#app-card-melanie', '.my-class') - event-based discovery
44
+ * - Component tag name (e.g., 'schmancy-fancy') - event-based discovery
45
+ * - HTMLElement reference directly
46
+ */
47
+ type ElementRef = string | HTMLElement
48
+
49
+ /** Waypoint with optional duration control */
50
+ interface Waypoint {
51
+ ref: ElementRef
52
+ duration?: number // How long to pause at this waypoint (ms)
53
+ }
54
+
55
+ interface HummingbirdOptions {
56
+ /** Waypoints the bird visits in sequence */
57
+ waypoints?: (ElementRef | Waypoint)[]
58
+ /** Where the bird returns home (triggers black hole effect). If omitted, bird fades out after last waypoint. */
59
+ home?: ElementRef
60
+ /** Delay before starting flight (ms) */
61
+ delay?: number
62
+ /** Whether the hummingbird should be playing. When false, cleans up and pauses. Defaults to true. */
63
+ playing?: boolean
64
+ /** Show animated connection lines between visited waypoints. Defaults to false. */
65
+ showConnections?: boolean
66
+ }
67
+
68
+ interface Destination {
69
+ x: number
70
+ y: number
71
+ element?: HTMLElement // The actual element for theme discovery
72
+ duration: number // How long to pause here
73
+ }
74
+
75
+ interface AnimationContext {
76
+ // State machine
77
+ state: AnimationState
78
+ // Position tracking
79
+ cx: number
80
+ cy: number
81
+ // Flight parameters
82
+ flightStartX: number
83
+ flightStartY: number
84
+ phaseStart: number
85
+ destIdx: number
86
+ // Destinations
87
+ destinations: Destination[]
88
+ // Black hole position
89
+ bx: number
90
+ by: number
91
+ }
92
+
93
+ // Shared keyframes - inject once
94
+ let keyframesInjected = false
95
+ function injectKeyframes(): void {
96
+ if (keyframesInjected) return
97
+ keyframesInjected = true
98
+
99
+ const style = document.createElement('style')
100
+ style.id = 'hb-keyframes'
101
+ style.textContent = `
102
+ @keyframes hb-wing {
103
+ 0% { transform: scaleY(0.85); opacity: 0.4; }
104
+ 100% { transform: scaleY(1.15); opacity: 0.6; }
105
+ }
106
+ @keyframes hb-disk-inner {
107
+ to { transform: translate(-50%, -50%) rotateX(75deg) rotate(360deg); }
108
+ }
109
+ @keyframes hb-disk-outer {
110
+ to { transform: translate(-50%, -50%) rotateX(70deg) rotate(-360deg); }
111
+ }
112
+ @keyframes hb-disk-mid {
113
+ to { transform: translate(-50%, -50%) rotateX(72deg) rotate(180deg); }
114
+ }
115
+ @keyframes hb-bh-in {
116
+ 0% { transform: translate(-50%, -50%) scale(0); }
117
+ 60% { transform: translate(-50%, -50%) scale(1.1); }
118
+ 100% { transform: translate(-50%, -50%) scale(1); }
119
+ }
120
+ @keyframes hb-horizon-pulse {
121
+ 0%, 100% { box-shadow: 0 0 20px 6px var(--hb-color), 0 0 40px 12px var(--hb-color-dim); }
122
+ 50% { box-shadow: 0 0 30px 10px var(--hb-color), 0 0 60px 20px var(--hb-color-dim); }
123
+ }
124
+ @keyframes hb-lensing {
125
+ 0% { transform: translate(-50%, -50%) rotateX(85deg) scale(1); opacity: 0.6; }
126
+ 50% { transform: translate(-50%, -50%) rotateX(85deg) scale(1.05); opacity: 0.8; }
127
+ 100% { transform: translate(-50%, -50%) rotateX(85deg) scale(1); opacity: 0.6; }
128
+ }
129
+ @keyframes hb-particle-orbit {
130
+ 0% { transform: rotate(0deg) translateX(var(--orbit-radius)) rotate(0deg) scale(1); opacity: 0.9; }
131
+ 100% { transform: rotate(720deg) translateX(0px) rotate(-720deg) scale(0); opacity: 0; }
132
+ }
133
+ @keyframes hb-ripple {
134
+ 0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0.8; }
135
+ 100% { transform: translate(-50%, -50%) scale(3); opacity: 0; }
136
+ }
137
+ `
138
+ document.head.appendChild(style)
139
+ }
140
+
141
+ /**
142
+ * Cosmic nebula color palette - based on real emission spectra
143
+ * H-alpha (656nm) - Crimson ionized hydrogen
144
+ * OIII (501nm) - Teal ionized oxygen
145
+ * SII (672nm) - Deep burgundy sulfur
146
+ * Reflection blue - scattered starlight
147
+ */
148
+ const COSMIC_COLORS = {
149
+ // H-alpha crimson
150
+ hAlpha: 'rgba(200, 50, 100, 0.85)',
151
+ hAlphaDim: 'rgba(150, 30, 80, 0.5)',
152
+ // OIII teal
153
+ oiii: 'rgba(40, 180, 180, 0.7)',
154
+ oiiiDim: 'rgba(30, 140, 150, 0.35)',
155
+ // Reflection/stellar
156
+ stellar: 'rgba(255, 230, 240, 0.9)',
157
+ stellarDim: 'rgba(255, 180, 200, 0.4)',
158
+ // Deep space
159
+ deepPurple: 'rgba(80, 40, 120, 0.6)',
160
+ deepBlue: 'rgba(20, 40, 100, 0.5)',
161
+ // Event horizon
162
+ voidBlack: 'rgba(0, 0, 0, 1)',
163
+ singularity: 'rgba(10, 5, 20, 1)',
164
+ }
165
+
166
+ /**
167
+ * Glowing spark - simple, elegant light guide
168
+ */
169
+ const createBirdSVG = (color: string) => `<svg viewBox="0 0 24 24" width="24" height="24" style="overflow:visible">
170
+ <defs>
171
+ <radialGradient id="glow">
172
+ <stop offset="0%" stop-color="${color}" stop-opacity="1"/>
173
+ <stop offset="50%" stop-color="${color}" stop-opacity="0.4"/>
174
+ <stop offset="100%" stop-color="${color}" stop-opacity="0"/>
175
+ </radialGradient>
176
+ </defs>
177
+
178
+ <!-- Outer glow -->
179
+ <circle cx="12" cy="12" r="10" fill="url(#glow)"/>
180
+
181
+ <!-- Core -->
182
+ <circle cx="12" cy="12" r="4" fill="${color}"/>
183
+
184
+ <!-- Bright center -->
185
+ <circle cx="12" cy="12" r="2" fill="white" opacity="0.9"/>
186
+ </svg>`
187
+
188
+ /**
189
+ * Convert hex color to rgba with alpha
190
+ */
191
+ function hexToRgba(hex: string, alpha: number): string {
192
+ const r = parseInt(hex.slice(1, 3), 16)
193
+ const g = parseInt(hex.slice(3, 5), 16)
194
+ const b = parseInt(hex.slice(5, 7), 16)
195
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`
196
+ }
197
+
198
+ class HummingbirdDirective extends AsyncDirective {
199
+ private state$ = new BehaviorSubject<AnimationState>('idle')
200
+ private destroyed$ = new BehaviorSubject<boolean>(false)
201
+ private visible$ = new BehaviorSubject<boolean>(true)
202
+ private bird: HTMLElement | null = null
203
+ private blackHole: HTMLElement | null = null
204
+ private particles: HTMLElement[] = []
205
+ private trailCanvas: HTMLCanvasElement | null = null
206
+ private trailCtx: CanvasRenderingContext2D | null = null
207
+ private lastTrailPos: { x: number; y: number } | null = null
208
+ private rafId = 0
209
+ private context: AnimationContext | null = null
210
+ private element: HTMLElement | null = null
211
+ private options: HummingbirdOptions = {}
212
+ private hasHome = false
213
+
214
+ /**
215
+ * Convert viewport coordinates to host-relative coordinates.
216
+ * getBoundingClientRect() returns viewport coords; we subtract the host's rect.
217
+ */
218
+ private toHostRelative(viewportX: number, viewportY: number): { x: number; y: number } {
219
+ if (!this.element) return { x: viewportX, y: viewportY }
220
+ const hostRect = this.element.getBoundingClientRect()
221
+ return {
222
+ x: viewportX - hostRect.left + this.element.scrollLeft,
223
+ y: viewportY - hostRect.top + this.element.scrollTop,
224
+ }
225
+ }
226
+
227
+ render(_options?: boolean | HummingbirdOptions) {
228
+ return noChange
229
+ }
230
+
231
+ override update(part: ElementPart, [options = true]: [boolean | HummingbirdOptions | undefined]) {
232
+ const element = part.element as HTMLElement
233
+ const opts: HummingbirdOptions = typeof options === 'object' ? options : {}
234
+ // Check both: boolean false OR options.playing === false
235
+ const shouldPlay = typeof options === 'boolean' ? options : (opts.playing !== false)
236
+
237
+ if (!shouldPlay) {
238
+ this.fadeOutAndCleanup()
239
+ return noChange
240
+ }
241
+
242
+ if (this.bird) return noChange // Already running
243
+
244
+ // Reset destroyed state so subscriptions can work again
245
+ if (this.destroyed$.value) {
246
+ this.destroyed$.next(false)
247
+ this.state$.next('idle')
248
+ }
249
+
250
+ this.element = element
251
+ this.options = opts
252
+
253
+ // Ensure host element can contain absolutely-positioned children
254
+ const computedPos = window.getComputedStyle(element).position
255
+ if (computedPos === 'static') {
256
+ element.style.position = 'relative'
257
+ }
258
+ element.style.overflow = 'visible'
259
+
260
+ injectKeyframes()
261
+ this.initStateMachine()
262
+
263
+ return noChange
264
+ }
265
+
266
+ private initStateMachine(): void {
267
+ // State machine subscription
268
+ this.state$
269
+ .pipe(
270
+ distinctUntilChanged(),
271
+ filter(() => !this.destroyed$.value),
272
+ switchMap(state => this.handleStateTransition(state)),
273
+ takeUntil(this.destroyed$.pipe(filter(v => v)))
274
+ )
275
+ .subscribe()
276
+
277
+ // Combine IntersectionObserver + Page Visibility + Parent Playing State - all must be true
278
+ combineLatest([
279
+ // IntersectionObserver
280
+ new Observable<boolean>(subscriber => {
281
+ if (!this.element || typeof IntersectionObserver === 'undefined') {
282
+ subscriber.next(true)
283
+ return
284
+ }
285
+ const observer = new IntersectionObserver(
286
+ entries => subscriber.next(entries[0].isIntersecting),
287
+ { threshold: 0 }
288
+ )
289
+ observer.observe(this.element)
290
+ return () => observer.disconnect()
291
+ }),
292
+ // Page Visibility
293
+ fromEvent(document, 'visibilitychange').pipe(
294
+ map(() => document.visibilityState === 'visible'),
295
+ startWith(document.visibilityState === 'visible')
296
+ ),
297
+ // Parent playing state - find ancestor with 'playing' property and observe changes
298
+ new Observable<boolean>(subscriber => {
299
+ if (!this.element) {
300
+ subscriber.next(true)
301
+ return
302
+ }
303
+ // Find ancestor with 'playing' property (scene components)
304
+ let ancestor: HTMLElement | null = this.element
305
+ let playingHost: HTMLElement | null = null
306
+ while (ancestor) {
307
+ if ('playing' in ancestor) {
308
+ playingHost = ancestor
309
+ break
310
+ }
311
+ ancestor = ancestor.parentElement
312
+ }
313
+ if (!playingHost) {
314
+ subscriber.next(true)
315
+ return
316
+ }
317
+ // Emit current value
318
+ subscriber.next((playingHost as HTMLElement & { playing: boolean }).playing)
319
+ // Observe attribute changes on the host
320
+ const observer = new MutationObserver(() => {
321
+ subscriber.next((playingHost as HTMLElement & { playing: boolean }).playing)
322
+ })
323
+ observer.observe(playingHost, { attributes: true, attributeFilter: ['playing'] })
324
+ return () => observer.disconnect()
325
+ }),
326
+ ])
327
+ .pipe(
328
+ map(([inView, tabActive, playing]) => inView && tabActive && playing),
329
+ distinctUntilChanged(),
330
+ tap(visible => this.visible$.next(visible)),
331
+ filter(() => !this.destroyed$.value && !!this.context),
332
+ takeUntil(this.destroyed$.pipe(filter(v => v)))
333
+ )
334
+ .subscribe(visible => {
335
+ if (visible && !this.rafId) {
336
+ this.startAnimationLoop()
337
+ } else if (!visible && this.rafId) {
338
+ cancelAnimationFrame(this.rafId)
339
+ this.rafId = 0
340
+ }
341
+ })
342
+
343
+ // React to window resize - update destination positions
344
+ // Scroll listener removed: bird is position:absolute inside host, moves naturally with scroll
345
+ fromEvent(window, 'resize')
346
+ .pipe(
347
+ debounceTime(100),
348
+ filter(() => !this.destroyed$.value && !!this.context),
349
+ takeUntil(this.destroyed$.pipe(filter(v => v)))
350
+ )
351
+ .subscribe(() => this.updateDestinationPositions())
352
+
353
+ // Wait for delay + host visibility, then discover elements and create bird
354
+ const delay = this.options.delay ?? 500
355
+ timer(delay)
356
+ .pipe(
357
+ filter(() => !this.destroyed$.value && !this.bird),
358
+ // Wait for host element to be visible (opacity > 0.5)
359
+ switchMap(() => new Observable<void>(subscriber => {
360
+ if (!this.element) {
361
+ subscriber.next()
362
+ subscriber.complete()
363
+ return
364
+ }
365
+ const checkVisibility = () => {
366
+ if (this.isElementVisible(this.element!)) {
367
+ subscriber.next()
368
+ subscriber.complete()
369
+ } else {
370
+ timer(50)
371
+ .pipe(takeUntil(this.destroyed$.pipe(filter(v => v))))
372
+ .subscribe(checkVisibility)
373
+ }
374
+ }
375
+ checkVisibility()
376
+ })),
377
+ switchMap(() => this.createBird()),
378
+ tap(() => {
379
+ if (!this.destroyed$.value) {
380
+ this.state$.next('flying')
381
+ }
382
+ }),
383
+ takeUntil(this.destroyed$.pipe(filter(v => v)))
384
+ )
385
+ .subscribe()
386
+ }
387
+
388
+ private handleStateTransition(state: AnimationState) {
389
+ switch (state) {
390
+ case 'idle':
391
+ return EMPTY
392
+ case 'flying':
393
+ case 'hovering':
394
+ case 'spiraling':
395
+ case 'absorbed':
396
+ this.startAnimationLoop()
397
+ return EMPTY
398
+ case 'done':
399
+ this.cleanup()
400
+ // Emit completion event so parent can react
401
+ this.element?.dispatchEvent(
402
+ new CustomEvent('hummingbird-complete', { bubbles: true, composed: true })
403
+ )
404
+ return EMPTY
405
+ default:
406
+ return EMPTY
407
+ }
408
+ }
409
+
410
+ private async createBird(): Promise<void> {
411
+ if (!this.element) return
412
+
413
+ // Create bird with default color first
414
+ const bird = document.createElement('div')
415
+ bird.innerHTML = createBirdSVG(this.currentColor)
416
+ bird.className = 'pointer-events-none'
417
+ bird.style.cssText = `
418
+ position: absolute;
419
+ left: 0;
420
+ top: 0;
421
+ width: 24px;
422
+ height: 24px;
423
+ z-index: 99999;
424
+ will-change: transform, opacity;
425
+ transform-origin: center center;
426
+ transition: opacity 400ms ease-out;
427
+ `
428
+ this.element.appendChild(bird)
429
+ this.bird = bird
430
+
431
+ // Discover initial theme color asynchronously
432
+ this.discoverInitialColor()
433
+
434
+ const rect = this.element.getBoundingClientRect()
435
+ const hostRel = this.toHostRelative(rect.left, rect.top)
436
+ const waypoints = await this.discoverWaypoints(this.options.waypoints || [])
437
+ const home = this.options.home ? await this.getPosition(this.options.home) : null
438
+
439
+ const destinations = [...waypoints]
440
+ this.hasHome = !!home
441
+ if (home) destinations.push(home)
442
+
443
+ // If no destinations, create simple fly-through
444
+ if (destinations.length === 0) {
445
+ destinations.push({
446
+ x: hostRel.x + rect.width + 60,
447
+ y: hostRel.y + rect.height * 0.5,
448
+ duration: PAUSE_DURATION_MS,
449
+ })
450
+ }
451
+
452
+ // Start position (host-relative)
453
+ const startX = hostRel.x - 60
454
+ const startY = hostRel.y + rect.height / 2
455
+
456
+ this.context = {
457
+ state: 'flying',
458
+ cx: startX,
459
+ cy: startY,
460
+ flightStartX: startX,
461
+ flightStartY: startY,
462
+ phaseStart: performance.now(),
463
+ destIdx: 0,
464
+ destinations,
465
+ bx: 0,
466
+ by: 0,
467
+ }
468
+
469
+ // Offset by half element size (-12px) so spark centers at the position
470
+ this.bird.style.transform = `translate(${startX - 12}px, ${startY - 12}px)`
471
+ }
472
+
473
+ /** Current bird color — empty until `discoverInitialColor()` resolves a theme color. */
474
+ private currentColor = ''
475
+
476
+ /**
477
+ * Discover initial theme color from nearest schmancy-theme.
478
+ * Dispatches event FROM the host element so it bubbles up to nearest theme.
479
+ */
480
+ private discoverInitialColor(): void {
481
+ if (!this.element) return
482
+
483
+ // Listen for theme response first
484
+ fromEvent<CustomEvent<{ theme: HTMLElement & { color?: string } }>>(window, ThemeHereIAm)
485
+ .pipe(
486
+ take(1),
487
+ takeUntil(timer(150)),
488
+ map(e => e.detail.theme?.color),
489
+ filter((color): color is string => !!color && color !== this.currentColor),
490
+ takeUntil(this.destroyed$.pipe(filter(v => v)))
491
+ )
492
+ .subscribe(color => {
493
+ if (!this.bird) return
494
+ this.currentColor = color
495
+ this.bird.innerHTML = createBirdSVG(color)
496
+ })
497
+
498
+ // Dispatch FROM the host element (bubbles up to nearest theme)
499
+ this.element.dispatchEvent(
500
+ new CustomEvent(ThemeWhereAreYou, { bubbles: true, composed: true })
501
+ )
502
+ }
503
+
504
+ /**
505
+ * Discover and update bird color based on current destination's theme.
506
+ * Dispatches event FROM the destination element so it bubbles to its nearest theme.
507
+ */
508
+ private updateBirdColor(): void {
509
+ if (!this.bird || !this.context || this.destroyed$.value) return
510
+
511
+ // Get the current destination element
512
+ const dest = this.context.destinations[this.context.destIdx]
513
+ if (!dest?.element) return
514
+
515
+ // Listen for theme response
516
+ fromEvent<CustomEvent<{ theme: HTMLElement & { color?: string } }>>(window, ThemeHereIAm)
517
+ .pipe(
518
+ take(1),
519
+ takeUntil(timer(100)),
520
+ map(e => e.detail.theme?.color),
521
+ filter((color): color is string => !!color && color !== this.currentColor),
522
+ takeUntil(this.destroyed$.pipe(filter(v => v)))
523
+ )
524
+ .subscribe(color => {
525
+ if (!this.bird) return
526
+ this.currentColor = color
527
+ this.bird.innerHTML = createBirdSVG(color)
528
+ })
529
+
530
+ // Dispatch FROM the destination element (bubbles up to its nearest theme)
531
+ dest.element.dispatchEvent(
532
+ new CustomEvent(ThemeWhereAreYou, { bubbles: true, composed: true })
533
+ )
534
+ }
535
+
536
+ /**
537
+ * Discover all waypoint elements and return their positions with element references.
538
+ * Supports both simple ElementRef and Waypoint objects with duration.
539
+ * Uses RxJS forkJoin for parallel discovery.
540
+ */
541
+ private discoverWaypoints(waypoints: (ElementRef | Waypoint)[]): Promise<Destination[]> {
542
+ if (waypoints.length === 0) return Promise.resolve([])
543
+
544
+ // Normalize waypoints to { ref, duration } format
545
+ const normalized = waypoints.map(wp => {
546
+ if (typeof wp === 'string' || wp instanceof HTMLElement) {
547
+ return { ref: wp, duration: PAUSE_DURATION_MS }
548
+ }
549
+ return { ref: wp.ref, duration: wp.duration ?? PAUSE_DURATION_MS }
550
+ })
551
+
552
+ const discoveries$ = normalized.map(wp => this.discoverElement(wp.ref))
553
+
554
+ return new Promise(resolve => {
555
+ forkJoin(discoveries$)
556
+ .pipe(
557
+ map(elements =>
558
+ elements
559
+ .map((el, i): Destination | null => {
560
+ if (!el) return null
561
+ const r = el.getBoundingClientRect()
562
+ const center = this.toHostRelative(r.left + r.width / 2, r.top + r.height / 2)
563
+ return {
564
+ x: center.x,
565
+ y: center.y,
566
+ element: el,
567
+ duration: normalized[i].duration,
568
+ }
569
+ })
570
+ .filter((d): d is Destination => d !== null)
571
+ ),
572
+ takeUntil(this.destroyed$.pipe(filter(v => v)))
573
+ )
574
+ .subscribe({
575
+ next: positions => resolve(positions),
576
+ error: () => resolve([]),
577
+ })
578
+ })
579
+ }
580
+
581
+ /**
582
+ * Discover a single element by reference using event-based discovery.
583
+ * - HTMLElement: return directly
584
+ * - String (CSS selector or component tag): use discover() service
585
+ */
586
+ private discoverElement(ref: ElementRef): Promise<HTMLElement | null> {
587
+ if (ref instanceof HTMLElement) {
588
+ return Promise.resolve(ref)
589
+ }
590
+
591
+ return new Promise(resolve => {
592
+ discover<HTMLElement>(ref, 300)
593
+ .pipe(takeUntil(this.destroyed$.pipe(filter(v => v))))
594
+ .subscribe({
595
+ next: el => resolve(el),
596
+ error: () => resolve(null),
597
+ })
598
+ })
599
+ }
600
+
601
+ /**
602
+ * Get position from an element reference (includes element for theme discovery).
603
+ * Target uses center position (for black hole effect).
604
+ */
605
+ private async getPosition(ref: ElementRef): Promise<Destination | null> {
606
+ const el = await this.discoverElement(ref)
607
+ if (!el) return null
608
+ const r = el.getBoundingClientRect()
609
+ const center = this.toHostRelative(r.left + r.width / 2, r.top + r.height / 2)
610
+ return { x: center.x, y: center.y, element: el, duration: PAUSE_DURATION_MS }
611
+ }
612
+
613
+ /**
614
+ * Update all destination positions from their stored element references.
615
+ * Called on window resize/scroll to keep positions accurate.
616
+ */
617
+ private updateDestinationPositions(): void {
618
+ if (!this.context) return
619
+
620
+ for (const dest of this.context.destinations) {
621
+ if (dest.element) {
622
+ const r = dest.element.getBoundingClientRect()
623
+ const center = this.toHostRelative(r.left + r.width / 2, r.top + r.height / 2)
624
+ dest.x = center.x
625
+ dest.y = center.y
626
+ }
627
+ }
628
+ }
629
+
630
+ private startAnimationLoop(): void {
631
+ if (this.rafId) return
632
+
633
+ const tick = (now: number) => {
634
+ if (!this.bird || !this.context || this.destroyed$.value || !this.visible$.value) {
635
+ this.rafId = 0
636
+ return
637
+ }
638
+
639
+ switch (this.state$.value) {
640
+ case 'flying':
641
+ this.tickFlying(now)
642
+ break
643
+ case 'hovering':
644
+ this.tickHovering(now)
645
+ break
646
+ case 'spiraling':
647
+ this.tickSpiraling(now)
648
+ break
649
+ case 'absorbed':
650
+ this.tickAbsorbed(now)
651
+ return // Don't schedule next frame - absorption handles completion
652
+ }
653
+
654
+ this.rafId = requestAnimationFrame(tick)
655
+ }
656
+
657
+ this.rafId = requestAnimationFrame(tick)
658
+ }
659
+
660
+ private tickFlying(now: number): void {
661
+ if (!this.context || !this.bird) return
662
+
663
+ const ctx = this.context
664
+ const elapsed = now - ctx.phaseStart
665
+ const dest = ctx.destinations[ctx.destIdx]
666
+
667
+ const dx = dest.x - ctx.flightStartX
668
+ const dy = dest.y - ctx.flightStartY
669
+ const dist = Math.hypot(dx, dy)
670
+ const duration = (dist / FORWARD_SPEED_PX_S) * 1000
671
+ const t = Math.min(1, elapsed / duration)
672
+
673
+ // Cubic ease-in-out
674
+ const e = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2
675
+
676
+ ctx.cx = ctx.flightStartX + dx * e
677
+ ctx.cy = ctx.flightStartY + dy * e
678
+
679
+ // Simple smooth movement - no rotation or bouncing
680
+ this.bird.style.transform = `translate(${ctx.cx - 12}px, ${ctx.cy - 12}px)`
681
+
682
+ // Draw trail in real-time as bird flies (airplane vapor trail effect)
683
+ if (this.options.showConnections) {
684
+ this.drawTrail(ctx.cx, ctx.cy)
685
+ this.fadeTrail() // Continuously fade for vapor effect
686
+ }
687
+
688
+ // Update color based on section the bird is flying over
689
+ if (Math.floor(elapsed / 200) !== Math.floor((elapsed - 16) / 200)) {
690
+ this.updateBirdColor()
691
+ }
692
+
693
+ if (t >= 1) {
694
+ const isLastDestination = ctx.destIdx >= ctx.destinations.length - 1
695
+
696
+ if (isLastDestination && this.hasHome) {
697
+ // Has home: transition to spiraling into black hole
698
+ ctx.bx = dest.x
699
+ ctx.by = dest.y
700
+ ctx.phaseStart = now
701
+ this.createBlackHole(ctx.bx, ctx.by)
702
+ this.state$.next('spiraling')
703
+ } else {
704
+ // Transition to hovering (including last waypoint when no home)
705
+ ctx.phaseStart = now
706
+ this.state$.next('hovering')
707
+ }
708
+ }
709
+ }
710
+
711
+ /** Active shimmer overlay for current waypoint */
712
+ private shimmerOverlay: HTMLDivElement | null = null
713
+ private shimmerAnimation: Animation | null = null
714
+ /** Active pulse rings for current waypoint */
715
+ private pulseRings: HTMLDivElement[] = []
716
+
717
+ private tickHovering(now: number): void {
718
+ if (!this.context || !this.bird) return
719
+
720
+ const ctx = this.context
721
+ const elapsed = now - ctx.phaseStart
722
+ const dest = ctx.destinations[ctx.destIdx]
723
+
724
+ // Show shimmer, pulse rings, and fade spark on first frame of hover
725
+ if (elapsed < 20 && dest.element && !this.shimmerOverlay) {
726
+ this.showShimmer(dest.element)
727
+ this.showPulseRings(dest.x, dest.y)
728
+ // Fade spark to invisible while hovering
729
+ this.bird.style.opacity = '0'
730
+ }
731
+
732
+ // Stay in place with subtle breathing pulse (no movement)
733
+ ctx.cx = dest.x
734
+ ctx.cy = dest.y
735
+ const pulse = 1 + Math.sin(elapsed * 0.006) * 0.08 // Very subtle 8% scale pulse
736
+ this.bird.style.transform = `translate(${dest.x - 12}px, ${dest.y - 12}px) scale(${pulse})`
737
+
738
+ // Use per-waypoint duration - but also wait for next element to be visible
739
+ if (elapsed > dest.duration) {
740
+ const nextDest = ctx.destinations[ctx.destIdx + 1]
741
+ const isLastWaypoint = !nextDest
742
+
743
+ // Last waypoint (no home): fade out after hovering
744
+ if (isLastWaypoint && !this.hasHome) {
745
+ this.hideShimmer()
746
+ this.fadeOutBird()
747
+ return
748
+ }
749
+
750
+ // Check if next waypoint element is visible (not opacity-0)
751
+ if (nextDest?.element && !this.isElementVisible(nextDest.element)) {
752
+ // Next element not visible yet, keep waiting
753
+ return
754
+ }
755
+ // Scroll next waypoint into view if off-screen
756
+ if (nextDest?.element && !this.isInViewport(nextDest.element)) {
757
+ nextDest.element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' })
758
+ }
759
+ // Refresh next destination position (element may have animated to new position)
760
+ if (nextDest?.element) {
761
+ const r = nextDest.element.getBoundingClientRect()
762
+ const center = this.toHostRelative(r.left + r.width / 2, r.top + r.height / 2)
763
+ nextDest.x = center.x
764
+ nextDest.y = center.y
765
+ }
766
+ // Restore spark opacity before moving
767
+ this.bird.style.opacity = '1'
768
+ // Hide shimmer before moving
769
+ this.hideShimmer()
770
+ // Reset trail for new flight segment
771
+ this.resetTrail()
772
+ // Move to next destination
773
+ ctx.destIdx++
774
+ ctx.flightStartX = dest.x
775
+ ctx.flightStartY = dest.y
776
+ ctx.phaseStart = now
777
+ this.state$.next('flying')
778
+ }
779
+ }
780
+
781
+ /**
782
+ * Check if an element is sufficiently visible (opacity > 0.5 and not hidden)
783
+ * Uses 0.5 threshold to wait for CSS transitions to be halfway done
784
+ */
785
+ private isElementVisible(element: HTMLElement): boolean {
786
+ const style = window.getComputedStyle(element)
787
+ const opacity = parseFloat(style.opacity)
788
+ return opacity > 0.5 && style.visibility !== 'hidden' && style.display !== 'none'
789
+ }
790
+
791
+ /** Check if element is within the viewport bounds (with padding so bird doesn't fly to edges) */
792
+ private isInViewport(element: HTMLElement): boolean {
793
+ const r = element.getBoundingClientRect()
794
+ const pad = 80
795
+ return r.bottom > pad && r.top < window.innerHeight - pad && r.right > pad && r.left < window.innerWidth - pad
796
+ }
797
+
798
+
799
+
800
+
801
+
802
+ /**
803
+ * Show shimmer effect on an element (similar to fyi directive)
804
+ */
805
+ private showShimmer(element: HTMLElement): void {
806
+ // Store and modify element position if needed
807
+ const computedStyle = window.getComputedStyle(element)
808
+ if (computedStyle.position === 'static') {
809
+ element.style.position = 'relative'
810
+ }
811
+
812
+ // Create shimmer overlay
813
+ const shimmer = document.createElement('div')
814
+ shimmer.style.cssText = `
815
+ position: absolute;
816
+ inset: 0;
817
+ pointer-events: none;
818
+ z-index: 9999;
819
+ border-radius: ${computedStyle.borderRadius};
820
+ background: linear-gradient(
821
+ 108deg,
822
+ transparent 0%,
823
+ transparent 30%,
824
+ rgba(255, 255, 255, 0.02) 38%,
825
+ rgba(255, 255, 255, 0.06) 45%,
826
+ rgba(255, 255, 255, 0.08) 50%,
827
+ rgba(255, 255, 255, 0.06) 55%,
828
+ rgba(255, 255, 255, 0.02) 62%,
829
+ transparent 70%,
830
+ transparent 100%
831
+ );
832
+ background-size: 300% 100%;
833
+ `
834
+
835
+ element.appendChild(shimmer)
836
+ this.shimmerOverlay = shimmer
837
+
838
+ // Animate shimmer - slow and elegant like fyi directive (10 seconds)
839
+ this.shimmerAnimation = shimmer.animate(
840
+ [{ backgroundPosition: '300% 0' }, { backgroundPosition: '-100% 0' }],
841
+ { duration: 10000, easing: 'cubic-bezier(0.25, 0.1, 0.25, 1)', iterations: Infinity }
842
+ )
843
+ }
844
+
845
+ /**
846
+ * Hide and cleanup shimmer effect
847
+ */
848
+ private hideShimmer(): void {
849
+ if (this.shimmerAnimation) {
850
+ this.shimmerAnimation.cancel()
851
+ this.shimmerAnimation = null
852
+ }
853
+ if (this.shimmerOverlay) {
854
+ // Fade out then remove
855
+ const fadeOut = this.shimmerOverlay.animate([{ opacity: 1 }, { opacity: 0 }], {
856
+ duration: 300,
857
+ easing: 'ease-out',
858
+ fill: 'forwards',
859
+ })
860
+ const overlay = this.shimmerOverlay
861
+ fadeOut.onfinish = () => overlay.remove()
862
+ this.shimmerOverlay = null
863
+ }
864
+ }
865
+
866
+ /**
867
+ * Fade out bird gracefully (when no home defined)
868
+ */
869
+ private fadeOutBird(): void {
870
+ if (!this.bird) return
871
+
872
+ // Set state to idle to prevent tickHovering from being called again
873
+ this.state$.next('idle')
874
+
875
+ // Restore opacity before fading (may be 0 from hovering)
876
+ this.bird.style.opacity = '1'
877
+
878
+ this.bird.animate(
879
+ [{ opacity: 1, transform: this.bird.style.transform }, { opacity: 0, transform: this.bird.style.transform }],
880
+ { duration: 400, easing: 'ease-out', fill: 'forwards' }
881
+ ).onfinish = () => {
882
+ this.state$.next('done')
883
+ }
884
+ }
885
+
886
+ /**
887
+ * Fade out bird and all visual elements gracefully before cleanup (when playing becomes false)
888
+ */
889
+ private fadeOutAndCleanup(): void {
890
+ // If no bird exists yet, just cleanup
891
+ if (!this.bird) {
892
+ this.cleanup()
893
+ return
894
+ }
895
+
896
+ // Cancel animation loop to prevent further updates
897
+ if (this.rafId) {
898
+ cancelAnimationFrame(this.rafId)
899
+ this.rafId = 0
900
+ }
901
+
902
+ // Restore opacity before fading (may be 0 from hovering)
903
+ this.bird.style.opacity = '1'
904
+
905
+ // Very slow, gradual fade - barely noticeable at first, then gently disappears
906
+ const birdAnim = this.bird.animate(
907
+ [
908
+ { opacity: 1 },
909
+ { opacity: 0.8, offset: 0.3 },
910
+ { opacity: 0.5, offset: 0.6 },
911
+ { opacity: 0.2, offset: 0.85 },
912
+ { opacity: 0 },
913
+ ],
914
+ { duration: 3000, easing: 'linear', fill: 'forwards' }
915
+ )
916
+
917
+ // Fade out trail canvas even more slowly if exists
918
+ if (this.trailCanvas) {
919
+ this.trailCanvas.animate(
920
+ [{ opacity: 1 }, { opacity: 0 }],
921
+ { duration: 3500, easing: 'ease-out', fill: 'forwards' }
922
+ )
923
+ }
924
+
925
+ // Fade out shimmer
926
+ this.hideShimmer()
927
+
928
+ // Cleanup after fade completes
929
+ birdAnim.onfinish = () => {
930
+ this.cleanup()
931
+ }
932
+ }
933
+
934
+ /**
935
+ * Show expanding pulse rings at position (like ripple/wave effect)
936
+ */
937
+ private showPulseRings(x: number, y: number): void {
938
+ const color = this.currentColor
939
+
940
+ // Create 3 staggered pulse rings
941
+ for (let i = 0; i < 3; i++) {
942
+ const ring = document.createElement('div')
943
+ ring.className = 'pointer-events-none'
944
+ ring.style.cssText = `
945
+ position: absolute;
946
+ left: ${x}px;
947
+ top: ${y}px;
948
+ width: 40px;
949
+ height: 40px;
950
+ border-radius: 9999px;
951
+ border: 2px solid ${hexToRgba(color, 0.5)};
952
+ transform: translate(-50%, -50%) scale(1);
953
+ z-index: 9998;
954
+ will-change: transform, opacity;
955
+ `
956
+ this.element?.appendChild(ring)
957
+ this.pulseRings.push(ring)
958
+
959
+ // Animate: expand and fade out
960
+ ring.animate(
961
+ [
962
+ { transform: 'translate(-50%, -50%) scale(1)', opacity: 0.6 },
963
+ { transform: 'translate(-50%, -50%) scale(4)', opacity: 0 },
964
+ ],
965
+ {
966
+ duration: 1200,
967
+ delay: i * 300,
968
+ easing: 'ease-out',
969
+ fill: 'forwards',
970
+ }
971
+ ).onfinish = () => ring.remove()
972
+ }
973
+
974
+ // Clear refs after animation completes (last ring)
975
+ timer(1200 + 600)
976
+ .pipe(takeUntil(this.destroyed$.pipe(filter(v => v))))
977
+ .subscribe(() => {
978
+ this.pulseRings = []
979
+ })
980
+ }
981
+
982
+ private tickSpiraling(now: number): void {
983
+ if (!this.context || !this.bird) return
984
+
985
+ const ctx = this.context
986
+ const elapsed = now - ctx.phaseStart
987
+ const dx = ctx.bx - ctx.cx
988
+ const dy = ctx.by - ctx.cy
989
+ const d = Math.hypot(dx, dy)
990
+
991
+ if (d > 8) {
992
+ // Smoother acceleration as it gets closer (gravitational pull)
993
+ const gravitationalPull = Math.pow(1 - d / 150, 2) * 180 + 40
994
+ const speed = Math.min(gravitationalPull, d * 0.15)
995
+ const angle = Math.atan2(dy, dx)
996
+
997
+ // Elegant spiral motion - tighter as it gets closer
998
+ const spiralIntensity = Math.min(1, d / 60) * 0.015
999
+ const spiralAngle = elapsed * 0.012
1000
+ const spiral = Math.sin(spiralAngle) * d * spiralIntensity
1001
+
1002
+ ctx.cx += Math.cos(angle) * speed * 0.016 + Math.cos(angle + Math.PI / 2) * spiral
1003
+ ctx.cy += Math.sin(angle) * speed * 0.016 + Math.sin(angle + Math.PI / 2) * spiral
1004
+
1005
+ // Smooth scale reduction with slight rotation
1006
+ const scale = Math.max(0.2, d / 120)
1007
+ const rotation = elapsed * 0.3
1008
+ this.bird.style.transform = `translate(${ctx.cx - 12}px, ${ctx.cy - 12}px) rotate(${rotation}deg) scale(${scale})`
1009
+
1010
+ // Add subtle glow intensification as it approaches
1011
+ const glowIntensity = 1 - d / 100
1012
+ if (glowIntensity > 0.3) {
1013
+ this.bird.style.filter = `brightness(${1 + glowIntensity * 0.5}) drop-shadow(0 0 ${glowIntensity * 8}px ${this.currentColor})`
1014
+ }
1015
+ } else {
1016
+ // Transition to absorbed
1017
+ ctx.phaseStart = now
1018
+ this.startHostDistortion()
1019
+ this.state$.next('absorbed')
1020
+ }
1021
+ }
1022
+
1023
+ /** Store home element for distortion effect */
1024
+ private homeElement: HTMLElement | null = null
1025
+
1026
+ /**
1027
+ * Start gravitational shake/wobble effect on the home element during absorption
1028
+ */
1029
+ private startHostDistortion(): void {
1030
+ if (!this.context) return
1031
+
1032
+ // Find the home element from destinations
1033
+ const homeDest = this.context.destinations[this.context.destinations.length - 1]
1034
+ if (!homeDest?.element) return
1035
+
1036
+ this.homeElement = homeDest.element
1037
+ const el = this.homeElement
1038
+ const color = this.currentColor
1039
+
1040
+ // Store original transform
1041
+ const originalTransform = el.style.transform || ''
1042
+
1043
+ // Violent shake/wobble - gravitational tremor as energy is being absorbed
1044
+ // Rapid oscillations that build in intensity
1045
+ el.animate(
1046
+ [
1047
+ { transform: `${originalTransform} translate(0, 0) scale(1) rotate(0deg)`, filter: 'brightness(1)' },
1048
+ { transform: `${originalTransform} translate(-2px, 1px) scale(0.992) rotate(-0.3deg)`, filter: 'brightness(1.05)', offset: 0.05 },
1049
+ { transform: `${originalTransform} translate(3px, -1px) scale(1.005) rotate(0.4deg)`, filter: 'brightness(1.1)', offset: 0.1 },
1050
+ { transform: `${originalTransform} translate(-1px, 2px) scale(0.995) rotate(-0.2deg)`, filter: `brightness(1.15) drop-shadow(0 0 5px ${color})`, offset: 0.15 },
1051
+ { transform: `${originalTransform} translate(2px, 0px) scale(1.008) rotate(0.5deg)`, filter: 'brightness(1.2)', offset: 0.2 },
1052
+ { transform: `${originalTransform} translate(-3px, -2px) scale(0.988) rotate(-0.6deg)`, filter: `brightness(1.25) drop-shadow(0 0 8px ${color})`, offset: 0.25 },
1053
+ { transform: `${originalTransform} translate(1px, 3px) scale(1.01) rotate(0.3deg)`, filter: 'brightness(1.3)', offset: 0.3 },
1054
+ { transform: `${originalTransform} translate(-2px, -1px) scale(0.985) rotate(-0.4deg)`, filter: `brightness(1.35) drop-shadow(0 0 12px ${color})`, offset: 0.35 },
1055
+ { transform: `${originalTransform} translate(4px, 1px) scale(1.015) rotate(0.7deg)`, filter: 'brightness(1.4)', offset: 0.4 },
1056
+ { transform: `${originalTransform} translate(-1px, 2px) scale(0.99) rotate(-0.5deg)`, filter: `brightness(1.35) drop-shadow(0 0 15px ${color})`, offset: 0.45 },
1057
+ { transform: `${originalTransform} translate(2px, -2px) scale(1.012) rotate(0.4deg)`, filter: 'brightness(1.3)', offset: 0.5 },
1058
+ { transform: `${originalTransform} translate(-3px, 1px) scale(0.993) rotate(-0.3deg)`, filter: `brightness(1.25) drop-shadow(0 0 10px ${color})`, offset: 0.55 },
1059
+ { transform: `${originalTransform} translate(1px, -1px) scale(1.006) rotate(0.2deg)`, filter: 'brightness(1.2)', offset: 0.6 },
1060
+ { transform: `${originalTransform} translate(-1px, 1px) scale(0.997) rotate(-0.15deg)`, filter: 'brightness(1.15)', offset: 0.7 },
1061
+ { transform: `${originalTransform} translate(1px, 0px) scale(1.003) rotate(0.1deg)`, filter: 'brightness(1.1)', offset: 0.8 },
1062
+ { transform: `${originalTransform} translate(0px, -1px) scale(0.999) rotate(-0.05deg)`, filter: 'brightness(1.05)', offset: 0.9 },
1063
+ { transform: `${originalTransform} translate(0, 0) scale(1) rotate(0deg)`, filter: 'brightness(1)' },
1064
+ ],
1065
+ {
1066
+ duration: 500,
1067
+ easing: 'linear',
1068
+ }
1069
+ )
1070
+ }
1071
+
1072
+ private tickAbsorbed(now: number): void {
1073
+ if (!this.context || !this.bird) return
1074
+
1075
+ const ctx = this.context
1076
+ const elapsed = now - ctx.phaseStart
1077
+ const duration = 500
1078
+ const t = Math.min(1, elapsed / duration)
1079
+
1080
+ // Exponential ease-in for dramatic acceleration into the singularity
1081
+ const e = t * t * t
1082
+
1083
+ // Spaghettification effect - stretch vertically while shrinking horizontally
1084
+ const scaleX = 0.2 * (1 - e * 0.9)
1085
+ const scaleY = 0.2 * (1 - e) * (1 + e * 2) // Elongates before disappearing
1086
+
1087
+ // Rapid rotation acceleration
1088
+ const rotation = t * t * 1080 // Accelerating spin
1089
+
1090
+ this.bird.style.transform = `translate(${ctx.bx - 12}px, ${ctx.by - 12}px) rotate(${rotation}deg) scale(${scaleX}, ${scaleY})`
1091
+ this.bird.style.opacity = String(Math.max(0, 1 - e * 1.2))
1092
+ this.bird.style.filter = `brightness(${1 + e * 3}) blur(${e * 2}px)`
1093
+
1094
+ if (t >= 1) {
1095
+ this.collapseBlackHole()
1096
+ } else {
1097
+ this.rafId = requestAnimationFrame(now => this.tickAbsorbed(now))
1098
+ }
1099
+ }
1100
+
1101
+ /** Additional visual elements for enhanced blackhole */
1102
+ private accretionDisks: HTMLElement[] = []
1103
+ private lensingRing: HTMLElement | null = null
1104
+ private eventHorizon: HTMLElement | null = null
1105
+
1106
+ private createBlackHole(x: number, y: number): void {
1107
+ const color = this.currentColor
1108
+
1109
+ // 1. Outer gravitational lensing ring - cosmic teal/purple glow
1110
+ const lensing = document.createElement('div')
1111
+ lensing.className = 'pointer-events-none'
1112
+ lensing.style.cssText = `
1113
+ position: absolute;
1114
+ left: ${x}px;
1115
+ top: ${y}px;
1116
+ width: 100px;
1117
+ height: 100px;
1118
+ border-radius: 9999px;
1119
+ border: 1px solid ${COSMIC_COLORS.oiiiDim};
1120
+ background: radial-gradient(circle, transparent 40%, ${COSMIC_COLORS.deepPurple} 70%, transparent 100%);
1121
+ box-shadow:
1122
+ inset 0 0 25px ${COSMIC_COLORS.oiiiDim},
1123
+ 0 0 40px ${COSMIC_COLORS.deepPurple},
1124
+ 0 0 60px ${hexToRgba(color, 0.15)};
1125
+ transform: translate(-50%, -50%) rotateX(85deg) scale(0);
1126
+ z-index: 9994;
1127
+ will-change: transform, opacity;
1128
+ animation: hb-lensing 2s ease-in-out infinite;
1129
+ `
1130
+ lensing.animate(
1131
+ [
1132
+ { transform: 'translate(-50%, -50%) rotateX(85deg) scale(0)' },
1133
+ { transform: 'translate(-50%, -50%) rotateX(85deg) scale(1)' },
1134
+ ],
1135
+ { duration: 600, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', fill: 'forwards' }
1136
+ )
1137
+ this.element?.appendChild(lensing)
1138
+ this.lensingRing = lensing
1139
+
1140
+ // 2. Layered accretion disks - cosmic nebula colors (H-alpha crimson, OIII teal, stellar pink)
1141
+ const diskConfigs = [
1142
+ { size: 85, speed: 4, color1: COSMIC_COLORS.oiii, color2: COSMIC_COLORS.deepBlue, rotateX: 68, reverse: true },
1143
+ { size: 68, speed: 2.5, color1: COSMIC_COLORS.hAlpha, color2: COSMIC_COLORS.hAlphaDim, rotateX: 72, reverse: false },
1144
+ { size: 52, speed: 1.8, color1: COSMIC_COLORS.stellar, color2: COSMIC_COLORS.stellarDim, rotateX: 76, reverse: true },
1145
+ ]
1146
+
1147
+ diskConfigs.forEach((config, i) => {
1148
+ const disk = document.createElement('div')
1149
+ disk.className = 'pointer-events-none'
1150
+ const direction = config.reverse ? '-' : ''
1151
+ disk.style.cssText = `
1152
+ position: absolute;
1153
+ left: ${x}px;
1154
+ top: ${y}px;
1155
+ width: ${config.size}px;
1156
+ height: ${config.size}px;
1157
+ border-radius: 9999px;
1158
+ background: conic-gradient(
1159
+ from ${i * 45}deg,
1160
+ transparent 0%,
1161
+ ${config.color1} 15%,
1162
+ ${config.color2} 35%,
1163
+ transparent 50%,
1164
+ ${config.color1} 65%,
1165
+ ${config.color2} 85%,
1166
+ transparent 100%
1167
+ );
1168
+ transform: translate(-50%, -50%) rotateX(${config.rotateX}deg) scale(0);
1169
+ z-index: ${9995 + i};
1170
+ will-change: transform;
1171
+ filter: blur(${i * 0.3}px);
1172
+ mix-blend-mode: screen;
1173
+ `
1174
+ // Entrance animation then continuous rotation
1175
+ disk.animate(
1176
+ [
1177
+ { transform: `translate(-50%, -50%) rotateX(${config.rotateX}deg) scale(0) rotate(0deg)` },
1178
+ { transform: `translate(-50%, -50%) rotateX(${config.rotateX}deg) scale(1) rotate(0deg)` },
1179
+ ],
1180
+ { duration: 400 + i * 100, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', fill: 'forwards' }
1181
+ ).onfinish = () => {
1182
+ disk.style.transform = `translate(-50%, -50%) rotateX(${config.rotateX}deg) scale(1)`
1183
+ disk.animate(
1184
+ [
1185
+ { transform: `translate(-50%, -50%) rotateX(${config.rotateX}deg) scale(1) rotate(0deg)` },
1186
+ { transform: `translate(-50%, -50%) rotateX(${config.rotateX}deg) scale(1) rotate(${direction}360deg)` },
1187
+ ],
1188
+ { duration: config.speed * 1000, easing: 'linear', iterations: Infinity }
1189
+ )
1190
+ }
1191
+ this.element?.appendChild(disk)
1192
+ this.accretionDisks.push(disk)
1193
+ })
1194
+
1195
+ // 3. Event horizon - deep void with cosmic glow corona
1196
+ const horizon = document.createElement('div')
1197
+ horizon.className = 'pointer-events-none'
1198
+ horizon.style.cssText = `
1199
+ --hb-color: ${COSMIC_COLORS.hAlpha};
1200
+ --hb-color-dim: ${COSMIC_COLORS.oiiiDim};
1201
+ position: absolute;
1202
+ left: ${x}px;
1203
+ top: ${y}px;
1204
+ width: 36px;
1205
+ height: 36px;
1206
+ border-radius: 9999px;
1207
+ background: radial-gradient(circle,
1208
+ ${COSMIC_COLORS.singularity} 0%,
1209
+ ${COSMIC_COLORS.voidBlack} 40%,
1210
+ ${COSMIC_COLORS.deepPurple} 60%,
1211
+ ${COSMIC_COLORS.hAlphaDim} 75%,
1212
+ transparent 100%
1213
+ );
1214
+ box-shadow:
1215
+ 0 0 15px 4px ${COSMIC_COLORS.hAlpha},
1216
+ 0 0 30px 8px ${COSMIC_COLORS.oiiiDim},
1217
+ 0 0 50px 15px ${COSMIC_COLORS.deepPurple},
1218
+ inset 0 0 15px ${COSMIC_COLORS.voidBlack};
1219
+ transform: translate(-50%, -50%) scale(0);
1220
+ z-index: 9999;
1221
+ will-change: transform, box-shadow;
1222
+ animation: hb-horizon-pulse 1.5s ease-in-out infinite;
1223
+ `
1224
+ horizon.animate(
1225
+ [
1226
+ { transform: 'translate(-50%, -50%) scale(0)' },
1227
+ { transform: 'translate(-50%, -50%) scale(1.15)', offset: 0.6 },
1228
+ { transform: 'translate(-50%, -50%) scale(1)' },
1229
+ ],
1230
+ { duration: 500, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', fill: 'forwards' }
1231
+ )
1232
+ this.element?.appendChild(horizon)
1233
+ this.eventHorizon = horizon
1234
+ this.blackHole = horizon // Keep reference for backward compatibility
1235
+
1236
+ // 4. Enhanced orbital particles with cosmic colors
1237
+ this.addParticles(x, y)
1238
+ }
1239
+
1240
+ private addParticles(cx: number, cy: number): void {
1241
+ // Cosmic particle colors - alternating between nebula emission colors
1242
+ const cosmicParticleColors = [
1243
+ { bg: COSMIC_COLORS.hAlpha, glow: COSMIC_COLORS.hAlphaDim },
1244
+ { bg: COSMIC_COLORS.oiii, glow: COSMIC_COLORS.oiiiDim },
1245
+ { bg: COSMIC_COLORS.stellar, glow: COSMIC_COLORS.stellarDim },
1246
+ { bg: 'rgba(255, 255, 255, 0.9)', glow: COSMIC_COLORS.stellarDim },
1247
+ ]
1248
+
1249
+ // Create orbital particles that spiral inward elegantly
1250
+ const particleCount = 10
1251
+ for (let i = 0; i < particleCount; i++) {
1252
+ const p = document.createElement('div')
1253
+ const startAngle = (i / particleCount) * Math.PI * 2
1254
+ const orbitRadius = 45 + (i % 3) * 18
1255
+ const size = 2 + (i % 3)
1256
+ const duration = 1400 + (i % 3) * 400
1257
+ const delay = i * 70
1258
+ const colorSet = cosmicParticleColors[i % cosmicParticleColors.length]
1259
+
1260
+ p.className = 'pointer-events-none'
1261
+ p.style.cssText = `
1262
+ --orbit-radius: ${orbitRadius}px;
1263
+ position: absolute;
1264
+ width: ${size}px;
1265
+ height: ${size}px;
1266
+ border-radius: 9999px;
1267
+ background: ${colorSet.bg};
1268
+ box-shadow: 0 0 ${size * 4}px ${colorSet.bg}, 0 0 ${size * 8}px ${colorSet.glow};
1269
+ left: ${cx}px;
1270
+ top: ${cy}px;
1271
+ z-index: 9997;
1272
+ will-change: transform, opacity;
1273
+ transform-origin: center center;
1274
+ transform: rotate(${startAngle}rad) translateX(${orbitRadius}px);
1275
+ opacity: 0;
1276
+ `
1277
+
1278
+ this.element?.appendChild(p)
1279
+ this.particles.push(p)
1280
+
1281
+ // Smooth orbital spiral animation
1282
+ p.animate(
1283
+ [
1284
+ {
1285
+ transform: `rotate(${startAngle}rad) translateX(${orbitRadius}px) scale(1)`,
1286
+ opacity: 0,
1287
+ },
1288
+ {
1289
+ transform: `rotate(${startAngle}rad) translateX(${orbitRadius}px) scale(1)`,
1290
+ opacity: 0.9,
1291
+ offset: 0.1,
1292
+ },
1293
+ {
1294
+ transform: `rotate(${startAngle + Math.PI}rad) translateX(${orbitRadius * 0.5}px) scale(0.8)`,
1295
+ opacity: 0.7,
1296
+ offset: 0.5,
1297
+ },
1298
+ {
1299
+ transform: `rotate(${startAngle + Math.PI * 2}rad) translateX(0px) scale(0)`,
1300
+ opacity: 0,
1301
+ },
1302
+ ],
1303
+ {
1304
+ duration,
1305
+ delay,
1306
+ easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
1307
+ fill: 'forwards',
1308
+ }
1309
+ ).onfinish = () => p.remove()
1310
+ }
1311
+
1312
+ // Cosmic light streaks being pulled in - alternating H-alpha and OIII
1313
+ const streakColors = [COSMIC_COLORS.hAlpha, COSMIC_COLORS.oiii, COSMIC_COLORS.stellar, COSMIC_COLORS.oiii]
1314
+ for (let i = 0; i < 5; i++) {
1315
+ const streak = document.createElement('div')
1316
+ const angle = (i / 5) * Math.PI * 2 + Math.PI / 10
1317
+ const startDist = 65 + Math.random() * 25
1318
+ const streakColor = streakColors[i % streakColors.length]
1319
+
1320
+ streak.className = 'pointer-events-none'
1321
+ streak.style.cssText = `
1322
+ position: absolute;
1323
+ width: 2px;
1324
+ height: 14px;
1325
+ border-radius: 1px;
1326
+ background: linear-gradient(to bottom, transparent, ${streakColor}, rgba(255,255,255,0.3));
1327
+ left: ${cx + Math.cos(angle) * startDist}px;
1328
+ top: ${cy + Math.sin(angle) * startDist}px;
1329
+ z-index: 9996;
1330
+ will-change: transform, opacity;
1331
+ transform: rotate(${angle + Math.PI / 2}rad) scale(0);
1332
+ opacity: 0;
1333
+ mix-blend-mode: screen;
1334
+ `
1335
+
1336
+ this.element?.appendChild(streak)
1337
+ this.particles.push(streak)
1338
+
1339
+ streak.animate(
1340
+ [
1341
+ {
1342
+ transform: `rotate(${angle + Math.PI / 2}rad) scale(0)`,
1343
+ opacity: 0,
1344
+ left: `${cx + Math.cos(angle) * startDist}px`,
1345
+ top: `${cy + Math.sin(angle) * startDist}px`,
1346
+ },
1347
+ {
1348
+ transform: `rotate(${angle + Math.PI / 2}rad) scale(1.5)`,
1349
+ opacity: 0.85,
1350
+ left: `${cx + Math.cos(angle) * startDist * 0.5}px`,
1351
+ top: `${cy + Math.sin(angle) * startDist * 0.5}px`,
1352
+ offset: 0.3,
1353
+ },
1354
+ {
1355
+ transform: `rotate(${angle + Math.PI / 2}rad) scale(0.5)`,
1356
+ opacity: 0,
1357
+ left: `${cx}px`,
1358
+ top: `${cy}px`,
1359
+ },
1360
+ ],
1361
+ {
1362
+ duration: 900,
1363
+ delay: 150 + i * 120,
1364
+ easing: 'cubic-bezier(0.55, 0, 1, 0.45)',
1365
+ fill: 'forwards',
1366
+ }
1367
+ ).onfinish = () => streak.remove()
1368
+ }
1369
+ }
1370
+
1371
+ private collapseBlackHole(): void {
1372
+ this.bird?.remove()
1373
+ this.particles.forEach(p => p.remove())
1374
+
1375
+ if (!this.context) {
1376
+ this.state$.next('done')
1377
+ return
1378
+ }
1379
+
1380
+ const { bx, by } = this.context
1381
+ const color = this.currentColor
1382
+
1383
+ // 1. Collapse accretion disks inward rapidly
1384
+ this.accretionDisks.forEach((disk, i) => {
1385
+ disk.animate(
1386
+ [
1387
+ { transform: disk.style.transform, opacity: 1 },
1388
+ { transform: 'translate(-50%, -50%) rotateX(90deg) scale(0)', opacity: 0 },
1389
+ ],
1390
+ { duration: 200 + i * 50, easing: 'ease-in', fill: 'forwards' }
1391
+ ).onfinish = () => disk.remove()
1392
+ })
1393
+ this.accretionDisks = []
1394
+
1395
+ // 2. Lensing ring collapses
1396
+ if (this.lensingRing) {
1397
+ this.lensingRing.animate(
1398
+ [
1399
+ { transform: 'translate(-50%, -50%) rotateX(85deg) scale(1)', opacity: 0.6 },
1400
+ { transform: 'translate(-50%, -50%) rotateX(85deg) scale(0)', opacity: 0 },
1401
+ ],
1402
+ { duration: 250, easing: 'ease-in', fill: 'forwards' }
1403
+ ).onfinish = () => this.lensingRing?.remove()
1404
+ }
1405
+
1406
+ // 3. Event horizon/blackhole - brief bright flash then collapse
1407
+ const bh = this.blackHole
1408
+ if (bh) {
1409
+ bh.animate(
1410
+ [
1411
+ { transform: 'translate(-50%, -50%) scale(1)', filter: 'brightness(1)' },
1412
+ { transform: 'translate(-50%, -50%) scale(1.3)', filter: 'brightness(3)', offset: 0.2 },
1413
+ { transform: 'translate(-50%, -50%) scale(0)', filter: 'brightness(5)', opacity: 0 },
1414
+ ],
1415
+ { duration: 350, easing: 'cubic-bezier(0.55, 0, 1, 0.45)', fill: 'forwards' }
1416
+ ).onfinish = () => bh.remove()
1417
+ }
1418
+
1419
+ // 4. COSMIC BIG BANG - Multiple expanding shockwave rings with nebula colors
1420
+ const cosmicRingColors = [
1421
+ { border: COSMIC_COLORS.stellar, glow: COSMIC_COLORS.stellarDim },
1422
+ { border: COSMIC_COLORS.hAlpha, glow: COSMIC_COLORS.hAlphaDim },
1423
+ { border: COSMIC_COLORS.oiii, glow: COSMIC_COLORS.oiiiDim },
1424
+ { border: COSMIC_COLORS.deepPurple, glow: COSMIC_COLORS.deepBlue },
1425
+ ]
1426
+
1427
+ for (let i = 0; i < 4; i++) {
1428
+ const ring = document.createElement('div')
1429
+ const ringColor = cosmicRingColors[i]
1430
+ ring.className = 'pointer-events-none'
1431
+ ring.style.cssText = `
1432
+ position: absolute;
1433
+ left: ${bx}px;
1434
+ top: ${by}px;
1435
+ width: 20px;
1436
+ height: 20px;
1437
+ border-radius: 9999px;
1438
+ border: ${2.5 - i * 0.4}px solid ${ringColor.border};
1439
+ box-shadow:
1440
+ 0 0 ${18 - i * 3}px ${ringColor.border},
1441
+ 0 0 ${30 - i * 5}px ${ringColor.glow},
1442
+ inset 0 0 ${12 - i * 2}px ${ringColor.glow};
1443
+ transform: translate(-50%, -50%) scale(0.5);
1444
+ z-index: ${9990 - i};
1445
+ will-change: transform, opacity;
1446
+ mix-blend-mode: screen;
1447
+ `
1448
+ this.element?.appendChild(ring)
1449
+
1450
+ ring.animate(
1451
+ [
1452
+ {
1453
+ transform: 'translate(-50%, -50%) scale(0.5)',
1454
+ opacity: 0.95,
1455
+ borderWidth: `${2.5 - i * 0.4}px`,
1456
+ },
1457
+ {
1458
+ transform: 'translate(-50%, -50%) scale(2.5)',
1459
+ opacity: 0.6,
1460
+ borderWidth: `${1.8 - i * 0.3}px`,
1461
+ offset: 0.35,
1462
+ },
1463
+ {
1464
+ transform: 'translate(-50%, -50%) scale(6)',
1465
+ opacity: 0,
1466
+ borderWidth: '0.5px',
1467
+ },
1468
+ ],
1469
+ {
1470
+ duration: 800 + i * 120,
1471
+ delay: i * 60,
1472
+ easing: 'cubic-bezier(0, 0.55, 0.45, 1)',
1473
+ fill: 'forwards',
1474
+ }
1475
+ ).onfinish = () => ring.remove()
1476
+ }
1477
+
1478
+ // 5. Central cosmic flash burst - white core with H-alpha corona
1479
+ const flash = document.createElement('div')
1480
+ flash.className = 'pointer-events-none'
1481
+ flash.style.cssText = `
1482
+ position: absolute;
1483
+ left: ${bx}px;
1484
+ top: ${by}px;
1485
+ width: 10px;
1486
+ height: 10px;
1487
+ border-radius: 9999px;
1488
+ background: radial-gradient(circle,
1489
+ white 0%,
1490
+ ${COSMIC_COLORS.stellar} 20%,
1491
+ ${COSMIC_COLORS.hAlpha} 50%,
1492
+ ${COSMIC_COLORS.oiiiDim} 70%,
1493
+ transparent 100%
1494
+ );
1495
+ transform: translate(-50%, -50%) scale(0);
1496
+ z-index: 10000;
1497
+ will-change: transform, opacity;
1498
+ `
1499
+ this.element?.appendChild(flash)
1500
+
1501
+ flash.animate(
1502
+ [
1503
+ { transform: 'translate(-50%, -50%) scale(0)', opacity: 1 },
1504
+ { transform: 'translate(-50%, -50%) scale(5)', opacity: 1, offset: 0.12 },
1505
+ { transform: 'translate(-50%, -50%) scale(8)', opacity: 0 },
1506
+ ],
1507
+ { duration: 450, easing: 'ease-out', fill: 'forwards' }
1508
+ ).onfinish = () => flash.remove()
1509
+
1510
+ // 6. Cosmic afterglow nebula - layered emission colors
1511
+ const afterglow = document.createElement('div')
1512
+ afterglow.className = 'pointer-events-none'
1513
+ afterglow.style.cssText = `
1514
+ position: absolute;
1515
+ left: ${bx}px;
1516
+ top: ${by}px;
1517
+ width: 50px;
1518
+ height: 50px;
1519
+ border-radius: 9999px;
1520
+ background: radial-gradient(circle,
1521
+ ${COSMIC_COLORS.stellarDim} 0%,
1522
+ ${COSMIC_COLORS.hAlphaDim} 30%,
1523
+ ${COSMIC_COLORS.oiiiDim} 50%,
1524
+ ${COSMIC_COLORS.deepPurple} 70%,
1525
+ transparent 100%
1526
+ );
1527
+ transform: translate(-50%, -50%);
1528
+ z-index: 9989;
1529
+ will-change: opacity;
1530
+ mix-blend-mode: screen;
1531
+ `
1532
+ this.element?.appendChild(afterglow)
1533
+
1534
+ afterglow.animate(
1535
+ [
1536
+ { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' },
1537
+ { opacity: 0.6, transform: 'translate(-50%, -50%) scale(1.8)', offset: 0.4 },
1538
+ { opacity: 0, transform: 'translate(-50%, -50%) scale(2.5)' },
1539
+ ],
1540
+ { duration: 900, easing: 'ease-out', fill: 'forwards' }
1541
+ ).onfinish = () => {
1542
+ afterglow.remove()
1543
+ this.state$.next('done')
1544
+ }
1545
+
1546
+ // 7. BIG BANG SHOCKWAVE on host element - violent impact then settle
1547
+ if (this.homeElement) {
1548
+ const el = this.homeElement
1549
+ const originalTransform = el.style.transform || ''
1550
+
1551
+ // Phase 1: Violent shockwave impact with shake
1552
+ el.animate(
1553
+ [
1554
+ // Initial impact - sudden expansion with bright flash
1555
+ { transform: `${originalTransform} translate(0, 0) scale(1) rotate(0deg)`, filter: 'brightness(1)' },
1556
+ { transform: `${originalTransform} translate(0, 0) scale(1.04) rotate(0deg)`, filter: `brightness(2) drop-shadow(0 0 30px ${color})`, offset: 0.05 },
1557
+ // Violent shake sequence
1558
+ { transform: `${originalTransform} translate(-4px, 2px) scale(1.025) rotate(-0.8deg)`, filter: `brightness(1.6) drop-shadow(0 0 25px ${color})`, offset: 0.1 },
1559
+ { transform: `${originalTransform} translate(5px, -3px) scale(0.98) rotate(1deg)`, filter: `brightness(1.4) drop-shadow(0 0 20px ${color})`, offset: 0.15 },
1560
+ { transform: `${originalTransform} translate(-3px, -2px) scale(1.02) rotate(-0.6deg)`, filter: `brightness(1.3) drop-shadow(0 0 18px ${color})`, offset: 0.2 },
1561
+ { transform: `${originalTransform} translate(4px, 3px) scale(0.985) rotate(0.8deg)`, filter: `brightness(1.25) drop-shadow(0 0 15px ${color})`, offset: 0.25 },
1562
+ { transform: `${originalTransform} translate(-2px, 1px) scale(1.015) rotate(-0.5deg)`, filter: `brightness(1.2) drop-shadow(0 0 12px ${color})`, offset: 0.3 },
1563
+ { transform: `${originalTransform} translate(3px, -2px) scale(0.99) rotate(0.6deg)`, filter: `brightness(1.18) drop-shadow(0 0 10px ${color})`, offset: 0.35 },
1564
+ { transform: `${originalTransform} translate(-2px, 2px) scale(1.01) rotate(-0.4deg)`, filter: `brightness(1.15) drop-shadow(0 0 8px ${color})`, offset: 0.4 },
1565
+ { transform: `${originalTransform} translate(2px, -1px) scale(0.995) rotate(0.4deg)`, filter: 'brightness(1.12)', offset: 0.45 },
1566
+ { transform: `${originalTransform} translate(-1px, 1px) scale(1.008) rotate(-0.3deg)`, filter: 'brightness(1.1)', offset: 0.5 },
1567
+ { transform: `${originalTransform} translate(1px, -1px) scale(0.997) rotate(0.25deg)`, filter: 'brightness(1.08)', offset: 0.55 },
1568
+ { transform: `${originalTransform} translate(-1px, 0px) scale(1.005) rotate(-0.2deg)`, filter: 'brightness(1.06)', offset: 0.6 },
1569
+ { transform: `${originalTransform} translate(1px, 1px) scale(0.998) rotate(0.15deg)`, filter: 'brightness(1.05)', offset: 0.65 },
1570
+ { transform: `${originalTransform} translate(0px, -1px) scale(1.003) rotate(-0.1deg)`, filter: 'brightness(1.04)', offset: 0.7 },
1571
+ { transform: `${originalTransform} translate(-1px, 0px) scale(0.999) rotate(0.08deg)`, filter: 'brightness(1.03)', offset: 0.75 },
1572
+ { transform: `${originalTransform} translate(0px, 1px) scale(1.002) rotate(-0.05deg)`, filter: 'brightness(1.02)', offset: 0.8 },
1573
+ { transform: `${originalTransform} translate(0px, 0px) scale(1.001) rotate(0.03deg)`, filter: 'brightness(1.01)', offset: 0.9 },
1574
+ { transform: `${originalTransform} translate(0, 0) scale(1) rotate(0deg)`, filter: 'brightness(1)' },
1575
+ ],
1576
+ {
1577
+ duration: 800,
1578
+ easing: 'linear',
1579
+ }
1580
+ )
1581
+ }
1582
+ }
1583
+
1584
+ /**
1585
+ * Create the canvas for real-time trail rendering
1586
+ */
1587
+ private ensureTrailCanvas(): void {
1588
+ if (this.trailCanvas || !this.element) return
1589
+
1590
+ const hostW = this.element.scrollWidth
1591
+ const hostH = this.element.scrollHeight
1592
+ const canvas = document.createElement('canvas')
1593
+ canvas.width = hostW * window.devicePixelRatio
1594
+ canvas.height = hostH * window.devicePixelRatio
1595
+ canvas.style.cssText = `
1596
+ position: absolute;
1597
+ top: 0;
1598
+ left: 0;
1599
+ width: ${hostW}px;
1600
+ height: ${hostH}px;
1601
+ pointer-events: none;
1602
+ z-index: 9990;
1603
+ `
1604
+ this.element.appendChild(canvas)
1605
+ this.trailCanvas = canvas
1606
+ this.trailCtx = canvas.getContext('2d')
1607
+ if (this.trailCtx) {
1608
+ this.trailCtx.scale(window.devicePixelRatio, window.devicePixelRatio)
1609
+ }
1610
+ }
1611
+
1612
+ /**
1613
+ * Draw trail segment from last position to current position
1614
+ */
1615
+ private drawTrail(x: number, y: number): void {
1616
+ if (!this.options.showConnections) return
1617
+ this.ensureTrailCanvas()
1618
+ if (!this.trailCtx || !this.lastTrailPos) {
1619
+ this.lastTrailPos = { x, y }
1620
+ return
1621
+ }
1622
+
1623
+ const ctx = this.trailCtx
1624
+ const color = this.currentColor
1625
+
1626
+ // Draw line segment from last position to current
1627
+ ctx.beginPath()
1628
+ ctx.moveTo(this.lastTrailPos.x, this.lastTrailPos.y)
1629
+ ctx.lineTo(x, y)
1630
+ ctx.strokeStyle = color
1631
+ ctx.lineWidth = 3
1632
+ ctx.lineCap = 'round'
1633
+ ctx.globalAlpha = 0.6
1634
+ ctx.shadowColor = color
1635
+ ctx.shadowBlur = 8
1636
+ ctx.stroke()
1637
+ ctx.globalAlpha = 1
1638
+ ctx.shadowBlur = 0
1639
+
1640
+ this.lastTrailPos = { x, y }
1641
+ }
1642
+
1643
+ /**
1644
+ * Reset trail position (called when starting new flight segment)
1645
+ */
1646
+ private resetTrail(): void {
1647
+ this.lastTrailPos = null
1648
+ }
1649
+
1650
+ /**
1651
+ * Fade out the entire trail canvas
1652
+ */
1653
+ private fadeTrail(): void {
1654
+ if (!this.trailCtx || !this.element) return
1655
+ const hostW = this.element.scrollWidth
1656
+ const hostH = this.element.scrollHeight
1657
+ // Gradually fade existing content
1658
+ this.trailCtx.globalCompositeOperation = 'destination-out'
1659
+ this.trailCtx.fillStyle = 'rgba(0, 0, 0, 0.02)'
1660
+ this.trailCtx.fillRect(0, 0, hostW, hostH)
1661
+ this.trailCtx.globalCompositeOperation = 'source-over'
1662
+ }
1663
+
1664
+ private cleanup(): void {
1665
+ this.destroyed$.next(true)
1666
+
1667
+ if (this.rafId) {
1668
+ cancelAnimationFrame(this.rafId)
1669
+ this.rafId = 0
1670
+ }
1671
+
1672
+ // Cleanup shimmer
1673
+ this.shimmerAnimation?.cancel()
1674
+ this.shimmerOverlay?.remove()
1675
+ this.shimmerAnimation = null
1676
+ this.shimmerOverlay = null
1677
+
1678
+ // Cleanup pulse rings
1679
+ this.pulseRings.forEach(r => r.remove())
1680
+ this.pulseRings = []
1681
+
1682
+ // Cleanup trail canvas
1683
+ this.trailCanvas?.remove()
1684
+ this.trailCanvas = null
1685
+ this.trailCtx = null
1686
+ this.lastTrailPos = null
1687
+
1688
+ // Cleanup enhanced blackhole elements
1689
+ this.accretionDisks.forEach(d => d.remove())
1690
+ this.accretionDisks = []
1691
+ this.lensingRing?.remove()
1692
+ this.lensingRing = null
1693
+ this.eventHorizon?.remove()
1694
+ this.eventHorizon = null
1695
+ this.homeElement = null
1696
+
1697
+ this.bird?.remove()
1698
+ this.blackHole?.remove()
1699
+ this.particles.forEach(p => p.remove())
1700
+
1701
+ this.bird = null
1702
+ this.blackHole = null
1703
+ this.particles = []
1704
+ this.context = null
1705
+ }
1706
+
1707
+ override disconnected(): void {
1708
+ this.cleanup()
1709
+ }
1710
+ }
1711
+
1712
+ export const hummingbird = directive(HummingbirdDirective)