@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.
- package/dist/agent/{overlay.confirm-body-xfOh5Q28.js → overlay.confirm-body-DozUyDYx.js} +242 -242
- package/dist/agent/{overlay.confirm-body-xfOh5Q28.js.map → overlay.confirm-body-DozUyDYx.js.map} +1 -1
- package/dist/agent/schmancy.agent.js +5670 -2939
- package/dist/agent/schmancy.agent.js.map +1 -1
- package/dist/{area-Ddk7P5wD.js → area-1EG1LrkX.js} +1 -1
- package/dist/{area-Ddk7P5wD.js.map → area-1EG1LrkX.js.map} +1 -1
- package/dist/{area-Cbkt0NX4.cjs → area-DrVE5pXW.cjs} +1 -1
- package/dist/{area-Cbkt0NX4.cjs.map → area-DrVE5pXW.cjs.map} +1 -1
- package/dist/area.cjs +1 -1
- package/dist/area.js +1 -1
- package/dist/{autocomplete-CfBFDSc3.cjs → autocomplete-6pdZxEab.cjs} +1 -1
- package/dist/{autocomplete-CfBFDSc3.cjs.map → autocomplete-6pdZxEab.cjs.map} +1 -1
- package/dist/{autocomplete-Ds3Q2cwR.js → autocomplete-nrIcCilw.js} +2 -2
- package/dist/{autocomplete-Ds3Q2cwR.js.map → autocomplete-nrIcCilw.js.map} +1 -1
- package/dist/autocomplete.cjs +1 -1
- package/dist/autocomplete.js +1 -1
- package/dist/avatar.cjs +2 -2
- package/dist/avatar.cjs.map +1 -1
- package/dist/avatar.js +3 -3
- package/dist/badge.cjs +1 -1
- package/dist/badge.js +1 -1
- package/dist/{boat-BF5P6p_f.js → boat--fLgbDAE.js} +3 -3
- package/dist/{boat-BF5P6p_f.js.map → boat--fLgbDAE.js.map} +1 -1
- package/dist/{boat-BPN8HLzZ.cjs → boat-BIB-gPqy.cjs} +1 -1
- package/dist/{boat-BPN8HLzZ.cjs.map → boat-BIB-gPqy.cjs.map} +1 -1
- package/dist/boat.cjs +1 -1
- package/dist/boat.js +1 -1
- package/dist/breadcrumb.cjs +1 -1
- package/dist/breadcrumb.js +1 -1
- package/dist/{busy-C7ejPa-Q.cjs → busy-DVCIxBVf.cjs} +1 -1
- package/dist/{busy-C7ejPa-Q.cjs.map → busy-DVCIxBVf.cjs.map} +1 -1
- package/dist/{busy-BuACDJy6.js → busy-DshZcVZ4.js} +1 -1
- package/dist/{busy-BuACDJy6.js.map → busy-DshZcVZ4.js.map} +1 -1
- package/dist/busy.cjs +1 -1
- package/dist/busy.js +1 -1
- package/dist/{button-C1IMGS6M.js → button-D9UJ7I6Z.js} +5 -5
- package/dist/{button-C1IMGS6M.js.map → button-D9UJ7I6Z.js.map} +1 -1
- package/dist/{button-CWNbPPq-.cjs → button-JrTMzwHY.cjs} +4 -4
- package/dist/{button-CWNbPPq-.cjs.map → button-JrTMzwHY.cjs.map} +1 -1
- package/dist/button.cjs +1 -1
- package/dist/button.js +1 -1
- package/dist/{card-CgQwXO8L.js → card-BvCFuX3J.js} +2 -2
- package/dist/{card-CgQwXO8L.js.map → card-BvCFuX3J.js.map} +1 -1
- package/dist/{card-BIzaLuEg.cjs → card-CTotavhH.cjs} +1 -1
- package/dist/{card-BIzaLuEg.cjs.map → card-CTotavhH.cjs.map} +1 -1
- package/dist/card.cjs +1 -1
- package/dist/card.js +1 -1
- package/dist/{checkbox-BAqE3sTx.cjs → checkbox-By4fFmjt.cjs} +1 -1
- package/dist/{checkbox-BAqE3sTx.cjs.map → checkbox-By4fFmjt.cjs.map} +1 -1
- package/dist/{checkbox-BNdg57Om.js → checkbox-GPsdCUbs.js} +1 -1
- package/dist/{checkbox-BNdg57Om.js.map → checkbox-GPsdCUbs.js.map} +1 -1
- package/dist/checkbox.cjs +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/{chips-DnqLaOb1.js → chips-27umqnat.js} +4 -4
- package/dist/{chips-DnqLaOb1.js.map → chips-27umqnat.js.map} +1 -1
- package/dist/{chips-DS3y4Lbn.cjs → chips-BZf9sGA8.cjs} +1 -1
- package/dist/{chips-DS3y4Lbn.cjs.map → chips-BZf9sGA8.cjs.map} +1 -1
- package/dist/chips.cjs +1 -1
- package/dist/chips.js +2 -2
- package/dist/connectivity.cjs +1 -1
- package/dist/connectivity.js +1 -1
- package/dist/content-drawer.cjs +1 -1
- package/dist/content-drawer.js +1 -1
- package/dist/{cursor-glow-Cs2XLDB9.js → cursor-glow-Ah7VXSj7.js} +1 -1
- package/dist/{cursor-glow-Cs2XLDB9.js.map → cursor-glow-Ah7VXSj7.js.map} +1 -1
- package/dist/{cursor-glow-C8LgCxpI.cjs → cursor-glow-Bulq-38P.cjs} +1 -1
- package/dist/{cursor-glow-C8LgCxpI.cjs.map → cursor-glow-Bulq-38P.cjs.map} +1 -1
- package/dist/{date-range-VA1mi1N7.cjs → date-range-BJnLWCRF.cjs} +1 -1
- package/dist/{date-range-VA1mi1N7.cjs.map → date-range-BJnLWCRF.cjs.map} +1 -1
- package/dist/{date-range-inline-CAa0_4EI.cjs → date-range-inline-B6uKUliV.cjs} +1 -1
- package/dist/{date-range-inline-CAa0_4EI.cjs.map → date-range-inline-B6uKUliV.cjs.map} +1 -1
- package/dist/{date-range-inline-PeRt1iIF.js → date-range-inline-BNbbRfIA.js} +1 -1
- package/dist/{date-range-inline-PeRt1iIF.js.map → date-range-inline-BNbbRfIA.js.map} +1 -1
- package/dist/date-range-inline.cjs +1 -1
- package/dist/date-range-inline.js +1 -1
- package/dist/{date-range-CAqB-B0M.js → date-range-wDVHcr0u.js} +2 -2
- package/dist/{date-range-CAqB-B0M.js.map → date-range-wDVHcr0u.js.map} +1 -1
- package/dist/date-range.cjs +1 -1
- package/dist/date-range.js +1 -1
- package/dist/delay.cjs +1 -1
- package/dist/delay.js +2 -2
- package/dist/{details-BpFjVclg.js → details-Ckxpwacj.js} +4 -4
- package/dist/{details-BpFjVclg.js.map → details-Ckxpwacj.js.map} +1 -1
- package/dist/{details-BnXbDpt7.cjs → details-DNrWIes6.cjs} +1 -1
- package/dist/{details-BnXbDpt7.cjs.map → details-DNrWIes6.cjs.map} +1 -1
- package/dist/details.cjs +1 -1
- package/dist/details.js +1 -1
- package/dist/directives-BBMqe8x3.js +4082 -0
- package/dist/directives-BBMqe8x3.js.map +1 -0
- package/dist/directives-F15SJZUR.cjs +348 -0
- package/dist/directives-F15SJZUR.cjs.map +1 -0
- package/dist/directives.cjs +1 -99
- package/dist/directives.js +6 -1363
- package/dist/discovery.cjs +1 -1
- package/dist/discovery.js +2 -61
- package/dist/discovery.service-COmbHaoI.js +61 -0
- package/dist/discovery.service-COmbHaoI.js.map +1 -0
- package/dist/discovery.service-CVDXO9rH.cjs +1 -0
- package/dist/discovery.service-CVDXO9rH.cjs.map +1 -0
- package/dist/{divider-D8cBBkdG.js → divider-BzcZGo4S.js} +1 -1
- package/dist/{divider-D8cBBkdG.js.map → divider-BzcZGo4S.js.map} +1 -1
- package/dist/{divider-B84lt1A3.cjs → divider-Cde33ivs.cjs} +1 -1
- package/dist/{divider-B84lt1A3.cjs.map → divider-Cde33ivs.cjs.map} +1 -1
- package/dist/divider.cjs +1 -1
- package/dist/divider.js +1 -1
- package/dist/dropdown.cjs +1 -1
- package/dist/dropdown.js +1 -1
- package/dist/{expand-BJiKggfg.js → expand-DI144OzN.js} +3 -3
- package/dist/{expand-BJiKggfg.js.map → expand-DI144OzN.js.map} +1 -1
- package/dist/{expand-DK-O37-j.cjs → expand-Db4V0jj-.cjs} +1 -1
- package/dist/{expand-DK-O37-j.cjs.map → expand-Db4V0jj-.cjs.map} +1 -1
- package/dist/expand.cjs +1 -1
- package/dist/expand.js +1 -1
- package/dist/{float-RWR6Q1Hh.cjs → float--RScf9BZ.cjs} +1 -1
- package/dist/{float-RWR6Q1Hh.cjs.map → float--RScf9BZ.cjs.map} +1 -1
- package/dist/{float-B4FDN40h.js → float-DIyzy1c2.js} +1 -1
- package/dist/{float-B4FDN40h.js.map → float-DIyzy1c2.js.map} +1 -1
- package/dist/float.cjs +1 -1
- package/dist/float.js +1 -1
- package/dist/{form-ha3df3K7.cjs → form-DWNpOsIU.cjs} +1 -1
- package/dist/{form-ha3df3K7.cjs.map → form-DWNpOsIU.cjs.map} +1 -1
- package/dist/{form-B-Sm6u25.js → form-RtXH8UHQ.js} +8 -8
- package/dist/{form-B-Sm6u25.js.map → form-RtXH8UHQ.js.map} +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.js +6 -6
- package/dist/handover/agent-runtime-followups.md +1 -1
- package/dist/handover/agent-runtime-v1.md +3 -3
- package/dist/{hashContent-dJrI-9sc.js.map → hashContent-Dgmzc32o.js.map} +1 -1
- package/dist/{hashContent-Ck6laKlk.cjs.map → hashContent-Dh1VzIAb.cjs.map} +1 -1
- package/dist/icons-DXanGDZ_.js +52 -0
- package/dist/icons-DXanGDZ_.js.map +1 -0
- package/dist/icons-bNxlWLlk.cjs +24 -0
- package/dist/icons-bNxlWLlk.cjs.map +1 -0
- package/dist/icons.cjs +1 -1
- package/dist/icons.js +1 -1
- package/dist/{iframe-BXe1TPx1.cjs → iframe-B1XWRaLC.cjs} +1 -1
- package/dist/{iframe-BXe1TPx1.cjs.map → iframe-B1XWRaLC.cjs.map} +1 -1
- package/dist/{iframe-CByrVlZy.js → iframe-BlHK0cjy.js} +1 -1
- package/dist/{iframe-CByrVlZy.js.map → iframe-BlHK0cjy.js.map} +1 -1
- package/dist/iframe.cjs +1 -1
- package/dist/iframe.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +60 -60
- package/dist/{input-BY9OCQWr.cjs → input-C-_XU9AX.cjs} +1 -1
- package/dist/{input-BY9OCQWr.cjs.map → input-C-_XU9AX.cjs.map} +1 -1
- package/dist/{input-Q0fm34Co.js → input-CiGa8Dkl.js} +1 -1
- package/dist/{input-Q0fm34Co.js.map → input-CiGa8Dkl.js.map} +1 -1
- package/dist/{input-chip-BwNf3GD0.cjs → input-chip-5aYnuRZ_.cjs} +1 -1
- package/dist/{input-chip-BwNf3GD0.cjs.map → input-chip-5aYnuRZ_.cjs.map} +1 -1
- package/dist/{input-chip-CytUirVS.js → input-chip-l--zCMGR.js} +1 -1
- package/dist/{input-chip-CytUirVS.js.map → input-chip-l--zCMGR.js.map} +1 -1
- package/dist/input.cjs +1 -1
- package/dist/input.js +1 -1
- package/dist/json.cjs +2 -2
- package/dist/json.cjs.map +1 -1
- package/dist/json.js +3 -3
- package/dist/json.js.map +1 -1
- package/dist/kbd.cjs +1 -1
- package/dist/kbd.js +1 -1
- package/dist/{layout-Dq2oeOTS.js → layout-DSAjo92m.js} +1 -1
- package/dist/{layout-Dq2oeOTS.js.map → layout-DSAjo92m.js.map} +1 -1
- package/dist/{layout-BbCIfIgo.cjs → layout-eXb9wjDh.cjs} +1 -1
- package/dist/{layout-BbCIfIgo.cjs.map → layout-eXb9wjDh.cjs.map} +1 -1
- package/dist/layout.cjs +1 -1
- package/dist/layout.js +1 -1
- package/dist/{lightbox-p2E0oVR0.cjs → lightbox-CfRDkeeb.cjs} +2 -2
- package/dist/{lightbox-p2E0oVR0.cjs.map → lightbox-CfRDkeeb.cjs.map} +1 -1
- package/dist/{lightbox-Ckvn5YNF.js → lightbox-D9oiu1Nv.js} +2 -2
- package/dist/{lightbox-Ckvn5YNF.js.map → lightbox-D9oiu1Nv.js.map} +1 -1
- package/dist/lightbox.cjs +1 -1
- package/dist/lightbox.js +1 -1
- package/dist/{list-CsrPVvmm.js → list-BOlRka4v.js} +1 -1
- package/dist/{list-CsrPVvmm.js.map → list-BOlRka4v.js.map} +1 -1
- package/dist/{list-r57UFHu3.cjs → list-CDJi3_Ut.cjs} +1 -1
- package/dist/{list-r57UFHu3.cjs.map → list-CDJi3_Ut.cjs.map} +1 -1
- package/dist/list.cjs +1 -1
- package/dist/list.js +1 -1
- package/dist/{magnetic-Bgh7aHHI.cjs → magnetic-D-ph029G.cjs} +1 -1
- package/dist/{magnetic-Bgh7aHHI.cjs.map → magnetic-D-ph029G.cjs.map} +1 -1
- package/dist/{magnetic-DxvoEz8_.js → magnetic-mHXl54Z8.js} +1 -1
- package/dist/{magnetic-DxvoEz8_.js.map → magnetic-mHXl54Z8.js.map} +1 -1
- package/dist/{menu-DBuZiPyW.cjs → menu-CJaDL2cd.cjs} +1 -1
- package/dist/{menu-DBuZiPyW.cjs.map → menu-CJaDL2cd.cjs.map} +1 -1
- package/dist/{menu-Csm6Fg88.js → menu-XyrLmCi_.js} +2 -2
- package/dist/{menu-Csm6Fg88.js.map → menu-XyrLmCi_.js.map} +1 -1
- package/dist/menu.cjs +1 -1
- package/dist/menu.js +1 -1
- package/dist/mixins-CsYsIJOI.cjs +254 -0
- package/dist/mixins-CsYsIJOI.cjs.map +1 -0
- package/dist/mixins-DySzfmal.js +642 -0
- package/dist/mixins-DySzfmal.js.map +1 -0
- package/dist/mixins.cjs +1 -1
- package/dist/mixins.js +2 -2
- package/dist/nav-drawer.cjs +1 -1
- package/dist/nav-drawer.js +1 -1
- package/dist/navigation-bar.cjs +1 -1
- package/dist/navigation-bar.js +1 -1
- package/dist/navigation-rail.cjs +5 -9
- package/dist/navigation-rail.cjs.map +1 -1
- package/dist/navigation-rail.js +5 -11
- package/dist/navigation-rail.js.map +1 -1
- package/dist/{notification-CgTBiAdf.js → notification-CHrEY4u8.js} +2 -2
- package/dist/{notification-CgTBiAdf.js.map → notification-CHrEY4u8.js.map} +1 -1
- package/dist/{notification-58tkVys8.cjs → notification-DKp4tjaB.cjs} +1 -1
- package/dist/{notification-58tkVys8.cjs.map → notification-DKp4tjaB.cjs.map} +1 -1
- package/dist/notification.cjs +1 -1
- package/dist/notification.js +1 -1
- package/dist/{option-Bicf6xpI.js → option-Vpy4UQ-D.js} +1 -1
- package/dist/{option-Bicf6xpI.js.map → option-Vpy4UQ-D.js.map} +1 -1
- package/dist/{option-61YE3gub.cjs → option-nRk4MuXH.cjs} +1 -1
- package/dist/{option-61YE3gub.cjs.map → option-nRk4MuXH.cjs.map} +1 -1
- package/dist/option.cjs +1 -1
- package/dist/option.js +1 -1
- package/dist/{overlay-CpvmytrQ.cjs → overlay-HNrWZ4sB.cjs} +1 -1
- package/dist/{overlay-CpvmytrQ.cjs.map → overlay-HNrWZ4sB.cjs.map} +1 -1
- package/dist/{overlay-CAI2FAp7.js → overlay-jlkcrt8F.js} +5 -5
- package/dist/{overlay-CAI2FAp7.js.map → overlay-jlkcrt8F.js.map} +1 -1
- package/dist/{overlay-stack-Dk0xETTy.cjs.map → overlay-stack-Bdr9lOqi.cjs.map} +1 -1
- package/dist/{overlay-stack-BR4iYivO.js.map → overlay-stack-D2rgxQLh.js.map} +1 -1
- package/dist/overlay.cjs +1 -1
- package/dist/{overlay.confirm-body-QD-5cj3_.cjs → overlay.confirm-body-B8dFI3cj.cjs} +1 -1
- package/dist/{overlay.confirm-body-QD-5cj3_.cjs.map → overlay.confirm-body-B8dFI3cj.cjs.map} +1 -1
- package/dist/{overlay.confirm-body-Cq25CkTw.js → overlay.confirm-body-CYShkjI6.js} +1 -1
- package/dist/{overlay.confirm-body-Cq25CkTw.js.map → overlay.confirm-body-CYShkjI6.js.map} +1 -1
- package/dist/overlay.js +3 -3
- package/dist/{overlay.service-BG0bqPwJ.cjs → overlay.service-BTPn7Uv7.cjs} +1 -1
- package/dist/{overlay.service-BG0bqPwJ.cjs.map → overlay.service-BTPn7Uv7.cjs.map} +1 -1
- package/dist/{overlay.service-Bpjrhaxh.js → overlay.service-BqhhxVJp.js} +2 -2
- package/dist/{overlay.service-Bpjrhaxh.js.map → overlay.service-BqhhxVJp.js.map} +1 -1
- package/dist/{progress-Zqx-S9NZ.js → progress-8Bn88GK_.js} +1 -1
- package/dist/{progress-Zqx-S9NZ.js.map → progress-8Bn88GK_.js.map} +1 -1
- package/dist/{progress-D8XZJVl5.cjs → progress-CAp_4jtq.cjs} +1 -1
- package/dist/{progress-D8XZJVl5.cjs.map → progress-CAp_4jtq.cjs.map} +1 -1
- package/dist/progress.cjs +1 -1
- package/dist/progress.js +1 -1
- package/dist/{radio-group-bl8K4Gls.cjs → radio-group-CN44mAoc.cjs} +1 -1
- package/dist/{radio-group-bl8K4Gls.cjs.map → radio-group-CN44mAoc.cjs.map} +1 -1
- package/dist/{radio-group-D9MU1Mxz.js → radio-group-GNHA7qJR.js} +1 -1
- package/dist/{radio-group-D9MU1Mxz.js.map → radio-group-GNHA7qJR.js.map} +1 -1
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/range.cjs +1 -1
- package/dist/range.js +1 -1
- package/dist/{reduced-motion-D7LqTUMn.js.map → reduced-motion-D-L12p7G.js.map} +1 -1
- package/dist/{reduced-motion-Dzfp_w5x.cjs.map → reduced-motion-Ds-HjMzn.cjs.map} +1 -1
- package/dist/{rxjs-utils-BK8VMe3K.js.map → rxjs-utils-BXpvHN4-.js.map} +1 -1
- package/dist/{rxjs-utils-DhOKenkS.cjs.map → rxjs-utils-CaC-tdot.cjs.map} +1 -1
- package/dist/rxjs-utils.cjs +1 -1
- package/dist/rxjs-utils.js +1 -1
- package/dist/{select-CMwkl-D6.js → select-BnuXRHS4.js} +3 -3
- package/dist/{select-CMwkl-D6.js.map → select-BnuXRHS4.js.map} +1 -1
- package/dist/{select-COIfVtZl.cjs → select-DZNns5Pa.cjs} +2 -2
- package/dist/{select-COIfVtZl.cjs.map → select-DZNns5Pa.cjs.map} +1 -1
- package/dist/select.cjs +1 -1
- package/dist/select.js +1 -1
- package/dist/skeleton.cjs +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/skills/INDEX.md +2 -1
- package/dist/skills/SKILL.md +33 -23
- package/dist/skills/area.md +5 -4
- package/dist/skills/connectivity.md +1 -3
- package/dist/skills/directives.md +36 -0
- package/dist/skills/icons.md +95 -31
- package/dist/skills/layout.md +36 -53
- package/dist/skills/mixins.md +26 -5
- package/dist/skills/schmancy/INDEX.md +2 -1
- package/dist/skills/schmancy/SKILL.md +33 -23
- package/dist/skills/schmancy/area.md +5 -4
- package/dist/skills/schmancy/connectivity.md +1 -3
- package/dist/skills/schmancy/directives.md +36 -0
- package/dist/skills/schmancy/icons.md +95 -31
- package/dist/skills/schmancy/layout.md +36 -53
- package/dist/skills/schmancy/mixins.md +26 -5
- package/dist/slider.cjs +1 -1
- package/dist/slider.js +1 -1
- package/dist/{splash-screen-xrMNpzkm.js → splash-screen-CUP_elaT.js} +1 -1
- package/dist/{splash-screen-xrMNpzkm.js.map → splash-screen-CUP_elaT.js.map} +1 -1
- package/dist/{splash-screen-2hxq8Sft.cjs → splash-screen-DeoPRrOu.cjs} +1 -1
- package/dist/{splash-screen-2hxq8Sft.cjs.map → splash-screen-DeoPRrOu.cjs.map} +1 -1
- package/dist/splash-screen.cjs +1 -1
- package/dist/splash-screen.js +1 -1
- package/dist/{src-B15R32Sp.js → src-B1VkLX3l.js} +159 -159
- package/dist/src-B1VkLX3l.js.map +1 -0
- package/dist/{src-BWQvtOOf.cjs → src-DQ4wr0qq.cjs} +13 -13
- package/dist/src-DQ4wr0qq.cjs.map +1 -0
- package/dist/steps.cjs +1 -1
- package/dist/steps.js +1 -1
- package/dist/{surface-BkQ44Wuo.cjs → surface-LkaZQXZn.cjs} +1 -1
- package/dist/{surface-BkQ44Wuo.cjs.map → surface-LkaZQXZn.cjs.map} +1 -1
- package/dist/{surface-3nnvlxeE.js → surface-hOvkrjGN.js} +1 -1
- package/dist/{surface-3nnvlxeE.js.map → surface-hOvkrjGN.js.map} +1 -1
- package/dist/surface.cjs +1 -1
- package/dist/surface.js +1 -1
- package/dist/switch.cjs +1 -1
- package/dist/switch.js +1 -1
- package/dist/table.cjs +2 -2
- package/dist/table.cjs.map +1 -1
- package/dist/table.js +2 -2
- package/dist/table.js.map +1 -1
- package/dist/{tabs-CnLIe8nE.js → tabs-CfwIHhHo.js} +1 -1
- package/dist/{tabs-CnLIe8nE.js.map → tabs-CfwIHhHo.js.map} +1 -1
- package/dist/{tabs-Dql0rcqZ.cjs → tabs-bplzstz6.cjs} +1 -1
- package/dist/{tabs-Dql0rcqZ.cjs.map → tabs-bplzstz6.cjs.map} +1 -1
- package/dist/tabs.cjs +1 -1
- package/dist/tabs.js +1 -1
- package/dist/teleport.cjs +1 -1
- package/dist/teleport.js +1 -1
- package/dist/{textarea-BAogS_Ff.js → textarea-C1A5xuw9.js} +1 -1
- package/dist/{textarea-BAogS_Ff.js.map → textarea-C1A5xuw9.js.map} +1 -1
- package/dist/{textarea-CGD6lAEe.cjs → textarea-hrDp5gQq.cjs} +1 -1
- package/dist/{textarea-CGD6lAEe.cjs.map → textarea-hrDp5gQq.cjs.map} +1 -1
- package/dist/textarea.cjs +1 -1
- package/dist/textarea.js +1 -1
- package/dist/{theme-CUK0HrS3.js → theme-BniFOMEo.js} +4 -4
- package/dist/{theme-CUK0HrS3.js.map → theme-BniFOMEo.js.map} +1 -1
- package/dist/{theme-DKrrQ-ic.cjs → theme-DmR6PKV8.cjs} +3 -3
- package/dist/{theme-DKrrQ-ic.cjs.map → theme-DmR6PKV8.cjs.map} +1 -1
- package/dist/{theme-button-Bb8qW2IH.js → theme-button--ruZIb0T.js} +1 -1
- package/dist/{theme-button-Bb8qW2IH.js.map → theme-button--ruZIb0T.js.map} +1 -1
- package/dist/{theme-button-CmTwFm3l.cjs → theme-button-a0LgZ7hQ.cjs} +1 -1
- package/dist/{theme-button-CmTwFm3l.cjs.map → theme-button-a0LgZ7hQ.cjs.map} +1 -1
- package/dist/theme-button.cjs +1 -1
- package/dist/theme-button.js +1 -1
- package/dist/theme.cjs +1 -1
- package/dist/{theme.interface-D4NeufQA.cjs.map → theme.interface-B5xjEk74.cjs.map} +1 -1
- package/dist/{theme.interface-C2XNgsLB.js.map → theme.interface-DVEw3s8m.js.map} +1 -1
- package/dist/theme.js +4 -4
- package/dist/{theme.service-CSzNkqBB.js.map → theme.service-Bh08uOSJ.js.map} +1 -1
- package/dist/{theme.service-CnFUmUpc.cjs.map → theme.service-Y-e8b331.cjs.map} +1 -1
- package/dist/tree.cjs +1 -1
- package/dist/tree.js +1 -1
- package/dist/typography.cjs +1 -1
- package/dist/typography.js +1 -1
- package/dist/{utils-Cxg0Kfy5.js → utils-578eFTx4.js} +1 -1
- package/dist/{utils-Cxg0Kfy5.js.map → utils-578eFTx4.js.map} +1 -1
- package/dist/{utils-aCJYAGUr.cjs → utils-CVWUrECT.cjs} +1 -1
- package/dist/{utils-aCJYAGUr.cjs.map → utils-CVWUrECT.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/dist/utils.js +2 -2
- package/dist/visually-hidden.cjs +1 -1
- package/dist/visually-hidden.js +1 -1
- package/dist/{window-DuDAQa6y.js → window-BT9JecWy.js} +5 -5
- package/dist/{window-DuDAQa6y.js.map → window-BT9JecWy.js.map} +1 -1
- package/dist/{window-BbWlaPZv.cjs → window-Bp7zWZpu.cjs} +1 -1
- package/dist/{window-BbWlaPZv.cjs.map → window-Bp7zWZpu.cjs.map} +1 -1
- package/dist/window.cjs +1 -1
- package/dist/window.js +1 -1
- package/package.json +1 -1
- package/skills/schmancy/INDEX.md +2 -1
- package/skills/schmancy/SKILL.md +33 -23
- package/skills/schmancy/area.md +5 -4
- package/skills/schmancy/connectivity.md +1 -3
- package/skills/schmancy/directives.md +36 -0
- package/skills/schmancy/icons.md +95 -31
- package/skills/schmancy/layout.md +36 -53
- package/skills/schmancy/mixins.md +26 -5
- package/src/badge/badge.ts +7 -11
- package/src/button/icon-button.ts +3 -4
- package/src/directives/ai-badge.ts +95 -0
- package/src/directives/art/art.directive.ts +228 -0
- package/src/directives/art/effects/error.ts +192 -0
- package/src/directives/art/effects/funkhaus.ts +121 -0
- package/src/directives/art/effects/howl.ts +203 -0
- package/src/directives/art/effects/samwa.ts +228 -0
- package/src/directives/art/effects/snow.ts +207 -0
- package/src/directives/art/effects/starfield.ts +107 -0
- package/src/directives/art/index.ts +2 -0
- package/src/directives/art/particle-pool.ts +40 -0
- package/src/directives/art/types.ts +129 -0
- package/src/directives/art/utils.ts +35 -0
- package/src/directives/battery.ts +1014 -0
- package/src/directives/beta.ts +44 -0
- package/src/directives/fill.ts +32 -60
- package/src/directives/fyi.ts +551 -0
- package/src/directives/hummingbird.ts +1712 -0
- package/src/directives/index.ts +9 -0
- package/src/directives/missed-punch.ts +407 -0
- package/src/directives/urgent.ts +660 -0
- package/src/directives/working-snake.ts +294 -0
- package/src/icons/icon.ts +53 -30
- package/src/json/json.ts +1 -1
- package/src/navigation-rail/navigation-rail-item.ts +7 -12
- package/src/navigation-rail/navigation-rail.ts +0 -2
- package/src/table/table.ts +2 -2
- package/src/theme/theme-audio-player.ts +1 -1
- package/types/mixins/SchmancyElement.d.ts +31 -0
- package/types/src/badge/badge.d.ts +1 -1
- package/types/src/directives/ai-badge.d.ts +15 -0
- package/types/src/directives/art/art.directive.d.ts +19 -0
- package/types/src/directives/art/effects/error.d.ts +7 -0
- package/types/src/directives/art/effects/funkhaus.d.ts +7 -0
- package/types/src/directives/art/effects/howl.d.ts +7 -0
- package/types/src/directives/art/effects/samwa.d.ts +7 -0
- package/types/src/directives/art/effects/snow.d.ts +7 -0
- package/types/src/directives/art/effects/starfield.d.ts +10 -0
- package/types/src/directives/art/index.d.ts +2 -0
- package/types/src/directives/art/particle-pool.d.ts +16 -0
- package/types/src/directives/art/types.d.ts +126 -0
- package/types/src/directives/art/utils.d.ts +5 -0
- package/types/src/directives/battery.d.ts +96 -0
- package/types/src/directives/beta.d.ts +33 -0
- package/types/src/directives/fill.d.ts +4 -11
- package/types/src/directives/fyi.d.ts +76 -0
- package/types/src/directives/hummingbird.d.ts +180 -0
- package/types/src/directives/index.d.ts +9 -0
- package/types/src/directives/missed-punch.d.ts +28 -0
- package/types/src/directives/urgent.d.ts +88 -0
- package/types/src/directives/working-snake.d.ts +46 -0
- package/types/src/icons/icon.d.ts +22 -0
- package/dist/directives.cjs.map +0 -1
- package/dist/directives.js.map +0 -1
- package/dist/discovery.cjs.map +0 -1
- package/dist/discovery.js.map +0 -1
- package/dist/icons-DJuXwn8D.js +0 -48
- package/dist/icons-DJuXwn8D.js.map +0 -1
- package/dist/icons-oNRUCAEY.cjs +0 -33
- package/dist/icons-oNRUCAEY.cjs.map +0 -1
- package/dist/mixins-DTCHPEd4.cjs +0 -254
- package/dist/mixins-DTCHPEd4.cjs.map +0 -1
- package/dist/mixins-pU53qf6R.js +0 -636
- package/dist/mixins-pU53qf6R.js.map +0 -1
- package/dist/skills/page.md +0 -84
- package/dist/skills/schmancy/page.md +0 -84
- package/dist/src-B15R32Sp.js.map +0 -1
- package/dist/src-BWQvtOOf.cjs.map +0 -1
- package/skills/schmancy/page.md +0 -84
- /package/dist/{hashContent-dJrI-9sc.js → hashContent-Dgmzc32o.js} +0 -0
- /package/dist/{hashContent-Ck6laKlk.cjs → hashContent-Dh1VzIAb.cjs} +0 -0
- /package/dist/{overlay-stack-Dk0xETTy.cjs → overlay-stack-Bdr9lOqi.cjs} +0 -0
- /package/dist/{overlay-stack-BR4iYivO.js → overlay-stack-D2rgxQLh.js} +0 -0
- /package/dist/{reduced-motion-D7LqTUMn.js → reduced-motion-D-L12p7G.js} +0 -0
- /package/dist/{reduced-motion-Dzfp_w5x.cjs → reduced-motion-Ds-HjMzn.cjs} +0 -0
- /package/dist/{rxjs-utils-BK8VMe3K.js → rxjs-utils-BXpvHN4-.js} +0 -0
- /package/dist/{rxjs-utils-DhOKenkS.cjs → rxjs-utils-CaC-tdot.cjs} +0 -0
- /package/dist/{theme.interface-D4NeufQA.cjs → theme.interface-B5xjEk74.cjs} +0 -0
- /package/dist/{theme.interface-C2XNgsLB.js → theme.interface-DVEw3s8m.js} +0 -0
- /package/dist/{theme.service-CSzNkqBB.js → theme.service-Bh08uOSJ.js} +0 -0
- /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)
|