From f2a68727caa2de9026b741d0d02f89a507ae174a Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Thu, 9 Feb 2023 21:10:58 -0800 Subject: [PATCH] repro --- Build/.DS_Store | Bin 0 -> 6148 bytes Build/assets/css/style.css | 67 + Build/assets/data/uil.1675992195953.json | 1 + Build/assets/data/uil.json | 1 + Build/assets/images/_scenelayout/black.jpg | Bin 0 -> 1129 bytes Build/assets/images/_scenelayout/mask.jpg | Bin 0 -> 1129 bytes Build/assets/images/_scenelayout/uv.jpg | Bin 0 -> 138695 bytes Build/assets/js/app--debug.1675992195953.js | 64554 ++++++++++++++++ Build/assets/js/app.1675992195953.js | 13 + Build/assets/js/hydra/hydra-thread.js | 1 + Build/assets/js/lib/basis_transcoder.js | 21 + Build/assets/js/lib/basis_transcoder.wasm | Bin 0 -> 474559 bytes Build/assets/js/lib/firebase-app.js | 1 + Build/assets/js/lib/firebase-auth.js | 1 + Build/assets/js/lib/firebase-database.js | 1 + Build/assets/js/lib/firebase-storage.js | 2 + .../assets/shaders/compiled.1675992195953.vs | 1768 + Build/index.html | 45 + Build/sw.js | 59 + 19 files changed, 66535 insertions(+) create mode 100644 Build/.DS_Store create mode 100644 Build/assets/css/style.css create mode 100644 Build/assets/data/uil.1675992195953.json create mode 100644 Build/assets/data/uil.json create mode 100755 Build/assets/images/_scenelayout/black.jpg create mode 100755 Build/assets/images/_scenelayout/mask.jpg create mode 100755 Build/assets/images/_scenelayout/uv.jpg create mode 100644 Build/assets/js/app--debug.1675992195953.js create mode 100644 Build/assets/js/app.1675992195953.js create mode 100644 Build/assets/js/hydra/hydra-thread.js create mode 100644 Build/assets/js/lib/basis_transcoder.js create mode 100755 Build/assets/js/lib/basis_transcoder.wasm create mode 100644 Build/assets/js/lib/firebase-app.js create mode 100644 Build/assets/js/lib/firebase-auth.js create mode 100644 Build/assets/js/lib/firebase-database.js create mode 100755 Build/assets/js/lib/firebase-storage.js create mode 100755 Build/assets/shaders/compiled.1675992195953.vs create mode 100644 Build/index.html create mode 100644 Build/sw.js diff --git a/Build/.DS_Store b/Build/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c32883842752acf742ff9e4a68f0123bb24f31f0 GIT binary patch literal 6148 zcmeHKISv9b4733uBpOP}e1RWC2wuPkI6EYYg?=mE#nTuc1!$pz0yJJZlQ^ENc8Yi{ zB09h9XCf038Nm(ZYD3p--@IeJj3^L}GtScNo857HIvqRNuLH&%%0?PIhey6`(5L_v zpaN8Y3Q&QME0862H2(O>Jdg@dfnQg^?uP<5tch))UmX~{1pp2ZcEj9z31G1RuqL*F zh`==`Eo-SGgIZ^>C@T@>TmJ_T0OZc1q z|Cz)U6`%rtrGO3=tHm6jl(n_);mIPrzF(ETwQppjHPjC z;)(b2GQw{zUmrV{Q%7}$-V1n;YN;RkII&^sp9is)>Ov7*qvxDP0xVpTsV*3KR`_mw z2u%`UsEF-`BkxPFql$7{+E?V~uqnwMBq>O0HzZk;x>{3)B?Ou+a!<7*Ygp!@mM)Y{ z&F6D*-V#Z4hGbP$k<>)ZW`lY(;)@`$(?$^QEh1Prb|NoKJQBb>qJ2VUi7wDeZ(8uf z@pu_{<+OZ1@2l{RlOudv#!Bz__#(vU2*+d=IheZdEyQ$o4=2hUX*HTfoSBb;t2_%X%@EypNwtC3v%*lKH#!m9C`1tJB)}AiwSAoiYk<@VYVcxr3|ED z-a+56+G@W)G}NwYHkGbWYTXsxm)iaRY7s34<=6YybuTEoL-}#Jywq5(KMv^5M|8Qf zUtp(LS>RsLN5o{`!3VIm1WQkBg?`yewUVP+s}`u$>h<+{y~c4H8yvTF7Yxg?)k>AC z)wu0VZgYEghhlfPa4LUWH`ZO<(#oy?zfNCug=KiK#xpF>WS_w+`qJ-WgZY;MkM1%2 UJtq4GwpfM+W_i#9y&u`vKZOq;)c^nh literal 0 HcmV?d00001 diff --git a/Build/assets/images/_scenelayout/mask.jpg b/Build/assets/images/_scenelayout/mask.jpg new file mode 100755 index 0000000000000000000000000000000000000000..b997bd032c4123b429c5cbc6ccef9b0a81225c1c GIT binary patch literal 1129 zcma)5O>fgc5M5&@F;SXgsRxcL_X3D`Z6|4CtENirMg+;IDwW{s+S|rT?X~O;aeLvy zzv4g-{0Kfyy(9PuW*w5W&<61&Tf0y5-ptO<7N3hN@Nhp5PXHJWp8yeDqxcRaXX1~~ z0S5#|4*)2xcx^;!Vj>i0iWiV$toS5CxtAbKQ4!eL%@fZbV+xP)X&77b&krAE7zUR7 zy6dWLV&hTx{36A#E?yq_7h~TDBRGGFFlAa>XFbt&XNY`5|qLrP;)XQ6Owz+U{aOS6BLPHY6($PC66Kct< z(%(mjlBMmPy}A+Ty8jN1qOt)q+l(IK>w51r&W_F#j1F-|CaI5E_+}|iOgqIMCFv0% zXNxW#j0h#!h$PUq%dTz1N3Q3GaY;U&sa)3_#u@cu9}gW%W+2iMW^>8^ z_oBE2lE_}Qz;hC~Cvm*Q6`#Nh_R{}^$oL ALI3~& literal 0 HcmV?d00001 diff --git a/Build/assets/images/_scenelayout/uv.jpg b/Build/assets/images/_scenelayout/uv.jpg new file mode 100755 index 0000000000000000000000000000000000000000..43da1da90598df182ebb7e5afe87247de7ad068e GIT binary patch literal 138695 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%0DgnOD zpGQdu?eO|VRw1HXMTi&;GQ!abI7iw(M=p@tprSe^+3Yp>*0zaL(h3tt0}ULc`T0Ft zlG>bkauH}`L>$z!fcDTVU3QX{Gc*`(Zn=y$}e> zrw~5N8(#Xr5LjhpFR$+jel8uemCkT)vr{Csk$zk%KGHXt#_b1GXa&9G7dn>AHT}F# zxwCKlXhDjQsikShZ%rgdjx#+RG}28^kEvK~WL_5Cui6tWl}Hi-O0FES#A_5MLUO~W zQJ*5iwT!BvA+h6>CB-^T4*>}%xpCxDH0HuV)-@>sqx8*hKPMPqYhtB{?CBp+^aE*F z#c7gMm%Tr0o8(G8pTcsZ8mxiWsUX?gO>tJ@-lHI=79Bq3j*v!a(<~<*>vBEt<_u8Y z+0ZO$y6{_ja+f=tio4vY_jnCZIP#=CiynN`C|2H(QwwAcMXB$;dd)hpVNGwr$4U=r zN{TTH!T;3~)H{SDa#?+RKcF(1?W7Aup&2|N4G@aI#?m|$W8IM-ZZFuZ;?+SjB16(* zqHbdVS>q-Z=ah|beFPjHmI2eqib$)yRk2RAp*Pn-T@+OI{29+n=le7z7&8S|kGa(` zq;Wm;R9bTiB_k1I-j}2$4`tVWlJ{iT1Kam{ZR^(e;0s2L{d# z;W`k~Z`{f3O+mjWj=LG2%(hG`K9)aL2P(2uqtxH3IVanxgiO*APcz$A{PSGi88C2) z&aKCXO}s{vox&3GcT)&m-dd8z@^TKJ64~P5AMDNSsUM|`Br73vMtfZ$&ogI(Gt32qSc*>Z)SI3bC(co10*FI%1$VfpH84liXSQB6xbwtDj$Lp2?LjGXQ}aiuk~`k*gSK<&gx`4+CLJISMe|;&i@Ytqx401!$P?gJQwZsu7_a zJSj>MB_=!hi0g6^Vg%yS(qs9<7S1xG@Jm~V^JF{Kz^GZR3s$XLf?Jvc-9m_la7wKjjxpc zt0)-0==sk3RFj%)dqkP?Vy88*`HPA2a+87XPDv$|;nq8uGff}j-O-gFPLGjG<_B}r zcf>u~jo?}@iPW2EtIeQ9SYs7*oG$n2O`q{#$lRGwB}iB_Wu>E4lq(REF%|B*4ATNb zPl#dm2FKr;6^gi4$;IaT!u7uT*7{Wkbc%266?X9Go&PoSG3(d=xBDjV0*T6WbL>0E zBG@dMvI>h5}NcnmR${L?Xc{5OnQ#r4`@jSe^<%@J}y z+Hv=VeucY3GCSitta0FQw7)+$|KBVC)_M&7OHadx8wwqhz}7vAnBS!QkOMwst?TZ$ z{m1F=XRYh~+iiSJfW-G+fAj$a%T?f&)g_weH?>-0ya-exP-%ZVS0L<9=h^TyZi%KZ z8GGj$DhWMp(oMJaXLyx2>?7*yYHzumxr?LwQsSqGI8`%?+}Q;!fd z4w)alP#l<0_n_~?LPNvcgMq$x4+;wE9@Kqk7;;Q3Y<3P<98L^eQ3^IL5fwN}F>ySq zC)BDxmY@*s-M@Da0m?O;51O9%6SGBCQ$mJawR=s|M0!rC=M|C*#6c#@tkJ$ZPKI6LTbf$ zSz~I0?~C3I=N5-=bCh`)-_5tzdK-NW(=+^7?W%W`Ip1J{;_@Y}dq3{{%h9Lz^PPqB zPBG)gCz3y)sDJ*deAyn^Pngnv%d~|kt(G(HP5A>V(f`};=RmBZ&M!@$Zp1o2GD3bB z&?>nucBi#6OngdLwx(x{uG+tN@14&cy>}9hyR*l_LzQE`M%h>=3(Wk%3ft_JYLwSK>CNdj`k5gftC5zT34g3j2TW6d{C{k z(RzCyN7FdJKF*={Fo~s~De?Q7OV8mB!xINf@|1Th$DUEI6>`QmP%>X6pIi9RkBvAt z?lYS;WNSE%Tx*q^Wn=PXJEW!5E1AL2jj;*V>@Mkfyoy))=ztrGX;aaluSguCXjTCQ z*YC45j~D;JE=Qfo;jvWNv=;YzMcK^ToH_seualbKR70sV@POw0BWcd^jzpWx`Pc1z z{ckSzo5RW;#p6Zdr%jHW9;mhCfM4lsXY1A+k#tc=|PrpEo{)m&kY9?y~r zzIin#1|x0KkO>zxS6=y{9e>huPN04rtFN^T#AKv6#momH`N?p6y zZ!7VGX&JuZ)wn@+v?4Fs`ebumWhf6w2^hit@fEogU?CQ8Qh?eCXrCo0F)0IVq%D4N z1e6LmuK4MC!&|Q zcQ?Vzwt9R24!TM4tCELlINmBWs52!G7g$GznO9(VVwojon3w3%r^;C*X551d!jriJ zT&QT(LE=Q*e}Ft}r=j}}N>TQgV|7x0L6U@SpjQ;XjznFA305}^RGcbEJ2Y`+L8>ms zgs7W4*p^vv7$Fsti4qb+B@_`2_g-cg2|=Cdn5R1_WdCw)P9C>jeEe8S!_ zlZUy3D>vHGYwn{Tv0HdGZ#Y@3MrYnxvea+m3v_)0!+&hm@{>qIhi2LB45B&lkC`P4 zUd|e%J>_|`X}d?#lJuBAQ@-G)MLd_(!((xY91E7MF> z0y6PkW9_AGBUpC@bd&Nu?gn*Ef?XQ7i3VwDus@aSr6F0!yM$K)JsO9uE}^r4sO>p# zywb{*P{A_`H&u1b4le_VCT_MT+acN)Bdxi->d(@_1KH2r!W+aa9`hgZ_w21rW{jM9 z2HeDXs78*hg<8E1dqSACo#!W?V)aqh=`+V+)&<9Nb@6H{oyw2iE^zLAO%zz@vcdzC z(*UPR8R|JoNO3Zb2NmsfHRFfv_>??%WEnoCvdrY1hRD#ofC`dgQ(>j;(y zaE87xq1GAo@9#Ze1c9xg3+%iVAc zZso?6UaOZyNjR;49q7+#@6^GsV`wn&_yE;a9u2L5MXRZJ;GWa~hZ-rDBd{rd{jocc zmnK%MSiW@xXI-R0EwKh1Ge)XZX{K0RlFa%0q5Sn!0<52zMp%7Y@q#;E25kEw^#LkLo-_HqXg4U2{wGdz+gYD*L3iO+NhRhA;U zkQ;J5)N6*^7nQQ4+4{VF#lBvJ9DG4u-ov)G)e}WOwDjgwpmmT*WvI{_ir~z(uP|EF zXm2R@wov=JREWIPUeM$%Y5O&D_5imcG7^{-MdgOVW;05{+Dd#{$)-NNT;Djrtz2T% zZq%x^t`;xRv~H?gs+e!mthKE)n$;@H(3P8-m51qmsY-zxii&&CB)@E%);!DVP+_#J zkt?p^)U&*Y-!jAcw9M$e%8N>jopQ>g7BhX7p^Pr%x-!EMv3&-76O}A+Aqt^b({*W! zNBgCQ$-9k7fe3!DYaBUk;lntrtx^`_Y&d=uUtE#RgSIKfqoHO_b7?3)R3|50GJDAA zWS3|od3s2Ru{pzInq|piSs_E29G;XAFFHy}vXC-drb{fO25Rf9&$M2rO0M_zf=OoY zLm9x$85T(OWIEJc$)O6+tNjTItk2Orq_Kh13KfQpBx@<>6YK_qr%YocKs8&ED}!wr z_^y=whJt(AuO+4ySGmY>SBKhKwLKXQHx(z(U09X8n}HZ+tN1j5hol=U!4)p)*5 z`L0wS%PjOs>n>8`qZp+mgZ80f?-aRnhLl51hFCdpyj)4DAtCbH3XD&zPGv@GD(&6* z-XUV=NWoW&p#+}OHCOKMmD+YExF3>gT#^;c^W>j)-!t4FZqM+1#q!mhk+$zq&69O} zNcIg^$}|;}5SJ!fLBnN6L~kuJc`Y8#RDx4H)oS)MhB9QY&N*tfG*^b&GBniWsT#sh zBalghW1YLk>G+tR;k}N6)Rz4ThF06$EzDR-2Wm+6lJsx5dP>AAj&E8!6LSWodYYup z9b&F5m+iM|N^jH)X?Lc?T!Q(F61Cvn3CT!BTpygv?2pyIgI^|zi&)HaV4S$dagB6zRycM!>(pI1rLyu_ z$k=D0!?10aiqp4{Q9bv<#hA8lmMnZZ3#9zfA#7;Yu+1?lWyVje^n^I09-I{*wc(1n z0?ezN{hzWx(ys2x8_iqStIEKX`Od`kHF{hA;{eQ}t$K-+JLqWH zzvl@1o4k^MeW7!EiNJ{b^yt8RV88S6;hq^wLN0L>)t)=Q<^#Mcv0f&~WkaT!&h*9- znN2q^q(`-~41^dshUfGuB%5iC|AWMOlh`b>2D3_&*e)xaS%`T5tGP10eA|!;yfQJ- zf~ltaGsr5;B2D)ToVaQk`Eq7)av`NH_+$|{JNT!SNMoS zcMcu1y5CE&2a<0PMjD7@1jI2T{wK|Lw|?%^Cwmb#^eY5cSA1>?Rc8<2R>d#k+J8Xd zD_>Nf#Uz9WMN1LZ-}mZu!3$pP_NTHpSgPd37a!U)vQQi;gJQjEpRD^}8Pm#%E@kf+ z6p2q(hgrXTrKv19n^X0erNZhhjo=qCaOz`Eb(vigvp;h7gp#0#+I-XeI8}Hr>vY$U zKagpZV_ZpRduB{G*!gTRml4rNNUNrJ6JNOd8(28KB3EEu_O#-yP5N=QCYdK3`*U7ob#+g~>&V(NcR~J4%d!$C?>(z{ zja?$dV(*vplA{%`oB=kccuQktsC28y&E)+m`HUs=ndQKAg{G!xcZn-1e*)p&dPs_7 z9J;mBZ3&q@Zcxz^S zTyymh=;gS^(4}E;tx1}AY%|hSw#Xmz0`|S%6>QDwg1`^7)lL~4Ekr~-Xx3@7<%`XT zQ#zs-uG|fErJ_j4v}LxihBERj7-BLA2=X?bbt31qS3A-gVq0HVZ@KRtMwD%ABI;{@Qza$n_u-S`3$ahz00Pz>sF!P{8 zI}{hXi%_dJHXsst)9%^==k!ELQZ+W=@s&c9KrH6cjVd8##q0vfvtGCP7&4W)Z z@gRJ8g>xBdvn$t+{5c2mOKDB4w$jDx-F^E}i}($!=Bj6Dz56493H!-I$C8ZlE8`YM zgY604lwTze{1*t?^S#Bsh8!RmaOAtrxjWkp1u)= z_P)s@cV3R-Enr&M@hWjRCkYvQcNFBbN_v>p%RX6|SVW7e zALo&O-?X2ABXRJd)Iw{BHVFLquw+Z)_2|gLDmu<+$3}eX^6FnEqABudTvu*LOAQFiLyOXa zeR6vNrJaL)Qm4a&2P^2{))~7Tx+aj&%wnSH$P(vW-kQQH0L%;z7|8MNJ^6Xv&7H91 z1mtkYP<6LgO#k&SQnj}naS+e+6>fS}pCu!ER~105zG}5{8QUbz$!2Kb6ErFN@(a!` z$%@vSiu9(KZSWeS^dzTWZp(m)|0HJoJ+8iX(|%a-sP&+zJm%YzOHrm2Ti{r z``j1rmLP_FB0dL0>-P=12mP1_jit-TX2vjQ63PCW1{OjOXQ~y`m~Qqfe)fT1`O}RI zPTZv6PyLl)`l1h7G^V$gbVFFodtTWF$9d92VGpDGM?r2r-IXWEx$Ew$oI@Hnx9FC3 z{NB5RC9R9)7E>-m)IqITtMD|mj9K+y=43(N2PaCGH^uZV)#v7W-5Y=zJp?3GDWeOkcta0>vEkMBbk&?P_C{%Gaal!i4NlUR zp{oT!Tg4;~(2?^Q!qPTeBdDRIxrCl!8E1MWo&tf+OV><*{<16Ju5Z4Fvr3 z{06HrllJ)=J6j~qgzwT3$TUxP=XNbwV@r^9Di|MySNI#vS9BwkGmr^zcKj+0@{WV6 zc&472g{B~fT3PTJm%W_?hlWP|=W;LP4%Eq0q}sv9a3&m2m>=}p<=y^64o^wQl%K1X5 zh*qmEcw(jt%}Ayeb>>H)OU-nyiOsH+niF|s$?ZNwBxs~IFnDeHgxrm?GKAzbXI%(J zaKX>{4?X(|@N6uVp}HrmVxpm$gMa~OfmbOehM7_-Oc0t%-0($x)-thpPJo%Xk;vdY zc0^MfvCf2NoN4+4>93m^7I%s&A0ow@!6PtG9wMrTd@X}s$@pyhawG=RgHGcM8i@IQ*^C3I%)S8PN;`x(%ql7o&J92~c`KuQxc0d}O+qB1xVmfcMrx7BU0es z=auEaH4;rSfnoZLsA_81wSyUpQ=Y*yoc`tfy^tU`PaYVfdCZ^{=m1voY{}?%a~Nne zMfl%2p_F)9w&vooHF>c;FNrI76xM|{E`8?@Jl_C9>WXdGYe#Un3172xYJAPQ+zL9r3t+XcqZ^3Dy*t>cowHK4b7YDjAcK-&9XjbGE ztpHs{T$6W#%bPNfj_nxK^=b9k^;!%<(&`S~+o4(UC>< z`1>PwEEK_S{wX^U8)4{{dosnNJU{8YU&nZR(Ran)O@or9_ZwI4AalSHDL>Y=o=92m z7brz+ao|5?IM_6NUD=Z2aZ*9|av*W{$+LuBkCU2rn#)6N&$OR1V7x84Kk@tyN=@vQ z;AKDkhMoQ&Pz6)Z@4#C&8n$Hf7ks4hpsQav8wkAXv--!&j@q(a ztI{LoKCvLLz6r0cQjAH4)YKJ3D)`B~Ko$q!Z>t%Ux~=M>`8 z7BbOOw|An>i^#F(q&39lt%kJF?qnRrC;V!sYBM(37?@)#z!)t<_dDGOF?_r@H1Ip- zmSidbHD~9EB%kHVtF+4LW*B%SFx$xsE4{&sCbidr#Bxu$t3vriQ^Ne7+QoPH@zO82 z_Kl4}a+dN_r2z-EyY0)TomY;}UecIk4YCMOIKSIy$>&4SP)w~stT2?E5y{6M8<;tt zv_qX)srgkoB<0U4I!si9zA)r%P@Fwr-<`FH$LsF5Dy*~@t0;nE$*H>B* zBgSWL`0Zz7ti!Y_Rf)WAk1`%cjKu#@b6RO^F9wUPFqO_pjG&;Q!p2MW^4#5dq>zxV z77Mry9dpYxF;PhAG<&&XcE|WT^B}Lf3s?zmPsl?4qf4o` z&b0x6r+woU){dB0=mckbTyfdl$Kr~r+%jc3XR~Y?VCzQ~#JlOPZ+;bv`(QW#J=*?! z>x{UtGcUiY)I462RpD8_JxXR|Mg0i613x+ic&h~&C4WI zL%J#NT|4@UTy9?JJYN!N6}rrA3GpX(nIoi6K00L7xU$*DxSHM@iy5l?E6bPoWKpgy4-qc96GjDec%{AXv)E9 zC1=w5;OE&qP7A~GpCFa|A{e=a@h8ZFJ_NBlBS@hw$;;X>Q=4@$RWb}URCT0RX<1}g z>L%qTo3^E_)NFdvDam$me{7?l;fogtfdnwNiajl1)U^W^-h+ep=S|sl#|Ww=%bM^C zW_0HYX7r*|E$MPg=s+PkR=RWUzkYO*#$wuSLZ-901lwb=zSKNOS8M>iE(*q>~HN&~iGhv&2PwD~($rE;V2=t6;qhr+StoZkGVkrU$Frb7tA`T+~TYrTeK*yqY*AYLVqL9Zh|Fx>QH z)zlorqQ|swnz>8ovGGd6tFHXSG6DxF{bI{Rm9=yv(Tz*N*7f!nCoKL{G`ZK2$=A0DKibChAIfk7O)vIb~`>zICocu(ztnO zh>z`V{IOyCGyQAHiOctHZHBb>^Ecd;y0@NQPjnW0DHqMUMnnx%zy^hLkMiqlm^ zPRPjn%Y1*i&$+Vohe`_11BsvW0$bxL70*^GI_;i4hObiArcR!H`l-pPTv~p2R`8zs zMTVP!;;et-II!C}Rw?J^52%Xd@rYUF59(ea##(f{x%}s2r6^3*na#HI;iKpZd+txG zeM#lQ#=aFV?egKGlS!N9?nZYs382e?cSmK4N_J-(#?E`VNTr(d;s@Qi`YUkTC+ zB9{p58saEaCYH~}4eiMxUA?Us!SZWtYI2{56c{__h$Q zIPyT=*%kQsb$mdj?9LcHx6(hdyh%6Pt}d&6tX$D`Q;jNbvuz=vn3AL3g)1D9CfU2H zeaUoIb4Tx{ATpE&eBuG+Zf8#ER%2jiEq6#PluNDSnY{UlWrDGEl~MU?z&kFVPj{D zL^nM0O=I2$116aFI#ooQOHEuoZ>$YAk@MLIK9-i31Z2SbE42a3Iny;aIY@g>=eV~% z&$CaRrJA@n)2}EpCcj2MqmJ`@_B00KjJPB1FgKNR7r4QIwOcZ+OCz6|<(Tk*Vi7f~x6* zvtRXCz2Xre3l%G;tX{d+T zv|bSzOSotEt@SaHlPx^v*N8WK>v;C?%d{|_$h`B&A*~&thbQzk{nH-yq6&Q6Tb6Xxh*!(HP*`9SvD&FCI^<(x{2rKI=in$+| zlT%yp+Q-~P+I~RY1CVL;;oB?&7EgBPMbw(O4A9CUjrwuhb;zuN@Mm@h&(SISjHd2w zSA5d=rJc*Hn6Y(DVdBm~L(QHaP|*K>zReb`_jBH=>b#xCG*jgMKk~jhs;cH|7zGps z=>|bskdO`uX(Xld(p}OmNGjcV1*B6D>F)0ClrHH8zjH6(^ZefTUF-YfTkl`rox9GN zGc#v$&)$3X%--jAVpa^Ti_EJ_O|gEX-V~Usey0S?W+y(+}DqA?A6-kCFb80DUPBn z0(iZ@!@8On-uI~sPo*D%kuJ$^juZfKm1o#t4BKOm3lCA}K9d|RR_bRp7Y^O{@eZC0 zkCs>7wjkD1p@xUTbF%n#CkdVux1|X=&=z|xdF6NH&u9x1d=oj9VMQ^~LgIzFXJu-z zk{B;W!=&I{O_w%U4)Nn@R#}F|$JfBWQHn-$h!w?EI`wi3%oX}J2SrXxs?*-TLrmsQ zb`AkF#?eJ6`@x!GxS!Eg{pnvI!GE8cBwF3xwNRZ+MwITbuuLmnNIT#ZI_3CkZl9$; zNw~0}XA6Ep=il1fI)vnmd=ltU{M6T1peaS?{f}%F1w+KA8NPB&IXVhIa^?Mh{(+f1b;6Af! z3>TLzMsHq!-fKSjHuXEw?n<5`5L`NRF5Ir`H!iqp-&Z9KFAFRfI;QikbWj#Q@9>w- zQ*NAPS_4xs;~+4k^;yB^o~=Z?uTN8fB|><)@>vscGs={=ncwPvr1Bkb{-fJc z)opyAiA!PmO*=-z^T?D$@`L%mx8w(_;{RmT2g2UGHTD1Ag$)GXhL{m}{Y@wbLt%WM zEwKdkyQ}J3I3WI?8)Tt*5OQje)=3$tC;!RI5@3^CO=H}XEi&rq$xti?HdPfN{-QXep_Puf%lMRP|&A#ufN*Y;#m)Y@> z!14WlVEZ2INsiMDJG+9F?A}vLczL(nWM7mA>$3&1UDJt0f;x6YjxNMZvEI?d%*0@((lKscaqC{>A;meJJ4KC~-WHxC!O>!PZHEBk2qgZ%I?9M0t zff=@y+<%2sr!=z@K!Et|uzCi5-lCQ3DJa;ytA1esH>fC&ZDjW_Gz&55QL+pdJqsKr4Bdx zfwffbDEt*k98L>0>b2S+XW&i9r_;Ll*2SOp_>+2$0=W^!Nu5{>1b(ZVAy0W9h&?fR z_=q#^7c=42>mclshv4!o^^LxQ%#KEWEp--=yzj%u_^Bk`8B7IeX)OQLexTu6ql-UK z40EqGgrakv$Vc}z?FLBBKT=M_0+w2Ip?*7JTwm}Y3 z1j5VBwR|i^l(f8z{EtG<@jZ{MtCf$0<8;CNTH>81Dd4m&V*<&}^;fTw8jkps^83jC zz+|O2s!IKVkqk}G&&YEdnI-Z+Dta!|Ryl||;rXm#f}O47iRFVGh%`lSh9Jtq%%RMX zr^Ub{yu$BYUU0!g%8znsDUB{#$-<^2PF!iT=ldotp=qSy!OnKHt_vhl+pc)t+ra0J zEOPYb@sQ#}4LDFL+`nC>ov7l{^{C6l>AsPxzIA#5JL0#F{Mw@KX+r2FnLoVp>lrLAInDyGIZ4|H zouE)I+z@iTeMHfKY~VK^>u9^_W50f_O-mSDsO{9y)zGo0MzUk_R-4XntP>$z>~(aC zG1B>tBRcp;dAVh#Jwlt2FVaI&v7$dP*x_Fq9hxY)1Aq;Im^fWD=9fR53C#EW+;^xq)%wJZ^ zmN^Tl3-pHL603l?MBV{M{(ROJ*pqHs8LkllaNDdz3k1nXwJmnc$~Ou(X!I7u_5ebX z%x(It!>Y9_5PNWUmQyf*->PE2cp&qcf`b8pZ5AM~tptM@;sMc!)C5Ro&AuPlUM7f@ z-X{QXoIL~T4Waue=Yv3ICByt))r4Z!$4KIRqltNMlpOMR6zSmusMueqP8F9LnF66X zg_elW2uO%He|!cs6p}WJrvj5`=HZCxz4L64T(ZY4y>p3Vyn%}MEZM%?eRvo;qqhd ze}cUhC0jSpb`bFPoK&=ej%wy%`H&d+MMG1==?a~(F$@(j#Bdt;jI^$rjApCBpC4by zJI@1ym<$Y}o9IwkViBskp!Aq0L*b?Rx?#{868dMj;k5b-I6zsWAhOJUJ|98+Z-shvtW0>5( zeRMH&E}924%8LRvu8({hjjqB@CA2go`q(~|AB!Q zUHnjZsg}F)x{*8;oF%9@L{$)zJz`J1vHoR4@|$1o%UnnW2e6Ox4fFishJVP}TOhcU zKEqT{-e`#vyFad?$@sr&|b@_k|%cbr-aQ=-=}q$JN018e!FMou1=l+N!brD4jXduhhY z!y_piy*?73N*Gf-eq5W6mpDQ+-b6TG?^`RHRhM0+l3k_0lf!B+Z?{47N}YS&%|*vT z=vfk#&~Jl$#sH&{MqaS01pm-HQf8`0T~jlWvM8;jC1Pc;BV)lT>r9)kkW=(bTXo#H zE&i^aoCL0D^U90jL`lAuU$P*j~OIw=1AMaf5Z?VBp=MvV%hY^f;owfvhGGZ$1&wAc%Z}dNk}Sj`PFcFF?zP zHJ@}mBTCBY8i1w*V;BI$lFr{j6@hx^9a z*d8rW>ylwjiByY?o8a$|NoEB3P6<_u#hRe+kjjf<3c;PlksCh<+uSJVqnK=|=7=>y zXR(G6$CO3IF+kr=1w~2x^w;-RmJ-E4Dm2Sk^3oO9)&&Jg6!|+aQppP8=sevf0r|Z8 z;fs&^mb@seYg~uWdL54#o)&}o^~srXc~fm2*3Mo*6X9asw2tQV(~}A1vZmaZ3!d>k z;Vs(eMSlr`_K)Jf1OeVR4tk~wN+0-a;!_60)CLAW71ac1q%_0I4NQDO3NF;MRVOCU zBGBsk@CG}Q*|2m*`~#Y0B(ssie8f>lTp!!JA5~&;9rH1s!EO`v3hBrtRD;ll!^>{JoDOcO$VIsbhCTMGszL_b4s5mqCCq zy9#d4dY3Kp_I+`J%SGEZuC^;7CE7DA*KM%jKrtVgBNR{h@cr**Eha5+hhkr1#ND4Q z&!Zy!^40V9(2K78lg2xrLd#YuaDYcOphrpf>#wNHiWNjo)vi7zo8o9mv25o%@7Jt~ z1#BSbYyOH6jZq>MeZmqc2jsNR?NRkL**w|qdzZrz=l23SI!%9IlBa-9{Kd{5URB(N z$xUGA3X)wj=wAW^j5!_@r@#n!V8lx96Vy*lS7rH-l+V1m9;~1J_bd4RFuJZ4@*0GU zMBzKYmO(6^ztj?a##QVEZ!YDVSE<<{g5Esj6CghS?o6lP`5lW{`6krq;n$a<9#!lB z1C)JbxO%{@MFpH6@99g4|!h zdD8YQYL0YMtJ5PxnA@PhdE=@-AB-Y;?1KF+eb3jP6I}lEKWm-lg)PPXo}K%LqTu@{ z0WR4lS3i0Qd;0Mqjma)OKFW6oC%+=IbZpj=V*c5_&i6iS;9Y?6D&DVzs-(vT;h|;? zwjW>qQ_qjDzO<&uJAKY~W!MC49gbq)$a>+^=SifD#1Ai+ZRXZzP*eKF|8~hf{JFLd zNc2bQBmTg=>p^zU6@{vl|_r1hBiP4F@tbI}a>hl&TOk+n&1rz{7wW#flW?VpXRx**AsO{AWsQSZ!}+mj5-qDoFPF znv$@cXcK$H1!ZK@Ey0Od8q&a)wxF>e-wX`wgAJnYoTHA5ATEaVbuPtcIoX>anDG&_q(nhh66+XK zPc)bw>qOtDnVTk4Hx%q&%Bw6360%52(J=^ja*(x84!(K*2zs0jhI>l}`v(Sd1yk}{ zrarBwE7J45e7H|fNOOuAcQ7NrPzMHKJc*A}y&0S4UV3E28tK2`-PRHG-lFuHH993j z_NWN1&6#ooewf$T_~oQ1a^w?Xy_5aprlAM87z5>cG%k|N4IP3m%yTE5@)x5~{n?fx zb~j%ym@?NVx=vd~#ky2{B#%PgkLlCpKEp*mcFd~eGXOhVOe3$F3E`4N;CUTZGYHoAJ9!p}1E z+7q6W+m6@=g@)cKsS>3GoKoyc%*0kQ*nmcK3XuFV^1rmL+M~9ue5hiu(_F6|bWJ{e z%;2E8T??q{ddQgvcWrE{o%xun-fl2LcNq;r3!uAY8<=&!>E7vE}IGrgDd#2|$e&}Fu=3Hx@Z zef)$EMgY}^1Od1Regf`7&UN z@lib-{uQd(p84?^o68jekNdNv`bYg*-bM@<66h!xrcND`?{5AE7B&`-FHA1dJKh8* zW%kP()DI(1#c~{XYT*a z>m~0<0@la>cbjgF6+R(AI>_zLcomgtvlk$jyBkeUQ_UN# zqG+IalgFE638E?9P#l*(D=5foo-16sNIMrE9d)W!`~y=EP)#4LhQGT}wFOER|E{U1 zwOPEex-yi<8x6AO3)tMO;n}=l$mNRyfs_MFwJz54er_nH6zqZvN+*wUM_2y$^N-Q* zjHEOgh-ZS_C4pNHhaFW`5f6|5#kfm_jkvHPGO zC-;O~GjC$wrBJVRvo;j+s8N}JfC+@jx)kaNZ`S$(VUWkKSKL!onDYC)rhi~O{}q0h zEEF^IJpbqa^j^ggP-K&P-ULwilZ!$fpc+)0wf+m=KQ#VFjerMfIiaJI|EzjDDNz66 ziKL_!ASR$}@G5n4vwBDA zBbEb*ri68N*dCOqR7g{Jta>z85$DWlm)oDA43cnk{m~lMS=jdn=IS)>>{Rq1FfaFa zsp^sXBG?e+*~#6j^>DQ8lcS~wxi{kA#wSjC<)ly6I&n++-tdA4yW%~*x88K@RUW7V zK#(k6P!+R#YP;?;jU7W=0b&{FmeqL&{som7AUH`PCsV zd1d(rrr!n4^U4(p!H!e;7j*NU)MTG2ZDtLIZ5H|r5uc6uRaaSNi}H-QMqSZrkPwQ$ zqBo>1byoDq5z$>-DR=~er!l`!$YAqjr{9(XG-B3jY0Tx^iPrsW0_Y3!>PDJ5@s5PJ zX?|`xx`KI>D~m_(Z(-I_KoNyD z&y*`4g$8g6^8hgJ3Vjv>?bLH~@l&{DIyO(CV{i4AR33AjNG1?zkPFTRQ?ghDZm| zjK7o!sgWEw+oH5BJ~A>0lUeOwN_+l z(8TIScP+i7EdU?^^8Uy6pp%R)!;g=f@@TYRN&51J&T|iQn-Au9@a%H?@=|AKRULC@ z2d|Qq73WQhH$c&<$}X09eR=L1vEuogRdlnXu`b_di8~?r02p>sG1@V6vu3k=H~#9h^nv_*buk6L$UY?zP+_|D2))7-NJjREd0UHslk)KBg{BYUfUnImKT1|7 z9B(o?>V;dF;Kl*u@u>!o$7tSzvACk9zfspbU|ehfL|vsT8EY*^wJkKAMGg# zU*ya_-u0`cueM+YPUco`+3_WuJ7z34XN5D^q*_eQ zf@;lR*a02$Gv{vcYzUmZtgd_3=Zdu!NasUy%7{mr`BI^GKJPefCnwfQ@Xmm4@~EII zzg&mpT~VLwuI?GyS(HjD9~?Vz0G2m>&GZMsEFb3f_yCb1TPLx#qdksD4TH0Aq#6}6 z0}NlCrzCsT&XaaC=Sa==N!!ZkkD@rrM27XpK-~~%P_$gH-sGf4+vNORy;FsksoTL( z;0gr{%fN6nuX$Un7S9Z1x-gE#SY#WLy{0`$p!Kz*#gVH}#y@4##cP6wO}Q%uj< z?0JpL9hDS4h}LbQWOBN|bt9Ex%k%teiqQZEp&8rjTz2grq(`(0nSyPM;Z{U&E@;NW zDcC-0z%8p2JDFU~E!^Y)@Di?s6MoI`ECFA+g>M{)W_1!Jle@Tu|DspA>CH^09F~bR z$?7Z_UY_c_;T5}*%F!V+W5D=;avy4PV8L?Emf#k?$g2f;?=B0qYM()N3+oXoJeqXb@3IaFV7{Tz{+5!(kke?c|zT6#7sWkO|l7G^#i=l%c?5x9~uAklqXwYqs(IuvELPeI6IIM(!hjU_Mk@?Co9QTHY34oW~0u zNI#uaj2&PovBpYMR)SAg z&2`;zI1k(q2$B0YD#2`5Dy*K5{SPHLi*-*-2{kg-G|J9OU(d#@aH4j;0#mP%-4`+J zN;r2(H_f)zAC`r_0_Iat|9SCPl9!^Flo)AGa-w9OS1U4hf}KtY7)p{udn3hp{{7sp z*p}{|`pM^gYqpg$yL#7&P*3ya8>lDrM6VwM2zPuF=V1g%;Q0DB0ulsC0Sk@1eQDQm z78-mLb7Sxa#;8>>bU@hx0n~CJVg?B7 z2Vl--z6>a9G#UJ1`h$|9M)^*>qVBQL>h#IW7CG%Y~B+kQ8Cd zOE&SP0TjfWQa6+qseYJ0q$Li7mb`Ep>$9d<78|LWel1nFizVz+cSp2IbO05cHD#Vz zS<;tkMqg$7_W5i{XeyaoIs60Z=nlTsLgY>RO=o9KaJnXYsQ=!DQ-t$`MLkCQ-j|uz zjV$S$6`EU+iqsBYL?)2DZ!YfTOujnbe^+dTgi6sQlRVt?mGLlc?^o0EADH&u{?PDf zC5GA${f?VH?N}F4qM_+FX zAcgu$17P;sUnT+5`?{N^V6(e$X46gVU;K^bzZfBbSYjI@iG+V~MJVjO|KgA|ob3M< zhnx+92h_fy+TZtJ;*501PW*P<&h4&M-UiCN7>!lS?^aA!E2tZ(H>f;}XxP@HfAjOV z;w4j8UR|ldxa}KU5A}aiRO9Wv`Osarf)XHaNkUQLdBCQLpzCqcx>~MP476-Z3FN5J zd@*mRBEOf4tJ%d)O)G8MOlH!gw`ty}e2N1n zgKXp{Wy?(`7^o#3v8Ovx(HJ1$+lbK|?|t}t(o%&9HeojQ<3Rm>TageQKM}?n+sm3a zyeNL=O%yEulTcn0WATZ4tn~(N{MsY0s1;>@%-DsX@m1MDgpMJzl|VH+c_X~Bz4t)$D)hjr zN$$ew>IprA>{+UpO`phoGkz1^Q(12Jc&P)9cYxFyS(;bhon`!S;V^JCgLTtf-Qrh= zcPkNbhy@t;t2Hkpua*pK2zEs`AvqQ7l+>%7@K$^vdkp`zqlseuVHq4xl)x*SgewAe zIBMHPUi1qmLaWj?Eq}yDOxy53Fho=(H(FGiT9R30I+zJj_3463drMykQ8Su6{5{Ul zK8hcVt*oTnP9FiBx!6Mn;Bdb$FvCtEZJ&VrPTBtXv4_sZ=5ZCCkX^*^W!9b616~<& zi%jq}6NywQFLzX?FgyFv{uzf0G> zkx5o4>0@FP6qGi~E{fL58eV_-V(VsVmZGA^pIg0*uGnuxn_Fr5Dv_-z*0fx(w@-D) zQ96J4v+S>s>D(CW$hlFDM2Jm#1i*Jc*^7a{NqCGOi&<2J0=%nFXTH$nc9V?NO2Gcq z8@o41wo>m2B;x1wI3H8%W?$ZQX=wPww!@p}LDKDdMNnS|#M1x?3u)sUyp7~5Ec#w=KMT{LCIcg*X^ns2k5s`UU!4Q(M09Tf&WVjrQ zM+(i4lGT$T%O4{T}g-Olm2&Q5rv_KNYg__kT-z(?vU?+`6vN6{Up_}L!^ z4FG3gJ_ml;gDgqI(I7u6Qfg9i2vaHSk4`Ei=8u+2UHId}sz!NyEI3(q*j|1aQnyPp z`nrGj_txs#SYB@a|1FB=mhm>cMAs0zE2xh4OE^arC_3<`yh9I|@3rG_@ zOpAfq#ctm;)~)hUuGI8Y?dnT(^?z4ZRE?cQKPHuNx7RAZd~{hkik?``GfuZ^Fy5h; zKq_%?dRwR6HD;W8oNKOAPSsKi~aDe zZk6k`;TZA0&{{h>dFtm!$7H_E7+=*3cs13THhIayHbR?Ayq|<;r!`8zen9b}{uLl; zYtdWN-qN4FQI*s0O&E3knMFvLwVwHrMamx-Buh&tunf|BTetnMz4;7{UOqO03CZW1US&cbph5K3DZBg|x=%o81J6;!$>B%4fpoiPeORjRqXMb_S{Ne`* zYh$8&+}IP~F?>%b7!QRY!~{x5MnWtyf9T8E#^2dwg3yw}e~1WCP=KZF<)a&e)_{ZI z*ESfyMIHkxnuEwEf$y2^W3)jZlkp(JJ4Ti@1_+R8#Kr_w1%}2{#2;hkRBg${KBCUK zeEM}mULYd5euDjwUcINg5Dd`ks!>C&DPU?FojkkhhKv~m{t8M}V^08#GI{Oo!32Sm z^UCAgt?x<(*MQ97#6x?&r}6{yE!=KF!7BGUdE!;vEnXq9#ks{a2my>Ahb7#~Z^(k3e08`aXQ=Ip&spH3Ti_fg#_>#6VAA!YLjH03T(h>#I^4EH%7gDA4-0GcA#o1ZP z4aR%>3%0Q5?Q~NtTu`&{oet%~FMfTcuE7M$8xFT3RkS)K%tDc=(XxB812ttu#@P!$ zc9fStVi!CGsl1vuirxiS%Mj^n?eIQ5upnUl8Ou_1;Q;Kwu{Ts>&bbx6mi+*5`qnmr zVe36MtBLVt)~(1GB9l4V8Jq-0C5h(<-PtuIMk9+20fFDAol+)+Ru{t`S~aC-Mv_Np zWi@3~+8A8YKzbz{$M2<;ad11qYHyxrYv_~YnBALX^O0x0O>8wB%%;Eg7BU#JrZ_&( z8;(|3keM#*>t@T7j6ek|_qzsic&O5<|N8tP@Ige#)1%Y|@6hGHiT-Z9aNG!UhM7XY zn+Qz?+3JAoc%UcgGs`EDqiaR{Yc~Uls{h~n8>Z{J%(w}${Twg@mWj*N@JANAs_93hrwAzNNB zz}L86)OZ~{i7s&Nn#i)9Q`ucy4x2l7+^UCcnb}pB&KB%AniN;g#Zwx_(kbf%?BZBe z`_7Q?QkZ^M%q>;Q?Xzmt18k^4)#yB1pvq7Ay!Z_wHY8-<4CwfQ#D_}cCRP<|Djm>^{I|E^|SjXu*nFx_02idh@O=_o%rPu&XYSE!X zFXb6dI6BxLS+QQat&-u$1en`6*)NWN)_hJB{)vJ)-O$)(ue+H6sfKAg)s}Eo zeway#^LJ?aPo-5OUrj70#YOoGUjBeRv&ORA96V>|KDnb5LF~ReqvSw=kNne&W)xc0I zUE2mKjNq(&b9vkmD%=^RobH&hON8mKi(Y?VQ2k5mb{iQhGS(d|4g7Jae& ze&}YhoXB{0Yu;)U)IsmiHp}0kFcVU7L+|ZB&ORaX8#v`ZyKhkmZXI=f_&EslXbmKP zN?QjusfzraXnTT%Ltv4+;Ia6g39Z(GK;i7b%0Y~Q|b zM<`uc*TnAerLyjKpheFdecl8nx9&PS*Jg61!O^tTNV3~j@{jC_X$xi8aiOWptSR~} zjR$u1WftuTZ>mmbSd|R=7~5Eb%y7pT^Yx@@J8*Uir?txJE!?M4y0fjKiel0=u}Dt{lfqHAI=Dn>)sg^L%g~(cGf}}4l#=aZ2!`bTB$_)L;g`i z+s!C5S{3!y?n~FLvS5ZW+Q~=6caN6=Nau;4cME5=s_O0CH^IEsjMpncd3Z`p`3725 z-^RMEu8%LbPRD;2&MP=yw(DGWpT_=XnA56B{vMTGYByEy?0#^)a@m!qEPQt4cCHnB z7hUfx^*u`O?oF+_s=CZi{loUIhAV%w6V*}-d@_cS4?PucpMTxZuXQ_MUH=*|(b@S0 zdZX%J$F}T&xn+R*`N(CWz33e{)!u1HB0?73gGrbn)v*=2w9b*Z*S2c)R89m} zdPY68`J>OrD>mG?hI0zfqMC;ty0NIEHxE}`>-e72oR^cXh7lH*rcHz9WmrBEQ&fIsVT%d;ZXvp5uu=Y8$NH zcWW!;0&Na%D)SHRQc+AFMhQXR!gn`*RCJLm{-}(IO+c2rpFC$-L^Fw;aG%=!369BN zy*~U1tq-c&XJN~FLT(0U;fgPm1@!F$Tb_C1LcgF^+_tC8XJ>}{d%q>7I4E4}Khbp*H zKsaWjLRGSyn|_}w=^64iECNVR?Bd^jX;ZiqZFMCS)MP$orL(0P(r+pA!Vx9j{4dk? zb=`aX)tIl(^~%`BiuyU&{Cy+B)>=+Gw#$-R0b8t_$W*i;%>?S(WV_j|+9{yO zxlDB_1`n1?GAM+-a=&AeAU;N>M4db+xH5S(L%*cg_NA$=U!s?U42wgrJQA*{{cy0L zSQNK7QKwFlvLVl{^;Y^J{Y{g=x1g`x#qkcd^nC_!7%kgPlr=q?t6L&#+9rlnSW}YK z?_wbqPeq>mV>m;IA_z52&ztc1Wjxb&oLiTIxG%!4TQYa+>NPd6AHNhuzqQLj{mt8{ znI68t7?*bE_KdQ5FfzO&5~vx+s&$6(9qk9pCw;&X%XfO8S`kre(z6WtmrazzGc-k_o$(bJ-e=IkICBXvZ{S3xYf6VJD$0(uhm9mS z$RnhhnHU5o1Z^sq8yg=)1$N0XP>a=Mkn+eyD*8H`vaV#0$E0bh1uMT3R9g5olHn&> zETYWah1;2xL}kFU++7pz7)gJiznSCB|JMSl0o-BNb}ALOq@F8ly6q2E+lKMSFVH*_Q}SU%+A41+Xj}Vka$=we_xY~3NjY#EWtig>MY7h5Xy2isXPc%) zYen)+q+xqUFm?9J+@hCxcvxtetur!KgP)fR)A}isIVWyuOE7$r%2Ty>+ zxwp*@S3U(tLs%TEDuBhgtk}k3p5346UD`~{rSsbS*jMFIVXz0`Z~(M51mSR?Xloa2 zT%ErMoN2q|aO$l_|NqHN?TQ=`qGFp}ijjW(OwH32J{$X+n&*2yyr&706jo5(TE*PJ zYg(wX?&f1X#DH7P-Go0d*~0URw^lhYE*{<*<%s*8d&?zJ*A-~Dc8h%{EeRNUC6*KK ztzb^^?e6J2X^J7aZ@N;rny?90x_)!UH&U^nW zZ(bNYYr$8MeveW7ENf#$t2Tpk={%*yj0S^U+eq=AgQ?FT9MOG=zx^}f`cx);rP$y{ ze#Yyd*!+)xnwKYb!XHST`aMU-mCM3>stJ!51>lV49CB}D0d9IXu}k5qK*6n7QO3W* zG;Au)D#!~Np18dQO_&h>K#89~Wm50caL+a4D_C+{A#UW#YA@d2_8_-sIRziS?R%aX z{{Esuw2fd!gn;&gcw}P&f8n9Q;BHl#X`r?u0j5P*`B9tQtS3!QWAf*ev+92)+UPRag$#dVn~NJGMr*bGF@lHSpUo9 z%gev6O7SZY^Y;po?nYBO5?3<{Z zbB!Dgf>-athI{=ioJ=O8eIl%1-tdyR*$l5&6{vLMA>xt-a2JUUjE9jd*!Rw$omg_~ zEuh81F~OZ0@*&OcBQ_KJ!v`Ug{QNYN=~y}e6MqK-mNW=v4OMej^{B1Uw^IrS@qLlnMI-Pk+%FvG!B8Hf}z+R+&e}y$?sNR z<{Uj+4UJqUqAL3FS++q()dj@NkHyN0{s;WgQGmXkjvmRk>};@-hj%Y=yX;7mNonIK zW}m4oRRs2MCi^QvWzd>wu1`0IaRM2}PcX2IZF~}cE=((e*DYhZk#vCYBFvmGQ0G(E zllMp@PHK-SUDYedwx`o=RzvB=2E)^@Lh(RSM^$caQ;RyTh}-Y ztTElsYb(SLm1%*w^TrFP*dpZ2jK@}&I@tzto9+j;G2qtdLJhS}(swe&Z%-3);;NNA zkEMZFVpW_#94R+CrVS+v1n~XR>4owfKj;}o0I#8rV0ZsP7K1a2LF>p^;d5-b)0=yabgi` z{qt>AW#l^IR(W}IJEo(P44k7^mE&bL&;1a43)yO>4o#&CI@QCEv4SGd5Xtg=M8?Fp zb{{y8t|=#K+B?^ejWh7pTpVuo=Un77FXn-iXp)Gs^t9f?uIb?(-~r6=lp*8q2YZa+ zBVipW0iqrHRxn|-e1RsZ!3%3X4-gxMLnTES%-M{aoL>n^ZruyMN`eBjGPcWnkBY-iNJUqq2gbT_<2tY4$vi)#dB*{xKfFYmDQIv&EAlZU% z6Tz`Z{)>AGA%X$Vads=w$;uhsRo4p2S)#41JM5aZ1|h<-*T4QD!USeeB5d`S2-oa@ z1fw)|f~&J^qWNC!@&$goo4}5^l}Wo#%0;U-PO{4ob56N7&Y`&4!|5MfEiwhg)r9}T z)ynTzaNg)yZp>uJbX`K0gU@Xp;XZ;B+ki!|mj!^^KHhN%?haayhV6;xxYnSohY&Tb$HguI+q{)A!GEWLAyuMSvE zJ4*G{e$uI8=Lcrh_Jw(K7ou}KgFGn-=aG(v;}IXJh2A@bKFY@!f4|g_aB7H9I}!c& z`Kxps!~iYSFl}cD$Gs8`+%Go|xmzW3K=EAR+oMbtY~MQ-bXEMEi6Kx382ip)_5!>h zpd%h^B2tp3Ci|W8rJkW2)OV{sE;g1>4x5I5uRoq8OeB{Uz#RhrNP1tg(qP1(>y#DnpU8fN`{jMy42}ATd(0=4< ziz&YbLK|#WnmHt!O!x{ zgJ{{V%fz#)SfG;uqn6w9C7Bno2x&SGAbt9F!CKl*j;EMZmAj9;wy0&bmJcw_HrqVA zZ+V8e=S5?Y%agkK!LrKT;YtP}I$oZ2qwhgTlCszDFKo%5h}vQ(y*fr{Ul`i_w@w#i z<&Iz6-O^+VP~r(IEtIz)#wgm36Q45V?%Dv6tNJ5(@o+RNs{m}ob+IM);Ow~bRFYHt@6`v65=*DWoW(2=VFmlceyv81OwJ$}l^)ZNoP>XITwU833ItQplY`Cy2 zZTjblztn;zJ~QO)#k+LB$5mX^TC^nnKP^(HCe!`U*EciWMl9^Semao^7yXDBkOD;9 zE*bhqyT*a}992p<3J#Ngg-3`NSl3T)d_4q04WbyxVa<@(z=IL5WFv$(JrS zbM@tgJ6o47l;L_JMQJL7`Nm@`rgVg#DOvI^6t8J+qCLbvTZ9h8VZWx;R%s~m)O*Ou zgk(F~1vnWclZjSe zrK1Q44eIsp3>I(8(3305Q5nhFG{P&&R~e%8FWcL{)AoC`W^2TwVkqwGptM%*mdS2FCryAebg zy&m+i)0)LO6~69Oa*Tge%N{m_&o`@ls(3x>VXp;vBzLsta87-%7d@P`=CB;Cv){xo zo7EVl5b)EqW zwghMU!|VsF3=ad6RQwr=rXj?O1APciVW_1OaYM)L2Ty*Q|Gp6ZC9pF8?)NT*E+iAD zS!Aq(nkor*mf*oNn8}+}_0gAW*|w4(ZS1awT>ue!5Lj+}&5-H2Og^iM+?ls!2k38d z({$Rb?MENWK>MK`$tilEDw%5*&?WkGfdh1jPACH+{>vS>PnLUB#sY+>;Qzl2b5A&j z9zML=VHIcjvTvlqp!rWtkf*;omB9o7p()u zoq63DQ7Z4ufuDb^?c(#RVBOl(m3oY68M>}?`&k#Gr=kxrG-2*P*7B0v>|Hx3@6P2a zd%qGJ4>oHn{dH^Qz!Wd-8OPQ?kI_3=6zvuMj%f5t6f;8AUm|l!yA*6&aA)lAUvN%i z!=4&E8^$Kv}5G5e1Q&(8e8+XSNYhZT^1Sx$Rr$lsb%{7WL+ErXKlWv5vs zGJemk#C=V<(-KxzhT`daqxrh_X}9?RD_D?3oW>2?>@a5lz!&sifkaRGE5Tll#dcrm zs(1jdS^CsYy}%=Vwj4sPZOhIaz&X4-<8SWiUU=!LR0(MG?BPbe`E>p4^I6Blyc}D^ zybD_1I%}<8;4s6 zF1$j4l`%%`EcRCN2D@qGkQpQ5=8$Lm5_q}*oLJsmGW+h-@+`_In@%Prf+RjlQAB3o zXa}-ZfN%;T&~@!?LzPu7?S+S{v%3dBipqqsw36v-yDDEo8C-jM9ow^MM@ir_649HV z6Y~JYEU%4Eo9I>HZ67}0@Nxl`bw7+XO?50kN+=YrR3DjWTcHWvNd{2FZ^I1^^*xUF zxH)GxWU~iL6G;;-KiqdkPF`Cq$u|&f3Q1sZS`XJpwP9v!pu(H zb6{t*+OhF69k94C;AI3yWdR5muUNR0DwjJ>`M;ReeeaZQIv!$!viJ2cAoiVvs`lxE zzy@V?TWdXKX%h?P2U@$KOV-pzJ4fZ5gXjy-72iy`*!8<2Y5|nDD)x2dl|^-?EtYK8 zu>_gwD~%1)?0G*&yUKj5g zE+lTxQX#q%ykdG3PA+xfPtL%HwtGJ2y8?VzjI(=cs(!%wUoDhxBzaWxqW^MVYG)o! z_Y?V&)r-s^%)hN{DT$1inA&uybPvtUG>2V;VC?#b)GCPh>&Ri+b~a)ntb`08GAJU= z17w}$-V~qG~`a#snI#2E4W%M%51UW>Mp` zfV6Rn+%N_(r}~R|Vu5LrXiESvQ4G67pgU{kNw&`a>|#L-S6ymnte;>;M0ttAkV|gM zue*2$nr}LP9vnzOYF^=ugNy5`g|t z>!z9MV~%17;`x8_`(*wH#?IkbT+M%G7)1pc{WaISnRY+MYyQD%>Vv$lLYkUf9+^j8 zH9rr!yBTQHHtEAB)v*NU4hb%EG}c&?K+g6n6>-9U<4I)!mIk%;f@P>`dvgJ87s|HQ&4x8E%LJwZhFFGR1 zHhhx!!9lwPm9@rgcSB8G(vWE1TD^R^)E}@DYhGag7#efvS3Sp9gx zINchT{jK&wqG~0hjx=xXOy=hV$v{f_#R#fUQ+hgq;QJ5iH(Asv;YoVP#u+t%>19;-U2GhF8UY7z@U*91VN=k8l?n8 zL_nk)BnFU@0USU|MRI@v6^TKRltwzGOIkvtJEfHl?|FuJec$g}|Mh)$-F447Yvzgb z)QP>%-e;fvYmW{Isl8#$!oY4hxf1iMn(IOt!wRzcpdjA~dz9jZ{wc-ISWo;1{rWv) zshRfpcWfbSTr^$%z>}Gq6+-b*t&jAjbyB86V%s>cqv;lO6x3%=oer(*?iAXz>FUZy zzd}ryry!#5{(8;Msul3+N1--(it{O<(aDF5KivB7F$bJJL+n6@Ht`h|F=Ufm97u_t zMW9{O=jT}wKB`5RpQ~5Ol4Iyy1Jo&01jVNNW5tOSO zh#La5@eLEzb4~sElr*7LX4j-~g^x~GXAb|ui zbTF;gd#k@Bh8ZFuiNDB{z_4gRPkJK@@BzexLm7SvM=!6f;--VxlHJp^k9&g$>LUbQ z&zOd8&p@4F03uw7@;L;u1Vgw69Csreb%9jo8V9kvutRp2{fwf&eA?#XKw#&J0HBnu zRpLV(yTxD2Gd+LVZ|k@U1Buh&=Ld|68%Uh8{lGym=ltF&hmk_-ktNuW{fD$wTe|Jn@)*8Y z;g0p&B<{xB7lhK*4~XOTJ^)4in{$5fq^KR2B(oNdzk4X@|Cw3tLf{4mKTbrM*Kxl|%LrcgSTZlkNX81F4EeLfd1 z)$`5W=54U+!?k*&%iDw8MDx@89z>vl?|%dB5oK&0pwOb7K|Mo64 zUp}{co&JNhDjrxt+8ts}HIo@1#8{x|bylx*YHLonPW6}AoFi_sd#7U1ho(=Y+ZBl9 zJ6PY8mM`x5zq-a-gB73T^5M)?ldGlr&&^*mn0F82m0anSDPt*g6*TMJIC#9_ez|_C zWsVZ-Eai`G+lJXk>kbC!gh@IwVN&fPci(63#Rv5LNr~ap|#H9x!zG>?d7bs1d9!_o+z=T_cyd^3WVPG}vh+4$Sdcb30@cr`k<_Qzh9=_B%pr0!6DNq)j7tY z)T>+k2~&gOv`S}=ad>xU4!J3g=$JTMRrx}aWmKcXCpW2&-m=j!|H2m+-~IJ%0Dr^x z?ql06&%Q15c3LIQQ?V(rqvQG#@XFZv*$P3~Mln_dbCQGcDbl*7$~~#PPXXGJOyu65 zXfot&lvd?7?|~jJ8d%^?kr|r*2a8y=qdCg?mOJdX*qPsq@Mr8EdEX9txEoS#l z>3Bx@jfOI&C4o6{&S!#WNaF1UwFrJjHtd$PmOD^m`7c<0B~dQdb06vn7n`Y76^#88TYS76v)kLZx#qlJF0}dQOR$KOZI0) zoF4!}Aiw;hv6S!I{dZeKbN#MGM<*g zg%eH1XF|vjFC%*dp|j!_l)(1B5V`l6Qr^bo-8x?f@73kGFc10C9oR@0Q*YPkO^nF}*ERXFK%SJ;M8N$V z@37P=y;NYgZX1q&@ZB55^q}km>zYH_*Ul^^x#a4ye1^4dWV13C$NjX#xHMb%JWt)V zfkgQxL}x@&e0*FA@{v=j^mqz|e}X=1h*OiE2ES92+60S6K%zcRsG4%8N<&mP&v})L zMjn>9R1tZkZCI!)@@3C>R=b-Ul#^#!8ih7Un?EP%dGRc;gsQMSX~c1gBdMpv769fA zkM#m@vtJ@^GAZ4;{J{D;_Q%XN?7qrUAqun8qUD*9CRRO84*0K>B^M3#4hoa(Rqz!oq~K3S5WUcPiFN8lj)Bq4^E*bIj(@N$W8F$K!QT>?`&F^!nf*eY zCblFTll`?+y2Gtz32ohJ#fo1jx1hUx|9SDqex}#PQt5scWKf_`*&O%VCVjVP$-RMb zkwRjVu*AAh_H(kp5LdSS7y9`bkBt(Ffs*Bo@}=BLGQl&P$ZsXNOB2PrH0V6N^Mhh@ z1w)M09|s)D>)L+%qycxL{j&x(KP&ooOWLti0m0$AH{V_wG_zo5^UZud`bN9?44(PIlkKG z(wo&`X*@mPVEp?#INw7pALJ)$cV;|MQyH|-e3+M}t(L(G9gX1wzz&@-++#51{7I!R z)n>$*>&NkWJBRl2aeNq2m%w;m^XBCx?W3st(6)B-XnGFKY5&T~<{DYT)KQ$ArcsaV z(zaB15;uqg5JZlg#Y0&U!;%}rl%?;;I1asKE*md}!)Fep)#_!VjtTjsng zy+mz~M)j2Z*AGFVeD?WUUsu|t0Sb<(vklD8{vK9wR+F0IpumhZ^K%N`&=G=>-(R_! zH}+WIn77p2qC%6=W6k*y|QR7yYtjlNh6!O$P9&_HujyA46C zv5=21XtChHaTBwm@fmL7n?yiJNsMS{kQo)E)zItSlJxXTO-bx1B8Z{xLkl*GlHR+9 z?dfU9l}7c#^y>KEuR?VtB@i{Ra)F~t2=x))=?00POwZav$(1J2m+}{%c4A4nS8xrg zJUmafBhdn|`OXGY`_vSAOc@fOSU7D?rQK{GrpK;D?Zpxe7OOs_B!8p*9Y#m0b~-w| ztgEeWqK#c7v%vW^dz-}!N!NLT#n1JX5@(Ir&El=r87`fQn*Q(_=l#4%K2+)@6JmF} zUI($fIp4ouXj)4>)n};_J{mrlS#|Nu<Z>`cO{wf4O>8t2lzVHhnLVSjl-hfO3Wk6r>+w&BRP1Ha2EHj>E2K)#(bT)6nUcFg( z^9c3S7o}i$o+Nn?>-Ntgy=q`o9}pg;j`2xPaoVr;5bowZ?9fcu9S$jb=nn8jP(k70 zb0#6Hr*{djWu&%&I&> z6t4F#YZRScPJ3~F&IFC0&7X-N2+Xib;9nEXFgItyH7FanHYh4v#NO8GvpNnpc=J+x z&jwvQAj6EsDVE{^SoTqQTFqYLK&DbjVT*qT-}QwSwrpX3HlB? zUwP(11g2A_hs1ep#QbS9saxCDU88}kz7n*vk`;xxbBV&lwoJto-A>6<;-a|X`osLT=n!MgGl4Z=JLCS#0o|~M@@NuYcNo3~pM2J{CjnGNXotPl zq8;YvMNb8Vd)8b^6aXOCl=Nra{pmSD)D%#fzw?-%E4w$*_%m(m6*90;U~U@?2Ss5! zY~z?Vx#@t$?;dDEJ}%yE?R-ktBntWgSYRR*6j8Iz)m@>yFSmXTeaj%lJ#i;&-XKNY zIEfoc^YUoVJ>jBevC$OrJ*Q@|T+qj3({0(RV+Ve@q_Wt@+sL3}M}F^U;VL3N9FqAs z-E5CCBxC3sAAD!i%rMIL|IQk^9WCikACh*LgD6k(8}41E`ZJA39`v;h9}HeUg>kVD z`PRpVkpF;peku6jq;B;pz-sXf7<5FZ2~{H>7wK_bJy3zK+q-#*{fw!%iJJtX-4GLf ziH(HEevyqhD8)zc7=Qn;u*m@B2kuE|O`(Fje_gv}pYxhm6z1YaI4Ve|A^1aOX)EaJ zIWvoF+29x2|GwYKCXDhU?QhME{X1KKunO@Twq@Ebcuaqs5GO6Gp_+~ey7k0@j)lH) zS^VrMIcE*2Yzs7-w`*>|k1r@ptA+uxxbTc-IzNts&av9Ny%1L%I=|y_7tZav1a+TWq;0 zB}`1{Tt{d?w>e6r^9oM+IQvw^8Tf(Hs!wGO)49@1xLU|}cV8qrT|Kqe(KtJLBX8M$ zqVD^Z>AvVgclTJjbF}q&e9!ZA*akuA_jb?eqt8J&-dIIp zbc$K3*8KbDiP0$(sz$l0>^->rlC6(&RaxZgDl#(Br@WthKZ($q1I?!FP-9j&LMW_%y^m)sA#Lq()mH?18&SUAwH?pZkOHU;KQRKTqD=v%o_@JzV{U|cs#`{FmjpS!& zy`QVh-tB+pl;h%OH5ip`x~4ii8#x&gr*=#weBvLE`%Hekct4K)_%~mCe~JCN=Kaqh zh78qrx$7RUdwwU>6ovi>j{2|WEj*1Lw|}MH?Q!Fni}+qa#y6k&zzfvF1hF>W*Pddx ze^Kc{(y#bw7xxTmO}G=lOIGDEY!n4tchO?z3~jZ~CKnGFc({=xuZI`-#!iR*(*5t` zd_-6R;=u#JoQcZx?EQ|rPrF+kCkI0MR;8TfpU~BxqKus*+~udbkXbs= z*ePAHF&#}U96aECcAn+YYQfAJb?Pl0kI6y7m`Sm>9>IUG_?f7RmIPH7; zGWVz>idZu?#RH(l5hB*bd*1Uw>Lc5N_%qvt2_uu@t+Mqt!ht=)Bm$^O_vI;%w2E`n zHaKZ+?Gu8J>_Q2tZ9M2p;zJ7$6l$Cgq~-*-34bZW(Ln%yse|?4!cG@ARPtNN*El2Yr zIFEUfP;nS5E8^nb@#Fc7`}ZH8SZg%%er7v%WvcWQg|JN>o-R9;x_%UY&lw~==nb`O z%Ae9+%WzgZi*iV~O?i!*FXPanBKA^;pBUrC3)=F>Q4RmJ>B|!vj~sp&o!UG0!=b;bHbFM z>M!5`X4l>kUH7Tex_)A>djP);sBJ8sChVt*H-+#%t-@ToBGe^_EtrMcb#ST9n4|zz z+2xGmCVH zVx3a{b|a~f$KEc@NJxR-=^wmYq22S;X==#SE~EjM?L zKP%=Hw;lImQvCy&s;UI+cjx0o$K4nQZN$%Qz#7)&vFvv4RjRgPjgtw~*cVbK^`qy6 zj7Tm^y2vU}5_+yu=Y%3J6KFP{@2+6k@$q0UJzpPh_c+p){{G6?Z-dRr_OppOB0Z&# zz656O=LY<%VEl8KnrYRQB)8%@+^qR>8fhA1uj9f1l++^7!sS-?JfoHMs-`Jroj6)_(qKMf}5r0h!9N8E|wfj>y8QN~C`hm<$V8qA!fi+p$K)lrmOK8d|oU(7MFJ3V$oNO_haH9{=bIP|4c zhGb(Co162U5m}r)Q~mMt&x4v8Q;}2LLf`T;o_P4%tt#rX{Hzmo`t-?R^E`QGRr6=N z1)9?HOr5@)vIH{T8K=;!3r)vyvFSK=QbOR27Q~)E4-xf_1g!K}EYCw&AZg49XdpQ6-W{2t=_kdogJzUercJ|fR?VIbLVG*O52*5$ zdwPX|zLpC0wYT=S@W;DmlXPta(PIPlg?da0oMKCR)UN~;YB|B!M!FF)&+CNWUV7W2 zO2c0AQ01A1@Y6kXMB`KP7-^mfc-aWSz=x7KQF`d(28l~-=mC}Byq6i<#QD~**CE>I z;~1>d4xL&&0BB>IlvCagOBi+F^cG@pu~&8u7gbtuZZxn9j$5(#&5j=!Jvu}^EKT@D zb{?1W?Ykkhwt+ubjMH}1b)UY;7Y#lYtiEG$49I({hV00SPx0z&oFfCfYfH;jhoi$& zL>Sb0fITmNF@PO{rU#hxkDlzL2Win9&drn1(308H%t`zM%d6-&&4eAx+7W90An%6u zrOXJDJ{C@I#D#G3GCBPlS(keKENf%f@DehPR8}17tYK$_?{`B`2ZsO%THZGfG2O@? zM>ML_8y#Bv5z#}79hJl-Bj81`d)Yh1!%KptLp6p)E;2(sBKSqm1tmZ+Z%ZB;-=2|e zJ_5ThQRG&wo#j3rP$w{HHACo}*=jMi%}}|lq3be#t2)wY-2Met26E#PuPr-L4=s&- z-osK%589^v2GAGJ*w6-cfRYt6Tpef%P`+uj>{ zrA&KSns=<`bmYyWr(Ty|Ut2cEdvM*y+tX8Dbp++<@?}tfE&}0-GT&A8h!ZTkKf%H2 z*d@O?`c0$Boz;oj_P32^NLtD-!l2Lti^2~hOnyz|W+CZ{A3Az+&Pjg)>kk5zqVMZ( z^~z4OqCxv_`I#m*R>4;m*Zx&zGhao_UB|0o-PrblS7`8%=NSn0IRJ}O~FaCZu`J$JAPa^!D`W{wRI>$~;Aa>~Rd^ z?^4;sJV_gV!TkKhnswWtUDDb+8p1T`gjYC*m98c%A5m{!R*vTu(x}{Tpt!Xaa9S)& zFyLn8s}6F2*8wepqd1%vG7__eI?h>b_#x0IX~*d2TMzb~?J}4SV=T$HSMutI%H56- z_i1^*1$ba=u7K7Z-26~PPUd&D{w6QJ3bnqL(pzDWrdT09ibiTZ!M5JCsjoI5Iy%YB zRMbmyftAAWQ7MkskM^X|vS$yD)zK+)7LVQGxTd@jChxlB0aBwI@0~M^U56^#HNLOw z9aOkJE~l3t>n-4Zd8sNEZG;njza^c$9$)@vsh=a9bLqxVZE4Z_G7q(^%ZG+D2Kg~O z1U7bZxRm*t$I%mfFb0Dp8HN?>O-ZDEx{Vw%$6**fT%+uVO>=XrmK~ZXYuJJc^ofYk)dFaezrq|qSw?6pAkxMdhGG2W}!;xWW zQg0Ah8ggFl?QPc};=BT_8@{JY`zTx(mQC~){POK+G~9TWlk|Q!mq!Q`7}*lIW)~Ng z%i>iFC~Am2Y;+B1Zfq%oeoD7v9!6>3yFCe*nACmdKC_|sjf^KpKzGO^buj~$44&9c zP3#v?(&v0ubxA@{d?>c|`BRejvA`F=5-^yX7v@w`sjS_m+5gV_%fYUAjqi5;l1Ww7 zvVEJGrqc&Y+G(2Bxiw%@Fe9Ug(Jhy?72MZi+-Wa1>NK_LB{UrjY>nWJ_?m&gZodA+ z9BZ`77f7MQS%Sz9kOhuMX+-r;$Pjo)rLs9~Jkh$|$U5Z|M9r4dp2cTEHcX{S!xFoxg6+ zj}*fu`ii6KSLb`JS%pz)ALeia$7ghb*;CBYG*}u3K2cD0g=g9wLx1d$D3VMtou|CQ zGw1x_+0((C)rG+}W2be?g3v1sJ#D|e>R0VEFAlf1Z`up#Va(=Jov7@^F&v;+x&g4v zOrsPhDu=SRlENh|rwto3?y~1V$rm&+Kl5C2!u=}ykzG1T6#0pi`Q+>;V!tEn^L%kU zjc;xgP~r`2J{CZ}2S~D0h8`mpMj4-xJO(Dbm`%_g!aW(H%CvMVB0p%3k{d4S?d0uEj_}YimsfcFY_h#&ET2W~im$ZU_V(3UebQ*pH zvU4e-6pMNSTZw2k+)wzvs=PRfmu2>A9QZ8y%pkCoJ#QVXG2`? zQuQw^g?;GN*dO4cZEcx;hN(gQz=oc7P>`FtyKx&aMIX7fx$Y9#iW6=+=yGBfU-i)7 zZ&{0=DDzj!xsl~mf47OXP`Rqh@6{!%H@7YB_JVcTu5xWZ(+(EHP^Q!N!lO;~QMe|S zUtCe@rB9Te1C@+;Cmkgcf2nv=dQlvHXEe?kA=F&9SJ7`N7j$an3pxUKnA{rrzR#&T5a`)ixo5ZnOvPpg-GGtSeOFR3#j3L| zc<}YLpD_;lU7K|N@I~1o@Md$PMi0D}-cU@xXd0)wV)e2ei@E5ip$Sp1V%6VE{o*+l z6M6xA@tl@^fBom%;R@Gy;<3nY?pV!UE_eif^_ z!Hn;p3Kimi*&fnH+D>Tp?YL4LYkkgi)4(@7mvjjG&ORhaI&M_jW*xUBV$E#;Dy`(zcSudrQFqrz}@*YnE@$;wDuR3n= z_CqTPzdf`wQ^7NQtms>*y*`4?*Yg!e^ z^Jo&>H8def#-|r8d#{4dU`rS}D#6&5Zhn8{7-ZGr8OI&e%G6S}q+ESnsVbxonX(GE zHww5n7?+ftH+e==D%IGQ%(|zm$VFfizq>KgHlj{If|)}vTdAH>EaqBg-RbPDZi2In z!dQ`T@)-5Eub7CktBC!Z4drd0C_33)+P}#Khu7{sKEl6lQO7K)y0G&5vPA7%u0{VZ5|ljDr|2#`9#rBMib4>~&nXnsWub34^dPz31n{(hfDR3^EKTg&J$bI)Gj zvSaqNOmsZ%k1D34thu&J>3%DoMo+o}05%LYWtc5jHjv@_YH zMu^@_J^+t^6#F&2rjqisx@caVpNDI}O5ZVRLn?h()Z;q@SdZ_9b6_0c@E9GU&oZd9 z@n9CN)?>W%@IY7&XRyzmJr8{J03ZhtPMyB0L~IzFT}4FCE%9ya%p;?aOE>Q*!(UcE z=3#hsUA|l^I_jcPYke@9E5a|1* zVF{4mwY$0(J~LDzPqSZG*e4jU5>Ws~k|M$(V}mGyNhil^HN|uSMbIDZmBy7N86W$) zk*y_Z*!$%@T8@g$!yHGa?8F35GCGAjis-nLJv}Y%#g-wWqcKrDZaBDla|t>w58eG_ zJDTxIJ9*vKlFTaO!kFRsp1|7Yf1M*CnDMJ0R^5m4LEAp|wKc^PTGm628o_Cq`K{#Y+ z7XDw4(jt+~Y@%IyQS0{4lj50k-lG`iQgb6qZtZI+Y|J+)xOTHn$E`KBJM@}~>JFzQ zH?b$iHssH?b@$Mn1#u}M$p zZI#Tl;9^hTR6yAGw9ZEc#5HM)Ad^ztEM{lcVS)o`%#2WAYLSOR>{D{guF^$65VoIr zN`WAAr7sX46X4G-Z@At25~Gx5)@|9h4tC@?hWQtE0$s%7_1lwsbLWqCbSap+x7=b> z#x~p(efrkL=*+rvJtueGyEhQ#&I`H4DAg?N1c8ILyJGRW8cYU_AfwE`PvxSxYA)xl zoMZ#Hp%H6O1{v=auQ}`;U;WQvIU&0}s6=j)*(*82%t|bCqcfkXcK@eX)B6;nuq&*8 zsl;mVQ+(Bz(OQaJ*!a>oQE-xX5#@5FVDY$s`g36d_GG(H`zi~^eJ-(aPPbirH>=hv z{c9U#&!Mre2R)~MTho3(BR8Zr*S-Ddfr}`+p>E5!w-!>{&B(RNa-TiC;b6a$gI?6i zL8~toE=;G>&Qy+;&@k9ke*jfj?lSDnzjAoDzHREfy{HYfh0zULU4iFGvC{AIoGEC$ zJEg4S-#4XiaGoCY?$)5u(xW*@Puf+t()Hq4i?yQ=+8_2PS_a1lA{;*ilD;*<2aahp>ymX`3JG_KwM#Ky#f;P?0G_DdsO zvofB4rB=IVd;eTKxQY3v{@OUHKL@0+w3%CS6*~T8!tXRvaeAUc_ zIYq$kLoS5`fD0ho;r7++gb{Kno8KYWF$EIHt98Lfjoc<8y@JYkTIbk3$-$w*jYcsF z>nI%&alg|3s}n9qc_?%6+lH+?DY{S6N`}n_8;O@lL!;dY8y$V$msV*uu11%#2-iHl z@WNIN*5T(~CJ<3do=+{Lc!`jCV;BoN<6BWdW;`+6M2UgzmOxR z>aJQOn+)!gii3W;A>#0I6SzxA-I5GNF^&Et*sQPbpBO#iheSeji z3-!Zc8Q;0OpzaNb!1TQP$^T15Rc%2%sICCX5!}y4A|l@RHEim4Q!@eXY#9dG@B{KicD|;MCBkDSWtP4Q{&NS_X>MH5ItqMvS zIh+(hLwQ56ZAVPcLb5OI* zzmGkZeo1~-llZaBi>(lw!+zaZ0DAZKEC9=RTm-td*YSA9lh{@S%(}n9yU%^K*7Fh= z>RfHeJ}2tSy&!=yt{K^0kg_gV+KavtQ(jBC@y{tE0*b%(jB9eo)!_>gt4wY_K1O>R zA|fudw4x|N+6Et#5#B%B#x)JuW7-v>zG9>w%B)4W{M!)G$N4blCVs4J>9keESA%@U zKUT6AoHRE?sIAr!^jlWyH0(lhu4M$|7C{2V$vKgGLE>Df9*yat@InO*)M)@#=jxq^#`M;S5jD30lA6IbK zH$y;OJk;I*Luz2hWC9$Eu}OFJ1?UMT@_r#=CWrmpj%n%cenX?*>fihITl5%oh2CRi zg&%;Ld)&GpN!|@8Ifm4b@zURA`{Ct6lx0M=Ao;o|2mF5V)yT%#px3V+Dmy-bIW#6o zR%J)8-{|GSK(^qkkwdRbN3SjUB?M>VE?XZ_mI<^uUabGGBf)P*;^`HC|La%?&dk?G zFI^b07knvluIe|wbfMQOJ?>>uCsg=9ACBqa1y@Eqz0h2dk#SkKl9MVpl6CgV7dEo{ z3wl~zp?dxnW;N)$d7#w*Q@7WU5l>?&AJTf5YmluSOTVQpSf}u=hs?QEnt$6h+`2G4 z<1QVpu1Fl9=RdXx>V*+U53Hk-WzGGuaHW7)%ZwxRh`Os}{>T=xsxZ#JdoRxB^%LzP zV&)2LerbKEs&q6L?P^0`t8?DENkN08? z?16`-KFJyQ#1I}T%$mob4x;c!rk`6b*L zQS94``3j{-8u4xMw@?z&syI*_!B?C-z@*Z}!^7fly$a#`bie*!Ve*4Zq8tIjp?L1m zW#aSAMehDIH_G*~`N6o1T*V-`aIgHvumZw(h+j7g4voS?*UYFNZ8ob#WjTYch_VAL zILq!zhGZ^`A2fALiNZkr43tHJdWqalZu-x_J}DE!O4=+S27z{bi(wC-i~O6DzJuBW zrmo-hPW`f3gT};`)A7vzks~4jBf>N#Ksd^+!FvKx0kk7hFz)<|vp)hZf*}vVsqBxk zCF^iW*_9;hjUd0lChY#89}yq-RTP@mxWyU8J#9NILb+mGg*qof`HSv`GCQ5*agtbj zMnz5-`&at7o%t|9Q|$F$$#W*2*5t2!aLzM}>Es@V8xm_h|M|$~ubCLm>h+)3SJA-m z#TlqR!dgqY7@^Texdo5E_P?l{1vqBmU$((!O+QiqG3x1|_f5Es7;fFy8lv;E0R~E$ z%ArTARgbnQj!m$y5|K_-wSW<(SU^=lbhOXi2bVw&My^Aa!f>{nmYj%jV0?`%g#QZ# zsD)-2lh@N9MfN+T7(VRQ|Mi}dl`FwD#IRBrrnnbU+2Vwj2j-iNvx9~v6`0M1t;B}3 z6m&7{H=>GqVJ|pJzlpuT*2Q}OdFH@(Q0aCUbYF{zAK%-=HK5vauWNA$5@v4VK-vrW zR|jB(MwA(~Li3t$2Dq_Ne((}m>NO;|ZFQ?wwa(P2ZLTUjKk7zYlMQ(p;2nOBZ*^@W zGc}4P{$PP#$l|2gCJ-Nxuhm>Tv1;B>G{Kd|;&csFoogackTLFr5N`*ZjL7(hCoRMX zC!CLXB*fmXWc~W#T_0MeNHF5nS9_S@n2GxRov$KW3zk4#aa|~QT*{B&uRn>>HBQX% zj{)+FZ?wY*0dtMZRIFgM2S?mo`Xw-@t6az#B)l~8jrOb)@u&=7^G?M7s1e3>jAALO zL&mi$4iyQ|_~4(j7!`ukd#(mMaX&$`Gj&_mMgJeQ02%q(hfRyk+Qx!CQN7>3kX}wJ z1fj3sux^+8A-+JHRJqiVCl$!;yLrYrHSfyP6A^s2kANh4Sff$hS_Ml8JmY<-m zt|TWT`VZOuznqB-op0jABSJ-shdL+Y$&khPFDcA(R@!D!37}_mqt18yose%PXOD!x z&nBAOy$tMs%Y(TXsSuptVW*r6Ave(qF_n%Whxj@Q{D?Ac_#r(M zN4=CWhmQ7qojJ`tNhSQR))5)`I*XaB2OIfv-=&PhQ#!}&6FHqK;XnU(k}};*ZnWNy zJHJ#CEdH$fbz!XL z_)}5|$Kw_NZWC)F6+&?L7SW`HKl!@hg(H{tVC-J*0uZ!bLGTVJG|)7%pD!NpyHb&K z(l)h1?BY*aqp*&Gt%j96Uy?0lSCaF)pw37}TC}hSk2D29!?5PsM{uG?kU)}8Jt1J%LUNn3*Fybg z6eLY|02y-;WU^?i$iy%_hG{yp0+7T^mHtam975>f4zagmFrnE`QN zV4G&G??T&P(+n!TF?(F)}RlQ@&$G2<5c7eWKmWM3<4!2ma_iR7l?wD=-+t26#=jL zYOb<}ZCYZn&Rh=b+ZHA5b?*7!-0RA9K9r)k%kKhN3g}N7T8uFPbfbkM4a>oE=*^CT z?ztn83L{rl@Td1G7;uzWkgDj-s11ZKtDlCrwF;#@X{ zawEb`#j1KX02oSo7Jh%jbs4PcGq%6W~D%8mB)*; z*EMl{0pSLH?7KA!1&40tq!P=rE}z*BC`|0|K|#n0+UaV{=LbQ^y%iezKNQdXPp0ie zcbIGRTRrH_BA5BHB04Q01uG~!*EZ-Vds1mz+O$LDT(T03fB2aJZ$htvB$6fbSL*f$ zM(}}J3XVJE7_bn4+XA76!~zHzVxn!LH7%lG5d2?(F^UQ|sI9}^C@7m5=|!%h#}DL# zs87w9yD=m_uxR24Q$To(2bcRP;AAi83hLxwp6(x1)vsg)RLVQBb<26@D4E|&^+9I) zR!7%KHzqYFm5L{OJetgkP)+ESs~j_|u6x(30Iz9*WLmq5=p^XLaC-2Ad7t3EC-~_v zFlZ1jgL%%~X{0t0;LEu7pgZF7wY%nl#BtD63?V^ zl9CZxI87ETT9`(@<`9|uNh=fZMkGrzc#+?v|Nr*+!M&&QrOyIF6$>fyE)-k%d^N!g z<8I>_Q#M1mH>kZtz?=A$z_`Z}$U)>PNZutkRDk~JBqSUIYqLFicp;3F^x&(~V z*^}ac{$BXPyHMbmbyd##@KXIYbsF-40*(RlrlTw~>Al+?gel`e%zoFi$iCCpAG5~@awGk+YhkbD2vCHsOTsBR80hnuLL#72ypZ>59G0b>9*!C38u z9{+ktDdPb|qd|SxtalIjSq@iS9kO(FVScHc?wm+}4MT+Su<-dCFqh_o(5Gv);G3aJ zK|cTN15JsY@V{|icG*vLME&WXJ^$fveA6eq^3_N`eYTbvsf@8K=RX?u&nBvqM=XmR zFQ~nXb~1$Mum3n~r+a+iX6*aM@lH3VdBp@NdB4rM*Bvr?Z1_*&sG4Jx`(N zjjM%DSsn<5Ay1w>F-Px5U6%CV;C)rNhdnX0Hax!I!$H$FG>Z`CH_mT%$gCIEi;SbT z@`CY`=U})>>XZ(!aH}-V`QUUeMkGA|E2+^(^~T?&qUx5$lAMo`Y5!oY zQ0xulLw=0qt7v>i%P>bPw#U#5LhS>{T@0X zlS5(Js2AD?nmK1ZT*rv+)C2(mbECp#ahb>?1vbvr-TNQ#n9d@3TtL%Y!}I z)T#sv=e`90n6vxq+V^d8DbnxzIUZ79La3@+9hTsuB=6cptp6llZ>Va8Bdw z+~FZqBoJR0O_P$<2k>qY_Z1<>OCE42v=V%mhLr-9YiEm!TC01*`mAK(R5mF6EvN=C zqL_4%F9;?;r+O%i+3XQCUsySu(>3nq4Jvk!zr zTouxUXz?p>G$`LFzip?{{rBonRWmG literal 0 HcmV?d00001 diff --git a/Build/assets/js/app--debug.1675992195953.js b/Build/assets/js/app--debug.1675992195953.js new file mode 100644 index 0000000..398dfa0 --- /dev/null +++ b/Build/assets/js/app--debug.1675992195953.js @@ -0,0 +1,64554 @@ +// -------------------------------------- +// +// _ _ _/ . _ _/ /_ _ _ _ +// /_|/_ / /|//_ / / //_ /_// /_/ +// https://activetheory.net _/ +// +// -------------------------------------- +// 2/9/23 6:23p +// -------------------------------------- + +/** + * Native polyfills and extensions for Hydra + * @name Polyfill + */ + +if (typeof(console) === 'undefined') { + window.console = {}; + console.log = console.error = console.info = console.debug = console.warn = console.trace = function() {}; +} + +window.performance = (function() { + if (window.performance && window.performance.now) return window.performance; + else return Date; +})(); + +Date.now = Date.now || function() { return +new Date; }; + +if (!window.requestAnimationFrame) { + window.requestAnimationFrame = (function() { + return window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + (function() { + const start = Date.now(); + return function(callback) { + window.setTimeout(() => callback(Date.now() - start), 1000 / 60); + } + })(); + })(); +} + +/** + * Temporary alias for Core. Gets overwritten when Timer instantiated. + * @see Timer + * @private + */ +window.defer = window.requestAnimationFrame; + +/** + * Extends clearTimeout to clear hydra timers as well as native setTimeouts + * @name window.clearTimeout + * @memberof Polyfill + * + * @function + * @param {Number} ref + * @example + * let timer = _this.delayedCall(myFunc, 1000); + * clearTimeout(timer); + */ +window.clearTimeout = (function() { + const _clearTimeout = window.clearTimeout; + return function(ref) { + + // If Timer exists, try and see if is a hydra timer ref otherwise run native + if (window.Timer) return Timer.__clearTimeout(ref) || _clearTimeout(ref); + return _clearTimeout(ref); + } +})(); + +/** + * Fires callback when framerate idles, else fire at max time. Alias of window.requestIdleCallback + * @name window.onIdle + * @memberof Polyfill + * + * @function + * @param {Function} callback + * @param {Number} max - Milliseconds + * @example + * onIdle(myFunc, 1000); + */ +window.requestIdleCallback = (function() { + const _requestIdleCallback = window.requestIdleCallback; + return function(callback, max) { + if (_requestIdleCallback) { + return _requestIdleCallback(callback, max ? {timeout: max} : null); + } + return defer(() => { + callback({didTimeout: false}); + }, 0); + } +})(); + +window.onIdle = window.requestIdleCallback; + +if (typeof Float32Array == 'undefined') Float32Array = Array; + +/** + * @name Math.sign + * @memberof Polyfill + * + * @function + * @param {Number} x + * @return {Number} Returns 1.0 if above 0.0, or -1.0 if below + */ +Math.sign = function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) return Number(x); + return x > 0 ? 1 : -1; +}; + +/** + * Returns rounded number, with decimal places equal to precision + * @name Math.round + * @memberof Polyfill + * + * @function + * @param {Number} Value to be rounded + * @param {Integer} [precision = 0] Number of decimal places to return. 0 for integers. + * @returns {Number} Rounded number + * @example + * // Returns 3.14 + * Math.round(3.14854839, 2); + */ +Math._round = Math.round; +Math.round = function(value, precision = 0) { + let p = Math.pow(10, precision); + return Math._round(value * p) / p; +}; + +/** + * Returns random number between min and max values inclusive, with decimal places equal to precision + * @name Math.random + * @memberof Polyfill + * + * @function + * @param {Number} [min=0] Min possible returned value + * @param {Number} [max=1] Max possible returned value - inclusive. + * @param {Integer} [precision = 0] Number of decimal places to return. 0 for integers. + * @returns {Number} Between min and max inclusive + * @example + * // Returns int between 3 and 5 inclusive + * Math.random(3, 5, 0); + */ +Math._random = Math.random; +Math.rand = Math.random = function(min, max, precision = 0) { + if (typeof min === 'undefined') return Math._random(); + if (min === max) return min; + + min = min || 0; + max = max || 1; + + if (precision == 0) return Math.floor(Math._random() * ((max+1) - min) + min); + return Math.round((min + Math._random() * (max - min)), precision); +}; + +/** + * Converts radians into degrees + * @name Math.degrees + * @memberof Polyfill + * + * @function + * @param {Number} radians + * @returns {Number} + */ + +Math.degrees = function(radians) { + return radians * (180 / Math.PI); +}; + +/** + * Converts degrees into radians + * @name Math.radians + * @memberof Polyfill + * + * @function + * @param {Number} degrees + * @returns {Number} + */ +Math.radians = function(degrees) { + return degrees * (Math.PI / 180); +}; + +/** + * Clamps value between min and max + * @name Math.clamp + * @memberof Polyfill + * + * @function + * @param {Number} value + * @param {Number} [min = 0] + * @param {Number} [max = 1] + * @returns {Number} + */ +Math.clamp = function(value, min = 0, max = 1) { + return Math.min(Math.max(value, Math.min(min, max)), Math.max(min, max)); +}; + +/** + * Maps value from an old range onto a new range + * @name Math.map + * @memberof Polyfill + * + * @function + * @param {Number} value + * @param {Number} [oldMin = -1] + * @param {Number} [oldMax = 1] + * @param {Number} [newMin = 0] + * @param {Number} [newMax = 1] + * @param {Boolean} [isClamp = false] + * @returns {Number} + * @example + * // Convert sine curve's -1.0 > 1.0 value to 0.0 > 1.0 range + * let x = Math.map(Math.sin(time)); + * @example + * // Shift range + * let y = 80; + * let x = Math.map(y, 0, 200, -10, 10); + * console.log(x); // logs -2 + * @example + * // Reverse direction and shift range + * let y = 0.9; + * let x = Math.map(y, 0, 1, 200, 100); + * console.log(x); // logs 110 + */ +Math.map = Math.range = function(value, oldMin = -1, oldMax = 1, newMin = 0, newMax = 1, isClamp) { + const newValue = (((value - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin; + if (isClamp) return Math.clamp(newValue, Math.min(newMin, newMax), Math.max(newMin, newMax)); + return newValue; +}; + +/** + * Return blend between two values based on alpha paramater + * @name Math.mix + * @memberof Polyfill + * + * @function + * @param {Number} a + * @param {Number} b + * @param {Number} alpha - Range of 0.0 to 1.0. Value of 0.0 returns a, value of 1.0 returns b + * @returns {Number} + * @example + * console.log(Math.mix(0, 10, 0.4)); // logs 4 + */ +Math.mix = function(a, b, alpha) { + return a * (1.0 - alpha) + b * alpha; +}; + +/** + * Returns 0.0 if value less than edge, 1.0 if greater. + * @name Math.step + * @memberof Polyfill + * + * @function + * @param {Number} edge + * @param {Number} value + * @returns {Number} + */ +Math.step = function(edge, value) { + return (value < edge) ? 0 : 1; +}; + +/** + * Returns 0.0 if value less than min, 1.0 if greater than max. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials. + * @name Math.smoothstep + * @memberof Polyfill + * + * @function + * @param {Number} min + * @param {Number} max + * @param {Number} value + * @returns {Number} + */ +Math.smoothStep = function(min, max, value) { + const x = Math.max(0, Math.min(1, (value - min) / (max - min))); + return x * x * (3 - 2 * x); +}; + +/** + * Returns fraction part of value + * @name Math.fract + * @memberof Polyfill + * + * @function + * @param {Number} value + * @returns {Number} + */ +Math.fract = function(value) { + return value - Math.floor(value); +}; + +/** + * Returns time-based interpolated value + * @name Math.lerp + * @memberof Polyfill + * + * @function + * @param {Number} target + * @param {Number} value + * @param {Number} alpha + * @returns {Number} + */ +Math.lerp = function (target, value, alpha, calcHz = true) { + if (calcHz) { + alpha = Math.framerateNormalizeLerpAlpha(alpha); + } else { + alpha = Math.clamp(alpha); + } + return value + ((target - value) * alpha); +}; + +{ + const mainThread = !!window.document; + + /** + * Returns logarithmically normalized value of a lerp alpha based on exact time + * since last frame, where the alpha is given based on the “standard” refresh + * rate 60Hz. Accounts for dropped frames and faster or slower refresh rates. + * + * To see why the alpha does not vary linearly with respect to the refresh + * rate, consider an example lerping from 0 to 1 with alpha 0.5. + * + * Lerping at 60Hz: + * frame 1.0: 0 * 0.5 + 1 * 0.5 = 0.5 + * frame 2.0: 0.5 * 0.5 + 1 * 0.5 = 0.75 + * frame 3.0: 0.75 * 0.5 + 1 * 0.5 = 0.875 + * ... + * + * When lerping at 120Hz, the lerp should proceed at the same rate as for + * the 60Hz example, so the values at frames 1.0, 2.0 etc. should be + * the same as above. But if we simply halve the alpha to 0.25, we get: + * frame 0.5: 0 * 0.75 + 1 * 0.25 = 0.25 + * frame 1.0: 0.25 * 0.75 + 1 * 0.25 = 0.4375 + * frame 1.5: 0.4375 * 0.75 + 1 * 0.25 = 0.578125 + * frame 2.0: 0.578125 * 0.75 + 1 * 0.25 = 0.68359375 + * frame 2.5: 0.68359375 * 0.75 + 1 * 0.25 = 0.7626953125 + * frame 3.0: 0.7626953125 * 0.75 + 1 * 0.25 = 0.822021484375 + * - i.e. the lerp is progressing more slowly + * + * The correct alpha is ~0.293: + * frame 0.5: 0 * 0.707 + 1 * 0.293 = 0.293 + * frame 1.0: 0.293 * 0.707 + 1 * 0.293 = ~0.5 + * frame 1.5: 0.5 * 0.707 + 1 * 0.293 = 0.6465 + * frame 2.0: 0.6465 * 0.707 + 1 * 0.293 = ~0.75 + * frame 2.5: 0.75 * 0.707 + 1 * 0.293 = 0.82325 + * frame 3.0: 0.82325 * 0.707 + 1 * 0.293 = ~0.875 + * + * @param t + * @returns {number} + */ + Math.framerateNormalizeLerpAlpha = function(t) { + t = Math.clamp(t); + if (!mainThread) return t; + return 1 - Math.exp(Math.log(1 - t) * Render.FRAME_HZ_MULTIPLIER); + }; +} + +/** + * Modulo limited to positive numbers + * @name Math.mod + * @memberof Polyfill + * + * @function + * @param {Number} value + * @param {Number} n + * @returns {Number} + */ +Math.mod = function(value, n) { + return ((value % n) + n) % n; +}; + +/** + * Shuffles array + * @name Array.prototype.shuffle + * @memberof Polyfill + * + * @function + * @returns {Array} shuffled + */ + +Object.defineProperty(Array.prototype, 'shuffle', { + writable: true, + value: function() { + let currentIndex = this.length, randomIndex; + + while (currentIndex != 0) { + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + [this[currentIndex], this[randomIndex]] = [this[randomIndex], this[currentIndex]]; + } + + return this; + } +}); + +Array.storeRandom = function(arr) { + arr.randomStore = []; +}; + +/** + * Returns random element. If range passed in, will not return same element again until function has been called enough times to surpass the value. + * @name Array.prototype.random + * @memberof Polyfill + * + * @function + * @param {Integer} [range] + * @returns {ArrayElement} + * @example + * let a = [1, 2, 3, 4]; + * for (let i = 0; i < 6; i++) console.log(a.random(4)); // logs 3, 1, 2, 4, 3, 1 + */ + + Object.defineProperty(Array.prototype, 'random', { + writable: true, + value: function(range) { + let value = Math.random(0, this.length - 1); + if (arguments.length && !this.randomStore) Array.storeRandom(this); + if (!this.randomStore) return this[value]; + if (range > this.length - 1) range = this.length; + if (range > 1) { + while (!!~this.randomStore.indexOf(value)) if ((value += 1) > this.length - 1) value = 0; + this.randomStore.push(value); + if (this.randomStore.length >= range) this.randomStore.shift(); + } + return this[value]; + } + }); + +/** + * Finds and removes element value from array + * @name Array.prototype.remove + * @memberof Polyfill + * + * @function + * @param {ArrayElement} element - Element to remove + * @returns {Array} Array containing removed element + * @example + * let a = ['cat', 'dog']; + * a.remove('cat'); + * console.log(a); // logs ['dog'] + */ + +Object.defineProperty(Array.prototype, 'remove', { + writable: true, + value: function(element) { + if (!this.indexOf) return; + const index = this.indexOf(element); + if (!!~index) return this.splice(index, 1); + } +}); + +/** + * Returns last element + * @name Array.prototype.last + * @memberof Polyfill + * + * @function + * @returns {ArrayElement} + */ + +Object.defineProperty(Array.prototype, 'last', { + writable: true, + value: function() { + return this[this.length - 1] + } +}); + +window.Promise = window.Promise || {}; + +if (!Array.prototype.flat) { + Object.defineProperty(Array.prototype, 'flat', { + configurable: true, + value: function flat () { + var depth = isNaN(arguments[0]) ? 1 : Number(arguments[0]); + + return depth ? Array.prototype.reduce.call(this, function (acc, cur) { + if (Array.isArray(cur)) { + acc.push.apply(acc, flat.call(cur, depth - 1)); + } else { + acc.push(cur); + } + + return acc; + }, []) : Array.prototype.slice.call(this); + }, + writable: true + }); +} + +/** + * Returns new Promise object + * @name Promise.create + * @memberof Polyfill + * + * @function + * @returns {Promise} + * @example + * function waitOneSecond() { + * let p = Promise.create(); + * _this.delayedCall(p.resolve, 1000); + * return p + * } + * waitOneSecond().then(() => console.log('happy days')); + */ +Promise.create = function() { + const promise = new Promise((resolve, reject) => { + this.temp_resolve = resolve; + this.temp_reject = reject; + }); + promise.resolve = this.temp_resolve; + promise.reject = this.temp_reject; + delete this.temp_resolve; + delete this.temp_reject; + return promise; +}; + +Promise.catchAll = function(array) { + return Promise.all(array.map(promise => + promise.catch(error => { + // Now that the rejection is handled, the original promise will + // complete with result undefined, so Promise.all() will complete. + // To allow the rejection to still be handled by a global + // `unhandledrejection` handler (e.g. so that Dev.postErrorsToServer() + // can log it), reject a new separate promise. + // (Note: For this to work, the new promise must not be returned here). + Promise.reject(error); + }) + )); +}; + +Promise.timeout = function(promise, timeout) { + if (Array.isArray(promise)) { + promise = Promise.all(promise); + } + return Promise.race([promise, Timer.delayedCall(timeout)]); +}; + +(function() { + // `IsRegExp` abstract operation + // https://tc39.es/ecma262/#sec-isregexp + function isRegExp(it) { + var isObject = typeof it == 'object' ? it !== null : (typeof it == 'function'); + if (!isObject) return false; + var match = it[typeof Symbol !== 'undefined' ? Symbol.match : 'match']; + if (match !== undefined) return !!match; + return Object.prototype.toString.call(it).slice(8, -1) === 'RegExp'; + } + + function notRegExp(it) { + if (isRegExp(it)) throw new Error('First argument to String.prototype.includes must not be a regular expression'); + return it; + } + + /** + * Check if string contains phrase + * @name String.prototype.includes + * @memberof Polyfill + * + * @function + * @param {String|String[]} str - Either a string or array of strings to check for + * @returns {boolean} + * @example + * let userName = 'roger moore'; + * console.log(userName.includes(['steve', 'andrew', 'roger']); // logs true + */ + Object.defineProperty(String.prototype, 'includes', { + writable: true, + value: function(str) { + if (!Array.isArray(str)) return !!~this.indexOf(notRegExp(str)); + for (let i = str.length - 1; i >= 0; i--) { + if (!!~this.indexOf(notRegExp(str[i]))) return true; + } + return false; + } + }); + +})(); + +Object.defineProperty(String.prototype, 'equals', { + writable: true, + value: function(str) { + let compare = String(this); + if (!Array.isArray(str)) return str === compare; + for (let i = str.length - 1; i >= 0; i--) { + if (str[i] === compare) return true; + } + return false; + } +}); + +Object.defineProperty(String.prototype, 'strpos', { + writable: true, + value: function(str) { + console.warn('strpos deprecated: use .includes()'); + return this.includes(str); + } +}); + +/** + * Returns clipped string. Doesn't alter original string. + * @name String.prototype.clip + * @memberof Polyfill + * + * @function + * @param {Number} num - character length to clip to + * @param {String} [end] - add string to end, such as elipsis '...' + * @returns {string} - clipped string + */ + Object.defineProperty(String.prototype, 'clip', { + writable: true, + value: function(num, end = '') { + return this.length > num ? this.slice(0, Math.max( 0, num - end.length )).trim() + end : this.slice(); + } + }); + +/** + * Returns string with uppercase first letter. Doesn't alter original string. + * @name String.prototype.capitalize + * @memberof Polyfill + * + * @function + * @returns {string} + */ + Object.defineProperty(String.prototype, 'capitalize', { + writable: true, + value: function() { + return this.charAt(0).toUpperCase() + this.slice(1); + } + }); + +/** + * Replaces all occurrences within a string + * @name String.prototype.replaceAll + * @memberof Polyfill + * + * @function + * @param {String} find - sub string to be replaced + * @param {String} replace - sub string that replaces all occurrences + * @returns {string} + */ + Object.defineProperty(String.prototype, 'replaceAll', { + writable: true, + value: function(find, replace) { + return this.split(find).join(replace); + } + }); + +Object.defineProperty(String.prototype, 'replaceAt', { + writable: true, + value: function(index, replacement) { + return this.substr(0, index) + replacement + this.substr(index + replacement.length); + } +}); + +/** + * fetch API polyfill + * @private + */ +if (!window.fetch || (!window.AURA && location.protocol.includes('file'))) window.fetch = function(url, options) { + options = options || {}; + const promise = Promise.create(); + const request = new XMLHttpRequest(); + + request.open(options.method || 'get', url); + if (url.includes('.ktx')) request.responseType = 'arraybuffer'; + + for (let i in options.headers) { + request.setRequestHeader(i, options.headers[i]); + } + + // request.withCredentials = options.credentials == 'include'; + + request.onload = () => { + promise.resolve(response()); + }; + + request.onerror = promise.reject; + + request.send(options.body); + + function response() { + let keys = [], + all = [], + headers = {}, + header; + + request.getAllResponseHeaders().replace(/^(.*?):\s*([\s\S]*?)$/gm, (m, key, value) => { + keys.push(key = key.toLowerCase()); + all.push([key, value]); + header = headers[key]; + headers[key] = header ? `${header},${value}` : value; + }); + + return { + ok: (request.status/200|0) == 1, // 200-399 + status: request.status, + statusText: request.statusText, + url: request.responseURL, + clone: response, + + text: () => Promise.resolve(request.responseText), + json: () => Promise.resolve(request.responseText).then(JSON.parse), + xml: () => Promise.resolve(request.responseXML), + blob: () => Promise.resolve(new Blob([request.response])), + arrayBuffer: () => Promise.resolve(request.response), + + headers: { + keys: () => keys, + entries: () => all, + get: n => headers[n.toLowerCase()], + has: n => n.toLowerCase() in headers + } + }; + } + return promise; +}; + +/** + * Send http GET request. Wrapper around native fetch api. Automatically parses json. + * @name window.get + * @memberof Polyfill + * + * @function + * @param {String} url + * @param {Object} options + * @returns {Promise} + * @example + * get('assets/geometry/curves.json).then(d => console.log(d)); + */ +window.get = function(url, options = {credentials: 'same-origin'}) { + let promise = Promise.create(); + options.method = 'GET'; + + fetch(url, options).then(handleResponse).catch(promise.reject); + + function handleResponse(e) { + if (!e.ok) return promise.reject(e); + e.text().then(text => { + if (text.charAt(0).includes(['[', '{'])) { + + // Try to parse json, else return text + try { + promise.resolve(JSON.parse(text)); + } catch (err) { + promise.resolve(text); + } + } else { + promise.resolve(text); + } + }); + } + + return promise; +}; + +/** + * Send http POST request. Wrapper around native fetch api. + * @name window.post + * @memberof Polyfill + * + * @function + * @param {String} url + * @param {Object} body + * @param {Object} [options] + * @returns {Promise} + */ +window.post = function(url, body = {}, options = {}) { + let promise = Promise.create(); + options.method = 'POST'; + if (body) options.body = typeof body === 'object' || Array.isArray(body) ? JSON.stringify(body) : body; + if (!options.headers) options.headers = {'content-type': 'application/json'}; + + fetch(url, options).then(handleResponse).catch(promise.reject); + + function handleResponse(e) { + if (!e.ok) return promise.reject(e); + e.text().then(text => { + if (text.charAt(0).includes('[') || text.charAt(0).includes('{')) { + + // Try to parse json, else return text + try { + promise.resolve(JSON.parse(text)); + } catch (err) { + promise.resolve(text); + } + } else { + promise.resolve(text); + } + }); + } + + return promise; +}; + +/** + * Send http PUT request. Wrapper around native fetch api. + * @name window.put + * @memberof Polyfill + * + * @function + * @param {String} url + * @param {Object} body + * @param {Object} [options] + * @returns {Promise} + */ +window.put = function(url, body, options = {}) { + let promise = Promise.create(); + options.method = 'PUT'; + if (body) options.body = typeof body === 'object' || Array.isArray(body) ? JSON.stringify(body) : body; + + fetch(url, options).then(handleResponse).catch(promise.reject); + + function handleResponse(e) { + if (!e.ok) return promise.reject(e); + e.text().then(text => { + if (text.charAt(0).includes(['[', '{'])) { + + // Try to parse json, else return text + try { + promise.resolve(JSON.parse(text)); + } catch (err) { + promise.resolve(text); + } + } else { + promise.resolve(text); + } + }); + } + + return promise; +}; + +/** + * Class creation and stucture. + * @name Core + */ + +/** + * Class constructor + * @name Class + * @memberof Core + * + * @function + * @param {Function} _class - main class function + * @param {String|Function} [_type] - class type ('static' or 'singleton') or static function + * @param {Function} [_static] - static function if type is passed through, useful for 'singleton' type + * @example + * + * // Instance + * Class(function Name() { + * //... + * }); + * + * new Name(); // or + * _this.initClass(Name); + * @example + * // Static + * Class(function Name() { + * //... + * }, 'static'); + * + * console.log(Name); + * @example + * // Singleton + * Class(function Name() { + * //... + * }, 'singleton'); + * + * Name.instance(); + * @example + * // Instance with Static function + * Class(function Name() { + * //... + * }, function() { + * // Static + * Name.EVENT_NAME = 'event_name'; + * }); + * @example + * // Singleton with Static function + * Class(function Name() { + * //... + * }, 'singleton', function() { + * // Static + * }); + + */ +window.Class = function(_class, _type, _static) { + const _this = this || window; + + // Function.name ie12+ only + const _name = _class.name || _class.toString().match(/function ?([^\(]+)/)[1]; + + // if (typeof Hydra !== 'undefined' && Hydra.LOCAL && _this[_name] && _this[_name].toString().indexOf('[native code]') < 0) { + // console.warn('Class ' + _name + ' already exists!'); + // } + + // Polymorphic if no type passed + if (typeof _type === 'function') { + _static = _type; + _type = null; + } + + _type = (_type || '').toLowerCase(); + + // Instanced Class + if (!_type) { + _this[_name] = _class; + + // Initiate static function if passed through + _static && _static(); + } else { + + // Static Class + if (_type == 'static') { + _this[_name] = new _class(); + + // Singleton Class + } else if (_type == 'singleton') { + _this[_name] = _class; + + (function() { + let _instance; + + _this[_name].instance = function() { + if (!_instance) _instance = new _class(...arguments); + return _instance; + }; + })(); + + // Initiate static function if passed through + _static && _static(); + } + } + + // Giving namespace classes reference to namespace + if (this && this !== window) this[_name]._namespace = this.__namespace; +}; + +/** + * Inherit class + * @name Inherit + * @memberof Core + * + * @function + * @param {Object} child + * @param {Function} parent + * @param {Array} [params] + * @example + * Class(function Parent() { + * this.method = function() { + * console.log(`I'm a Parent`); + * }; + * }); + * + * Class(function Child() { + * Inherit(this, Parent); + * + * // Call parent method + * this.method(); + * // Logs 'I'm a Parent' + * + * // Overwrite method + * this.method = function() { + * console.log(`I'm a Child`); + * + * // Call overwritten method with _ prefix + * this._method(); + * }; + * }); + * + * let child = new Child(); + * + * // Need to defer to wait for method overwrite + * defer(child.method); + * // Logs 'I'm a Child', 'I'm a Parent' + */ +window.Inherit = function(child, parent) { + const args = [].slice.call(arguments, 2); + parent.apply(child, args); + + // Store methods for super calls + const save = {}; + for (let method in child) { + save[method] = child[method]; + } + + const addSuperMethods = () => { + for (let method in save) { + if (child[method] && child[method] !== save[method]) { + if (method == 'destroy' && !child.__element) throw 'Do not override destroy directly, use onDestroy :: ' + child.constructor.toString(); + let name = method; + do { + name = `_${name}`; + } while (child[name]); + child[name] = save[method]; + } + } + }; + if (child.__afterInitClass) { + // When using Component.initClass(), use the hook to add super methods + // synchronously, immediately after the constructor returns + child.__afterInitClass.push(addSuperMethods); + } else { + // defer to wait for child to add its own methods + defer(addSuperMethods); + } +}; + +/** + * Create class namespace for hydra + * @name Namespace + * @memberof Core + * + * @function + * @param {Object|String} obj + * @example + * // Example using object + * Class(function Baby() { + * Namespace(this); + * }, 'static'); + * + * Baby.Class(function Powder() {}); + * + * new Baby.Powder(); + * @example + * // Example using string + * Class(function Baby() { + * Namespace('Talcum'); + * }, 'static'); + * + * Talcum.Class(function Powder() {}); + * + * new Talcum.Powder(); + */ +window.Namespace = function(obj) { + if (typeof obj === 'string') { + if (!window[obj]) window[obj] = {Class, __namespace: obj}; + } else { + obj.Class = Class; + obj.__namespace = obj.constructor.name || obj.constructor.toString().match(/function ([^\(]+)/)[1]; + } +}; + +/** + * Object to attach global properties + * @name window.Global + * @memberof Core + * + * @example + * Global.PLAYGROUND = true; + */ +window.Global = {}; + +/** + * Boolean for if Hydra is running on a thread + * @name window.THREAD + * @memberof Core + */ +window.THREAD = false; + +/** + * Hydra namespace. Fires ready callbacks and kicks off Main class once loaded. + * @name Hydra + */ + +Class(function Hydra() { + const _this = this; + const _readyPromise = Promise.create(); + var _base; + + var _callbacks = []; + + this.HASH = window.location.hash.slice(1); + this.LOCAL = !window._BUILT_ && (location.hostname.indexOf('local') > -1 || location.hostname.split('.')[0] == '10' || location.hostname.split('.')[0] == '192' || /atdev.online$/.test(location.hostname)) && location.port == ''; + + (function () { + initLoad(); + })(); + + function initLoad() { + if (!document || !window) return setTimeout(initLoad, 1); + if (window._NODE_) return setTimeout(loaded, 1); + + if (window._AURA_) { + if (!window.Main) return setTimeout(initLoad, 1); + else return setTimeout(loaded, 1); + } + + window.addEventListener('load', loaded, false); + } + + function loaded() { + window.removeEventListener('load', loaded, false); + + _this.LOCAL = (!window._BUILT_ || location.pathname.toLowerCase().includes('platform')) && (location.hostname.indexOf('local') > -1 || location.hostname.split('.')[0] == '10' || location.hostname.split('.')[0] == '192' || /atdev.online$/.test(location.hostname)) && location.port == ''; + + _callbacks.forEach(cb => cb()); + _callbacks = null; + + _readyPromise.resolve(); + + // Initiate app + if (window.Main) { + _readyPromise.then(() => Hydra.Main = new window.Main()); + } + } + + /** + * Trigger page load callback + * @memberof Hydra + * @private + */ + this.__triggerReady = function () { + loaded(); + }; + + /** + * Attachment for ready event + * @name Hydra.ready + * @memberof Hydra + * + * @function + * @param {Function} [callback] Function to trigger upon page load + * @returns {Promise} - Returns promise if no callback passed in + * @example + * // either + * Hydra.ready(init); + * // or + * Hydra.ready().then(init); + * function init() {} + */ + this.ready = function (callback) { + if (!callback) return _readyPromise; + if (_callbacks) _callbacks.push(callback); + else callback(); + }; + + this.absolutePath = function (path) { + if (window.AURA) return path; + let base = _base; + if (base === undefined) { + try { + if (document.getElementsByTagName('base').length > 0) { + var a = document.createElement('a'); + a.href = document.getElementsByTagName('base')[0].href; + base = a.pathname; + _base = base; + } + } catch (e) { + _base = null; + } + } + let pathname = base || location.pathname; + if (pathname.includes('/index.html')) pathname = pathname.replace('/index.html', ''); + let port = Number(location.port) > 1000 ? `:${location.port}` : ''; + return path.includes('http') ? path : (location.protocol.length ? location.protocol + '//' : '') + (location.hostname + port + pathname + '/' + path).replace('//', '/'); + } + +}, 'Static'); + +/** + * Hydra tool-belt + * @name Utils + */ + +Class(function Utils() { + + var _queries = {}; + var _searchParams = new URLSearchParams(window.location.search); + + /** + * Parse URL queries + * @name this.query + * @memberof Utils + * + * @function + * @param {String} key + * @returns {string} + * @example + * // url is myProject/HTML?dev=1 + * console.log(Utls.query('dev')); // logs '1' + * @example + * // url is myProject/HTML?dev=0 + * console.log(Utls.query('dev')); // logs false + * // Also logs false for ?dev=false or ?dev= + */ + this.query = this.queryParams = function(key, value) { + if (value !== undefined) _queries[key] = value; + + if (_queries[key] !== undefined) return _queries[key]; + + if (_searchParams) { + value = _searchParams.get(key); + if (value === '0') value = 0; + else if (value === 'false' || value === null) value = false; + else if (value === '') value = true; + } else { + let escapedKey = encodeURIComponent(key).replace(/[\.\+\*]/g, '\\$&'); + value = decodeURIComponent(window.location.search.replace(new RegExp(`^(?:.*?[&?]${escapedKey}(?:\=([^&]*)|[&$]))?.*$`, 'i'), '$1')); + if (value == '0') { + value = 0; + } else if (value == 'false') { + value = false; + } else if (!value.length) { + value = new RegExp(`[&?]${escapedKey}(?:[&=]|$)`, 'i').test(window.location.search); + } + } + _queries[key] = value; + return value; + }; + + /** + * @name this.addQuery + * @memberof Utils + * + * @function + * @param query + * @param value + */ + this.addQuery = function ( query, value ) { + if ( _queries[ query ] === value ) return _queries[ query ]; + let url = new URL(location.href); + url.searchParams.set(query, value); + _searchParams = url.searchParams; + window.history.replaceState({}, document.title, url.toString()); + return _queries[ query ] = value; + } + + /** + * @name this.removeQuery + * @memberof Utils + * + * @function + * @param query + */ + this.removeQuery = function ( query ) { + let url = new URL(location.href); + url.searchParams.delete(query); + _searchParams = url.searchParams; + window.history.replaceState({}, document.title, url.toString()); + return delete _queries[ query ]; + } + + this.addQueryToPath = function(path, hash) { + return [ + [path, _searchParams.toString()].filter(Boolean).join('?'), + hash + ].filter(Boolean).join('#'); + } + + /** + * @name this.addParam + * @memberof Utils + * + * @function + * @param url + * @param param + * @param value + */ + this.addParam = function(url, param, value) { + let index = url.indexOf('?'); + let prefix = url.substring(0, index + 1); + let suffix = url.substring(index + 1); + let searchParams = new URLSearchParams(suffix); + searchParams.append(param, value); + return prefix + searchParams.toString(); + }; + + /** + * @name this.removeParam + * @memberof Utils + * + * @function + * @param url, param + */ + this.removeParam = function(url, param) { + let index = url.indexOf('?'); + let prefix = url.substring(0, index + 1); + let suffix = url.substring(index + 1); + let searchParams = new URLSearchParams(suffix); + searchParams.delete(param); + return prefix + searchParams.toString(); + }; + + // Object utils + + /** + * Get class constructor name + * @name this.getConstructorName + * @memberof Utils + * + * @function + * @param {Object} obj + * @returns {String} + */ + this.getConstructorName = function(obj) { + if (!obj) return obj; + + if (!obj.___constructorName) { + obj.___constructorName = (function() { + if (typeof obj === 'function') return obj.toString().match(/function ([^\(]+)/)[1]; + return obj.constructor.name || obj.constructor.toString().match(/function ([^\(]+)/)[1]; + })(); + } + + return obj.___constructorName; + }; + + /** + * Nullify object's properties + * @name this.nullObject + * @memberof Utils + * + * @function + * @param {Object} object + * @returns {null} + */ + this.nullObject = function(object) { + if (object && ( object.destroy || object.div)) { + for (var key in object) { + if (typeof object[key] !== 'undefined') object[key] = null; + } + } + return null; + }; + + /** + * Clone object + * @name this.cloneObject + * @memberof Utils + * + * @function + * @param {Object} obj + * @returns {Object} + */ + this.cloneObject = function(obj) { + return JSON.parse(JSON.stringify(obj)); + }; + + /** + * Return one of two parameters randomly + * @name this.headsTails + * @memberof Utils + * + * @function + * @param {Number} n0 + * @param {Number} n1 + * @returns {Object} + */ + this.headsTails = function(n0, n1) { + return Math.random(0, 1) ? n1 : n0; + }; + + /** + * Merge objects. Takes all arguments and merges them into one object. + * @name this.mergeObject + * @memberof Utils + * + * @function + * @param {Object} Object - Any number of object paramaters + * @returns {Object} + */ + this.mergeObject = function() { + var obj = {}; + for (var i = 0; i < arguments.length; i++) { + var o = arguments[i]; + for (var key in o) { + obj[key] = o[key]; + } + } + + return obj; + }; + + // Mathematical utils + + /** + * Returns unique timestamp + * @name this.timestamp + * @memberof Utils + * + * @function + * @returns {string} + */ + this.timestamp = this.uuid = function() { + return Date.now() + 'xx-4xx-yxx-xxx'.replace(/[xy]/g, function(c) { + let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }; + + /** + * Returns random Hex color value + * @name this.randomColor + * @memberof Utils + * + * @function + * @returns {string} - Hex color value + */ + this.randomColor = function() { + var color = '#' + Math.floor(Math.random() * 16777215).toString(16); + if (color.length < 7) color = this.randomColor(); + return color; + }; + + /** + * Turn number into comma-delimited string + * @name this.numberWithCommas + * @memberof Utils + * + * @function + * @param {Number} num + * @returns {String} - String of value with comma delimiters + */ + this.numberWithCommas = function(num) { + return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + }; + + /** + * Pads number with 0s to match digits amount + * @name this.padInt + * @memberof Utils + * + * @function + * @param {Number} num - Number value to convert to pad + * @param {Integer} [digits] - Number of digits to match + * @param {Boolean} [isLimit] limit to digit amount of 9s + * @returns {string} - Padded value + */ + this.padInt = function(num, digits, isLimit) { + if (isLimit) num = Math.min(num, Math.pow(10, digits) - 1); + let str = Math.floor(num).toString(); + return Math.pow(10, Math.max(0, digits - str.length)).toString().slice(1) + str; + }; + + /** + * Copies string to clipboard on interaction + * @name this.copyToClipboard + * @memberof Utils + * + * @function + * @param {string} string to copy to clipboard + * @returns {Boolean} - Success + */ + this.copyToClipboard = function(string) { + try { + var el = document.createElement( 'textarea' ); + var range = document.createRange(); + el.contentEditable = true; + el.readOnly = true; + el.value = string; + document.body.appendChild( el ); + el.select(); + range.selectNodeContents( el ); + var s = window.getSelection(); + s.removeAllRanges(); + s.addRange(range); + el.setSelectionRange(0, string.length); + document.execCommand('copy'); + document.body.removeChild( el ); + return true; + } catch ( e ) { + return false; + } + }; + + /** + * Formats an array of strings into a single string list + * @name this.stringList + * @memberof Utils + * + * @function + * @param {Array} Array of strings to join and format + * @param {Integer} Max number of items to list before shortening - optional + * @param {Object} Additional formatting options - optional + * @returns {String} - Formatted string + */ + this.stringList = function ( items = [], limit = 0, options = {} ) { + if ( items.length === 0 ) return ''; + + let output = ''; + let printed = 0; + + if ( typeof limit === 'object' ) { + options = limit; + limit = 0; + } + + options.oxford = options.oxford === true ? true : false; + options.more = options.more === false ? false : options.more ? options.more : 'more'; + options.and = options.and ? options.and : '&'; + options.comma = options.comma ? options.comma : ','; + + if ( !isNaN(options.limit)) limit = options.limit; + if ( limit === 0 ) limit = items.length; + + do { + let item = items.shift(); + output = `${output}${item}${options.comma} `; + printed++; + } while ( items.length > 1 && printed + 1 < limit ); + + output = output.trim(); + output = output.slice(0,output.length-1); + + if ( items.length === 1 ) { + output = `${output}${options.oxford && printed > 1 ? options.comma : ''} ${options.and} ${items.shift()}`; + } else if ( items.length > 1 && options.more ) { + let more = `${items.length} ${options.more}`; + output = `${output}${options.oxford && printed > 1 ? options.comma : ''} ${options.and} ${more}`; + } + + return output; + } + + /** + * @name this.debounce + * @memberof Utils + * + * @function + * @param callback + * @param time + */ + this.debounce = function (callback, time = 100) { + clearTimeout(callback.__interval); + callback.__interval = Timer.create(callback, time); + } + +}, 'Static'); + +/** + * Single global requestAnimationFrame render loop to which all other classes attach their callbacks to be triggered every frame + * @name Render + */ + +Class(function Render() { + const _this = this; + + const _render = []; + const _native = []; + const _drawFrame = []; + const _multipliers = []; + + // If loops are added or removed from within a loop, the loop counter might + // need to be adjusted to avoid missed callbacks. So put the loop counters + // in the top-level scope. + let _renderIndex = null; + let _nativeIndex = null; + + var _last = performance.now(); + var _skipLimit = 200; + var _localTSL = 0; + var _elapsed = 0; + var _capLast = 0; + var _sampleRefreshRate = []; + var _firstSample = false; + var _saveRefreshRate = 60; + var rAF = requestAnimationFrame; + var _refreshScale = 1; + var _canCap = 0; + var _screenHash = getScreenHash(); + + /** + * @name timeScaleUniform + * @memberof Render + * @property + */ + this.timeScaleUniform = {value: 1, type: 'f', ignoreUIL: true}; + /** + * @name REFRESH_TABLE + * @memberof Render + * @property + */ + this.REFRESH_TABLE = [30, 60, 72, 90, 100, 120, 144, 240]; + /** + * @name REFRESH_RATE + * @memberof Render + * @property + */ + this.REFRESH_RATE = 60; + /** + * @name HZ_MULTIPLIER + * @memberof Render + * @property + */ + this.HZ_MULTIPLIER = 1; + + /** + * @name capFPS + * @memberof Render + * @property + */ + this.capFPS = null; + + //*** Constructor + (function() { + if (THREAD) return; + rAF(render); + setInterval(_ => _sampleRefreshRate = [], 3000); + setInterval(checkMoveScreen, 5000); + })(); + + function render(tsl) { + if (_native.length) { + let multiplier = (60/_saveRefreshRate); + for (_nativeIndex = _native.length-1; _nativeIndex > -1; _nativeIndex--) { + _native[_nativeIndex](multiplier); + } + _nativeIndex = null; + } + + if (_this.capFPS > 0 && ++_canCap > 31) { + let delta = tsl - _capLast; + _capLast = tsl; + _elapsed += delta; + if (_elapsed < 1000 / _this.capFPS) return rAF(render); + _this.REFRESH_RATE = _this.capFPS; + _this.HZ_MULTIPLIER = (60/_this.REFRESH_RATE) * _refreshScale; + _elapsed = 0; + } + + _this.timeScaleUniform.value = 1; + if (_multipliers.length) { + for (let i = 0; i < _multipliers.length; i++) { + let obj = _multipliers[i]; + _this.timeScaleUniform.value *= obj.value; + } + } + + _this.DT = tsl - _last; + _last = tsl; + + let delta = _this.DT * _this.timeScaleUniform.value; + delta = Math.min(_skipLimit, delta); + + if (_sampleRefreshRate && !_this.capFPS) { + let fps = 1000 / _this.DT; + _sampleRefreshRate.push(fps); + if (_sampleRefreshRate.length > 30) { + _sampleRefreshRate.sort((a, b) => a - b); + let rate = _sampleRefreshRate[Math.round(_sampleRefreshRate.length / 2)]; + rate = _this.REFRESH_TABLE.reduce((prev, curr) => (Math.abs(curr - rate) < Math.abs(prev - rate) ? curr : prev)); + _this.REFRESH_RATE = _saveRefreshRate = _firstSample ? Math.max(_this.REFRESH_RATE, rate) : rate; + _this.HZ_MULTIPLIER = (60/_this.REFRESH_RATE) * _refreshScale; + _sampleRefreshRate = null; + _firstSample = true; + } + } + + _this.TIME = tsl; + _this.DELTA = delta; + + if (_this.startFrame) _this.startFrame(tsl, delta); + + _localTSL += delta; + + for (_renderIndex = _render.length - 1; _renderIndex >= 0; _renderIndex--) { + var callback = _render[_renderIndex]; + if (!callback) { + _render.splice(_renderIndex, 1); + continue; + } + if (callback.fps) { + if (tsl - callback.last < 1000 / callback.fps) continue; + callback(++callback.frame); + callback.last = tsl; + continue; + } + callback(tsl, delta); + } + _renderIndex = null; + + for (let i = _drawFrame.length-1; i > -1; i--) { + _drawFrame[i](tsl, delta); + } + + if (_this.drawFrame) _this.drawFrame(tsl, delta); //Deprecated + if (_this.endFrame) _this.endFrame(tsl, delta); //Deprecated + + if (!THREAD && !_this.isPaused) rAF(render); + } + + function getScreenHash() { + if (!window.screen) return 'none'; + + return `${window.screen.width}x${window.screen.height}.${window.screen.pixelDepth}`; + } + + function checkMoveScreen() { + var newScreen = getScreenHash(); + if (_screenHash === newScreen) return; + // Changed screen. recalculate refresh rate + + _screenHash = newScreen; + _sampleRefreshRate = null; + _firstSample = false; + } + + /** + * @name Render.now + * @memberof Render + * + * @function + */ + this.now = function() { + return _localTSL; + } + + /** + * @name Render.setRefreshScale + * @memberof Render + * + * @function + * @param scale + */ + this.setRefreshScale = function(scale) { + _refreshScale = scale; + _sampleRefreshRate = []; + } + + /** + * Add callback to render queue + * @name Render.start + * @memberof Render + * + * @function + * @param {Function} callback - Function to call + * @param {Integer} [fps] - Optional frames per second callback rate limit + * @example + * // Warp time using multiplier + * Render.start(loop); + * let _timewarp = 0; + * function loop(t, delta) { + * console.log(_timewarp += delta * 0.001); + * } + * @example + * // Limits callback rate to 5 + * Render.start(tick, 5); + * + * // Frame count is passed to callback instead of time information + * function tick(frame) { + * console.log(frame); + * } + */ + this.start = function(callback, fps, native) { + if (fps) { + callback.fps = fps; + callback.last = -Infinity; + callback.frame = -1; + } + + // unshift as render queue works back-to-front + if (native) { + if (!~_native.indexOf(callback)) { + _native.unshift(callback); + if (_nativeIndex !== null) _nativeIndex += 1; + } + } else { + if (!~_render.indexOf(callback)) { + _render.unshift(callback); + if (_renderIndex !== null) _renderIndex += 1; + } + } + }; + + /** + * Remove callback from render queue + * @name Render.stop + * @memberof Render + * + * @function + * @param {Function} callback + */ + this.stop = function(callback) { + let i = _render.indexOf(callback); + if (i >= 0) { + _render.splice(i, 1); + if (_renderIndex !== null && i <= _renderIndex) { + _renderIndex -= 1; + } + } + i = _native.indexOf(callback); + if (i >= 0) { + _native.splice(i, 1); + if (_nativeIndex !== null && i <= _nativeIndex) { + _nativeIndex -= 1; + } + } + }; + + /** + * Force render - for use in threads + * @name Render.tick + * @memberof Render + * + * @function + */ + this.tick = function() { + if (!THREAD) return; + this.TIME = performance.now(); + render(this.TIME); + }; + + /** + * Force render - for Vega frame by frame recording + * @name Render.tick + * @memberof Render + * + * @function + */ + this.forceRender = function(time) { + this.TIME = time; + render(this.TIME); + }; + + /** + * Distributed worker constuctor + * @name Render.Worker + * @memberof Render + + * @constructor + * @param {Function} _callback + * @param {Number} [_budget = 4] + * @example + * const worker = _this.initClass(Render.Worker, compute, 1); + * function compute() {console.log(Math.sqrt(Math.map(Math.sin(performance.now()))))}; + * _this.delayedCall(worker.stop, 1000) + * + */ + this.Worker = function(_callback, _budget = 4) { + Inherit(this, Component); + let _scope = this; + let _elapsed = 0; + this.startRender(loop); + function loop() { + if (_scope.dead) return; + while (_elapsed < _budget) { + if (_scope.dead || _scope.paused) return; + const start = performance.now(); + _callback && _callback(); + _elapsed += performance.now() - start; + } + _elapsed = 0; + } + + /** + * @name Render.stop + * @memberof Render + * + * @function + */ + this.stop = function() { + this.dead = true; + this.stopRender(loop); + //defer(_ => _scope.destroy()); + } + + /** + * @name Render.pause + * @memberof Render + * + * @function + */ + this.pause = function() { + this.paused = true; + this.stopRender(loop); + } + + /** + * @name Render.resume + * @memberof Render + * + * @function + */ + this.resume = function() { + this.paused = false; + this.startRender(loop); + } + + /** + * @name Render.setCallback + * @memberof Render + * + * @function + * @param cb + */ + this.setCallback = function(cb) { + _callback = cb; + } + }; + + /** + * Pause global render loop + * @name Render.pause + * @memberof Render + * + * @function + */ + this.pause = function() { + _this.isPaused = true; + }; + + /** + * Resume global render loop + * @name Render.resume + * @memberof Render + * + * @function + */ + this.resume = function() { + if (!_this.isPaused) return; + _this.isPaused = false; + rAF(render); + }; + + /** + * Use an alternative requestAnimationFrame function (for VR) + * @name Render.useRAF + * @param {Function} _callback + * @memberof Render + * + * @function + */ + this.useRAF = function(raf) { + _firstSample = null; + _last = performance.now(); + rAF = raf; + rAF(render); + } + + /** + * @name Render.onDrawFrame + * @memberof Render + * + * @function + * @param cb + */ + this.onDrawFrame = function(cb) { + _drawFrame.push(cb); + } + + /** + * @name Render.setTimeScale + * @memberof Render + * + * @function + * @param v + */ + this.setTimeScale = function(v) { + _this.timeScaleUniform.value = v; + } + + /** + * @name Render.getTimeScale + * @memberof Render + * + * @function + */ + this.getTimeScale = function() { + return _this.timeScaleUniform.value; + } + + /** + * @name Render.createTimeMultiplier + * @memberof Render + * + * @function + */ + /** + * @name Render.createTimeMultiplier + * @memberof Render + * + * @function + */ + this.createTimeMultiplier = function() { + let obj = {value: 1}; + _multipliers.push(obj); + return obj; + } + + /** + * @name Render.destroyTimeMultiplier + * @memberof Render + * + * @function + * @param obj + */ + this.destroyTimeMultiplier = function(obj) { + _multipliers.remove(obj); + } + + /** + * @name Render.tweenTimeScale + * @memberof Render + * + * @function + * @param value + * @param time + * @param ease + * @param delay + */ + this.tweenTimeScale = function(value, time, ease, delay) { + return tween(_this.timeScaleUniform, {value}, time, ease, delay, null, null, true); + } + + Object.defineProperty(_this, 'FRAME_HZ_MULTIPLIER', { + get() { + return (60 / (1000 / _this.DELTA)) * _refreshScale; + }, + enumerable: true + }); +}, 'Static'); + +/** + * Timer class that uses hydra Render loop, which has much less overhead than native setTimeout + * @name Timer + */ + +Class(function Timer() { + const _this = this; + const _callbacks = []; + const _discard = []; + const _deferA = []; + const _deferB = []; + var _defer = _deferA; + + (function() { + Render.start(loop); + })(); + + + function loop(t, delta) { + for (let i = _discard.length - 1; i >= 0; i--) { + let obj = _discard[i]; + obj.callback = null; + _callbacks.remove(obj); + } + if (_discard.length) _discard.length = 0; + + for (let i = _callbacks.length - 1; i >= 0; i--) { + let obj = _callbacks[i]; + if (!obj) { + _callbacks.remove(obj); + continue; + } + + if (obj.scaledTime) { + obj.current += delta; + } else { + obj.current += Render.DT; + } + + if (obj.current >= obj.time) { + obj.callback && obj.callback(); + _discard.push(obj); + } + } + + for (let i = _defer.length-1; i > -1; i--) { + _defer[i](); + } + _defer.length = 0; + _defer = _defer == _deferA ? _deferB : _deferA; + } + + function find(ref) { + for (let i = _callbacks.length - 1; i > -1; i--) if (_callbacks[i].ref == ref) return _callbacks[i]; + } + + //*** Event handlers + + //*** Public methods + + /** + * + * @private + * + * @param ref + * @returns {boolean} + */ + this.__clearTimeout = function(ref) { + const obj = find(ref); + if (!obj) return false; + obj.callback = null; + _callbacks.remove(obj); + return true; + }; + + /** + * Create timer + * @name Timer.create + * @memberof Timer + * + * @function + * @param {Function} callback + * @param {Number} time + * @returns {Number} Returns timer reference for use with window.clearTimeout + */ + this.create = function(callback, time, scaledTime) { + if (window._NODE_) return setTimeout(callback, time); + const obj = { + time: Math.max(1, time || 1), + current: 0, + ref: Utils.timestamp(), + callback, + scaledTime + }; + _callbacks.unshift(obj); + return obj.ref; + }; + + /** + * @name Timer.delayedCall + * @memberof Timer + * + * @function + * @param time + */ + this.delayedCall = function(time) { + let promise = Promise.create(); + _this.create(promise.resolve, time); + return promise; + } + + /** + * Defer callback until next frame + * @name window.defer + * @memberof Timer + * + * @function + * @param {Function} callback + */ + window.defer = this.defer = function(callback) { + let promise; + if (!callback) { + promise = Promise.create(); + callback = promise.resolve; + } + + let array = _defer == _deferA ? _deferB : _deferA; + array.unshift(callback); + return promise; + }; + +}, 'static'); +/** + * Events class + * @name Events + */ + +Class(function Events() { + const _this = this; + this.events = {}; + + const _e = {}; + const _linked = []; + let _emitter; + + /** + * Add event listener + * @name this.events.sub + * @memberof Events + * + * @function + * @param {Object} [obj] - Optional local object to listen upon, prevents event from going global + * @param {String} evt - Event string + * @param {Function} callback - Callback function + * @returns {Function} callback - Returns callback to be immediately triggered + * @example + * // Global event listener + * _this.events.sub(Events.RESIZE, resize); + * function resize(e) {}; + * @example + * // Local event listener + * _this.events.sub(_someClass, Events.COMPLETE, loaded); + * function loaded(e) {}; + * @example + * // Custom event + * MyClass.READY = 'my_class_ready'; + * _this.events.sub(MyClass.READY, ready); + * function ready(e) {}; + */ + this.events.sub = function(obj, evt, callback) { + if (typeof obj !== 'object') { + callback = evt; + evt = obj; + obj = null; + } + + if (!obj) { + Events.emitter._addEvent(evt, !!callback.resolve ? callback.resolve : callback, this); + return callback; + } + + let emitter = obj.events.emitter(); + emitter._addEvent(evt, !!callback.resolve ? callback.resolve : callback, this); + emitter._saveLink(this); + _linked.push(emitter); + + return callback; + }; + + this.events.wait = async function(obj, evt) { + const promise = Promise.create(); + const args = [obj, evt, (e) => { + _this.events.unsub(...args); + promise.resolve(e); + }]; + if (typeof obj !== 'object') { + args.splice(1, 1); + } + _this.events.sub(...args); + return promise; + }; + + /** + * Remove event listener + * @name this.events.unsub + * @memberof Events + * + * @function + * @param {Object} [obj] - Optional local object + * @param {String} evt - Event string + * @param {Function} callback - Callback function + * @example + * // Global event listener + * _this.events.unsub(Events.RESIZE, resize); + * @example + * // Local event listener + * _this.events.unsub(_someClass, Events.COMPLETE, loaded); + */ + this.events.unsub = function(obj, evt, callback) { + if (typeof obj !== 'object') { + callback = evt; + evt = obj; + obj = null; + } + + if (!obj) return Events.emitter._removeEvent(evt, !!callback.resolve ? callback.resolve : callback); + obj.events.emitter()._removeEvent(evt, !!callback.resolve ? callback.resolve : callback); + }; + + /** + * Fire event + * @name this.events.fire + * @memberof Events + * + * @function + * @param {String} evt - Event string + * @param {Object} [obj] - Optional passed data + * @param {Boolean} [isLocalOnly] - If true, prevents event from going global if no-one is listening locally + * @example + * // Passing data with event + * const data = {}; + * _this.events.fire(Events.COMPLETE, {data}); + * _this.events.sub(Events.COMPLETE, e => console.log(e.data); + * @example + * // Custom event + * MyClass.READY = 'my_class_ready'; + * _this.events.fire(MyClass.READY); + */ + this.events.fire = function(evt, obj, isLocalOnly) { + obj = obj || _e; + obj.target = this; + Events.emitter._check(evt); + if (_emitter && _emitter._fireEvent(evt, obj)) return; + if (isLocalOnly) return; + Events.emitter._fireEvent(evt, obj); + }; + + /** + * Bubble up local event - subscribes locally and re-emits immediately + * @name this.events.bubble + * @memberof Events + * + * @function + * @param {Object} obj - Local object + * @param {String} evt - Event string + * @example + * _this.events.bubble(_someClass, Events.COMPLETE); + */ + this.events.bubble = function(obj, evt) { + _this.events.sub(obj, evt, e => _this.events.fire(evt, e)); + }; + + /** + * Destroys all events and notifies listeners to remove reference + * @private + * @name this.events.destroy + * @memberof Events + * + * @function + * @returns {null} + */ + this.events.destroy = function() { + Events.emitter._destroyEvents(this); + if (_linked) _linked.forEach(emitter => emitter._destroyEvents(this)); + if (_emitter && _emitter.links) _emitter.links.forEach(obj => obj.events && obj.events._unlink(_emitter)); + return null; + }; + + /** + * Gets and creates local emitter if necessary + * @private + * @name this.events.emitter + * @memberof Events + * + * @function + * @returns {Emitter} + */ + this.events.emitter = function() { + if (!_emitter) _emitter = Events.emitter.createLocalEmitter(); + return _emitter; + }; + + /** + * Unlink reference of local emitter upon its destruction + * @private + * @name this.events._unlink + * @memberof Events + * + * @function + * @param {Emitter} emitter + */ + this.events._unlink = function(emitter) { + _linked.remove(emitter); + }; +}, () => { + + /** + * Global emitter + * @private + * @name Events.emitter + * @memberof Events + */ + Events.emitter = new Emitter(); + Events.broadcast = Events.emitter._fireEvent; + + Events.VISIBILITY = 'hydra_visibility'; + Events.HASH_UPDATE = 'hydra_hash_update'; + Events.COMPLETE = 'hydra_complete'; + Events.PROGRESS = 'hydra_progress'; + Events.CONNECTIVITY = 'hydra_connectivity'; + Events.UPDATE = 'hydra_update'; + Events.LOADED = 'hydra_loaded'; + Events.END = 'hydra_end'; + Events.FAIL = 'hydra_fail'; + Events.SELECT = 'hydra_select'; + Events.ERROR = 'hydra_error'; + Events.READY = 'hydra_ready'; + Events.RESIZE = 'hydra_resize'; + Events.CLICK = 'hydra_click'; + Events.HOVER = 'hydra_hover'; + Events.MESSAGE = 'hydra_message'; + Events.ORIENTATION = 'orientation'; + Events.BACKGROUND = 'background'; + Events.BACK = 'hydra_back'; + Events.PREVIOUS = 'hydra_previous'; + Events.NEXT = 'hydra_next'; + Events.RELOAD = 'hydra_reload'; + Events.UNLOAD = 'hydra_unload'; + Events.FULLSCREEN = 'hydra_fullscreen'; + + const _e = {}; + + function Emitter() { + const prototype = Emitter.prototype; + this.events = []; + + if (typeof prototype._check !== 'undefined') return; + prototype._check = function(evt) { + if (typeof evt == 'undefined') throw 'Undefined event'; + }; + + prototype._addEvent = function(evt, callback, object) { + this._check(evt); + this.events.push({evt, object, callback}); + }; + + prototype._removeEvent = function(eventString, callback) { + this._check(eventString); + + for (let i = this.events.length - 1; i >= 0; i--) { + if (this.events[i].evt === eventString && this.events[i].callback === callback) { + this._markForDeletion(i); + } + } + }; + + prototype._sweepEvents = function() { + for (let i = 0; i < this.events.length; i++) { + if (this.events[i].markedForDeletion) { + delete this.events[i].markedForDeletion; + this.events.splice(i, 1); + --i; + } + } + } + + prototype._markForDeletion = function(i) { + this.events[i].markedForDeletion = true; + if (this._sweepScheduled) return; + this._sweepScheduled = true; + defer(() => { + this._sweepScheduled = false; + this._sweepEvents(); + }); + } + + prototype._fireEvent = function(eventString, obj) { + if (this._check) this._check(eventString); + obj = obj || _e; + let called = false; + for (let i = 0; i < this.events.length; i++) { + let evt = this.events[i]; + if (evt.evt == eventString && !evt.markedForDeletion) { + evt.callback(obj); + called = true; + } + } + return called; + }; + + prototype._destroyEvents = function(object) { + for (var i = this.events.length - 1; i >= 0; i--) { + if (this.events[i].object === object) { + this._markForDeletion(i); + } + } + }; + + prototype._saveLink = function(obj) { + if (!this.links) this.links = []; + if (!~this.links.indexOf(obj)) this.links.push(obj); + }; + + prototype.createLocalEmitter = function() { + return new Emitter(); + }; + } + + // Global Events + Hydra.ready(() => { + + /** + * Visibility event handler + * @private + */ + (function() { + let _lastTime = performance.now(); + let _last; + + Timer.create(addVisibilityHandler, 250); + + function addVisibilityHandler() { + let hidden, eventName; + [ + ['msHidden', 'msvisibilitychange'], + ['webkitHidden', 'webkitvisibilitychange'], + ['hidden', 'visibilitychange'] + ].forEach(d => { + if (typeof document[d[0]] !== 'undefined') { + hidden = d[0]; + eventName = d[1]; + } + }); + + if (!eventName) { + const root = Device.browser == 'ie' ? document : window; + root.onfocus = onfocus; + root.onblur = onblur; + return; + } + + document.addEventListener(eventName, () => { + const time = performance.now(); + if (time - _lastTime > 10) { + if (document[hidden] === false) onfocus(); + else onblur(); + } + _lastTime = time; + }); + } + + function onfocus() { + Render.blurTime = -1; + if (_last != 'focus') Events.emitter._fireEvent(Events.VISIBILITY, {type: 'focus'}); + _last = 'focus'; + } + + function onblur() { + Render.blurTime = Date.now(); + if (_last != 'blur') Events.emitter._fireEvent(Events.VISIBILITY, {type: 'blur'}); + _last = 'blur'; + } + + window.addEventListener('online', _ => Events.emitter._fireEvent(Events.CONNECTIVITY, {online: true})); + window.addEventListener('offline', _ => Events.emitter._fireEvent(Events.CONNECTIVITY, {online: false})); + + window.onbeforeunload = _ => { + Events.emitter._fireEvent(Events.UNLOAD); + return null; + }; + })(); + + window.Stage = window.Stage || {}; + let box; + if (Device.system.browser == 'social' && Device.system.os == 'ios') { + box = document.createElement('div'); + box.style.position = 'fixed'; + box.style.top = box.style.left = box.style.right = box.style.bottom = '0px'; + box.style.zIndex = '-1'; + box.style.opacity = '0'; + box.style.pointerEvents = 'none'; + document.body.appendChild(box); + } + updateStage(); + + let iosResize = Device.system.os === 'ios'; + let html = iosResize ? document.querySelector('html') : false; + let delay = iosResize ? 500 : 16; + let timer; + + function handleResize() { + clearTimeout(timer); + timer = setTimeout(_ => { + updateStage(); + if ( html && Math.min( window.screen.width, window.screen.height ) !== Stage.height && !Mobile.isAllowNativeScroll ) { + html.scrollTop = -1; + } + Events.emitter._fireEvent(Events.RESIZE); + }, delay); + } + + window.addEventListener('resize', handleResize); + + window.onorientationchange = window.onresize; + + if (Device.system.browser == 'social' && (Stage.height >= screen.height || Stage.width >= screen.width)) { + setTimeout(updateStage, 1000); + } + + // Call initially + defer(window.onresize); + + function updateStage() { + if (box) { + let bbox = box.getBoundingClientRect(); + Stage.width = bbox.width || window.innerWidth || document.body.clientWidth || document.documentElement.offsetWidth; + Stage.height = bbox.height || window.innerHeight || document.body.clientHeight || document.documentElement.offsetHeight; + + document.body.parentElement.scrollTop = document.body.scrollTop = 0; + document.documentElement.style.width = document.body.style.width = `${Stage.width}px`; + document.documentElement.style.height = document.body.style.height = `${Stage.height}px`; + Events.emitter._fireEvent(Events.RESIZE); + } else { + Stage.width = window.innerWidth || document.body.clientWidth || document.documentElement.offsetWidth; + Stage.height = window.innerHeight || document.body.clientHeight || document.documentElement.offsetHeight; + } + } + }); +}); +/** + * Read-only class with device-specific information and exactly what's supported. + * Information split into: system, mobile, media, graphics, style, tween. + * @name Device + */ + +Class(function Device() { + var _this = this; + + /** + * Stores user agent as string + * @name Device.agent + * @memberof Device + */ + this.agent = navigator.userAgent.toLowerCase(); + + /** + * Checks user agent against match query + * @name Device.detect + * @memberof Device + * + * @function + * @param {String|String[]} match - Either string or array of strings to test against + * @returns {Boolean} + */ + this.detect = function(match) { + return this.agent.includes(match) + }; + + /** + * Boolean + * @name Device.touchCapable + * @memberof Device + */ + this.touchCapable = !!navigator.maxTouchPoints; + + /** + * Alias of window.devicePixelRatio + * @name Device.pixelRatio + * @memberof Device + */ + this.pixelRatio = window.devicePixelRatio; + + //==================================================================================// + //===// System //===================================================================// + + this.system = {}; + + /** + * Boolean. True if devicePixelRatio greater that 1.0 + * @name Device.system.retina + * @memberof Device + */ + this.system.retina = window.devicePixelRatio > 1; + + /** + * Boolean + * @name Device.system.webworker + * @memberof Device + */ + this.system.webworker = typeof window.Worker !== 'undefined'; + + + /** + * Boolean + * @name Device.system.geolocation + * @memberof Device + */ + if (!window._NODE_) this.system.geolocation = typeof navigator.geolocation !== 'undefined'; + + /** + * Boolean + * @name Device.system.pushstate + * @memberof Device + */ + if (!window._NODE_) this.system.pushstate = typeof window.history.pushState !== 'undefined'; + + /** + * Boolean + * @name Device.system.webcam + * @memberof Device + */ + this.system.webcam = !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.mediaDevices); + + /** + * String of user's navigator language + * @name Device.system.language + * @memberof Device + */ + this.system.language = window.navigator.userLanguage || window.navigator.language; + + /** + * Boolean + * @name Device.system.webaudio + * @memberof Device + */ + this.system.webaudio = typeof window.AudioContext !== 'undefined'; + + /** + * Boolean + * @name Device.system.xr + * @memberof Device + */ + this.system.xr = {}; + this.system.detectXR = async function() { + if (window.AURA) { + _this.system.xr.vr = true; + _this.system.xr.ar = true; + return; + } + + if (!navigator.xr) { + _this.system.xr.vr = false; + _this.system.xr.ar = false; + return; + } + + try { + [_this.system.xr.vr, _this.system.xr.ar] = await Promise.all([ + navigator.xr.isSessionSupported('immersive-vr'), + navigator.xr.isSessionSupported('immersive-ar') + ]); + } catch(e) { } + + if (_this.system.os == 'android') { + if (!_this.detect('oculus')) { + _this.system.xr.vr = false; + } + } + }; + + /** + * Boolean + * @name Device.system.localStorage + * @memberof Device + */ + try { + this.system.localStorage = typeof window.localStorage !== 'undefined'; + } catch (e) { + this.system.localStorage = false; + } + + /** + * Boolean + * @name Device.system.fullscreen + * @memberof Device + */ + this.system.fullscreen = document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled; + + function detectIpad() { + let aspect = Math.max(screen.width, screen.height) / Math.min(screen.width, screen.height); + // iPads getting bigger and bigger, 2022 iPad Pro is 1389x970. But the aspect + // ratio has stayed consistent: iPads are all 4:3, Macbooks are 16:10. + // Need to account for external displays too, but they're likely to be + // closer to 16:10 than 4:3 + return _this.detect('mac') && _this.touchCapable && Math.abs(aspect - 4/3) < Math.abs(aspect - 16/10); + } + + /** + * String of operating system. Returns 'ios', 'android', 'blackberry', 'mac', 'windows', 'linux' or 'unknown'. + * @name Device.system.os + * @memberof Device + */ + this.system.os = (function() { + if (_this.detect(['ipad', 'iphone', 'ios']) || detectIpad()) return 'ios'; + if (_this.detect(['android', 'kindle'])) return 'android'; + if (_this.detect(['blackberry'])) return 'blackberry'; + if (_this.detect(['mac os'])) return 'mac'; + if (_this.detect(['windows', 'iemobile'])) return 'windows'; + if (_this.detect(['linux'])) return 'linux'; + return 'unknown'; + })(); + + /** + * Mobile os version. Currently only applicable to mobile OS. + * @name Device.system.version + * @memberof Device + */ + this.system.version = (function() { + try { + if (_this.system.os == 'ios') { + if (_this.agent.includes('intel mac')) { + let num = _this.agent.split('version/')[1].split(' ')[0]; + let split = num.split('.'); + return Number(split[0] + '.' + split[1]); + } else { + var num = _this.agent.split('os ')[1].split('_'); + var main = num[0]; + var sub = num[1].split(' ')[0]; + return Number(main + '.' + sub); + } + } + if (_this.system.os == 'android') { + var version = _this.agent.split('android ')[1].split(';')[0]; + if (version.length > 3) version = version.slice(0, -2); + if (version.charAt(version.length-1) == '.') version = version.slice(0, -1); + return Number(version); + } + if (_this.system.os == 'windows') { + if (_this.agent.includes('rv:11')) return 11; + return Number(_this.agent.split('windows phone ')[1].split(';')[0]); + } + } catch(e) {} + return -1; + })(); + + /** + * String of browser. Returns, 'social, 'chrome', 'safari', 'firefox', 'ie', 'browser' (android), or 'unknown'. + * @name Device.system.browser + * @memberof Device + */ + this.system.browser = (function() { + if (_this.system.os == 'ios') { + if (_this.detect(['twitter', 'fbios', 'instagram'])) return 'social'; + if (_this.detect(['crios'])) return 'chrome'; + if (_this.detect(['fxios'])) return 'firefox'; + if (_this.detect(['safari'])) return 'safari'; + return 'unknown'; + } + if (_this.system.os == 'android') { + if (_this.detect(['twitter', 'fb', 'facebook', 'instagram'])) return 'social'; + if (_this.detect(['chrome'])) return 'chrome'; + if (_this.detect(['firefox'])) return 'firefox'; + return 'browser'; + } + if (_this.detect(['msie'])) return 'ie'; + if (_this.detect(['trident']) && _this.detect(['rv:'])) return 'ie'; + if (_this.detect(['windows']) && _this.detect(['edge'])) return 'ie'; + if (_this.detect(['chrome'])) return 'chrome'; + if (_this.detect(['safari'])) return 'safari'; + if (_this.detect(['firefox'])) return 'firefox'; + + // TODO: test windows phone and see what it returns + //if (_this.os == 'Windows') return 'ie'; + return 'unknown'; + })(); + + /** + * Number value of browser version + * @name Device.browser.browserVersion + * @memberof Device + */ + this.system.browserVersion = (function() { + try { + if (_this.system.browser == 'chrome') { + if (_this.detect('crios')) return Number(_this.agent.split('crios/')[1].split('.')[0]); + return Number(_this.agent.split('chrome/')[1].split('.')[0]); + } + if (_this.system.browser == 'firefox') return Number(_this.agent.split('firefox/')[1].split('.')[0]); + if (_this.system.browser == 'safari') return Number(_this.agent.split('version/')[1].split('.')[0].split('.')[0]); + if (_this.system.browser == 'ie') { + if (_this.detect(['msie'])) return Number(_this.agent.split('msie ')[1].split('.')[0]); + if (_this.detect(['rv:'])) return Number(_this.agent.split('rv:')[1].split('.')[0]); + return Number(_this.agent.split('edge/')[1].split('.')[0]); + } + } catch(e) { + return -1; + } + })(); + + //==================================================================================// + //===// Mobile //===================================================================// + + /** + * Object that only exists if device is mobile or tablet + * @name Device.mobile + * @memberof Device + */ + this.mobile = !window._NODE_ && (!!(('ontouchstart' in window) || ('onpointerdown' in window)) && _this.system.os.includes(['ios', 'android', 'magicleap'])) ? {} : false; + if (_this.detect('oculusbrowser')) this.mobile = true; + if (_this.detect('quest')) this.mobile = true; + if (this.mobile && this.detect(['windows']) && !this.detect(['touch'])) this.mobile = false; + if (this.mobile) { + + /** + * Boolean + * @name Device.mobile.tablet + * @memberof Device + */ + this.mobile.tablet = Math.max(window.screen ? screen.width : window.innerWidth, window.screen ? screen.height : window.innerHeight) > 1000; + + /** + * Boolean + * @name Device.mobile.phone + * @memberof Device + */ + this.mobile.phone = !this.mobile.tablet; + + /** + * Boolean + * @name Device.mobile.pwa + * @memberof Device + */ + this.mobile.pwa = (function() { + if (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) return true; + if (window.navigator.standalone) return true; + return false; + })(); + + /** + * Boolean. Only available after Hydra is ready + * @name Device.mobile.native + * @memberof Device + */ + Hydra.ready(() => { + _this.mobile.native = (function() { + if (Mobile.NativeCore && Mobile.NativeCore.active) return true; + if (window._AURA_) return true; + return false; + })(); + }); + } + + //=================================================================================// + //===// Media //===================================================================// + + this.media = {}; + + /** + * String for preferred audio format ('ogg' or 'mp3'), else false if unsupported + * @name Device.media.audio + * @memberof Device + */ + this.media.audio = (function() { + if (!!document.createElement('audio').canPlayType) { + return _this.detect(['firefox', 'opera']) ? 'ogg' : 'mp3'; + } else { + return false; + } + })(); + + /** + * String for preferred video format ('webm', 'mp4' or 'ogv'), else false if unsupported + * @name Device.media.video + * @memberof Device + */ + this.media.video = (function() { + var vid = document.createElement('video'); + if (!!vid.canPlayType) { + if (vid.canPlayType('video/webm;')) return 'webm'; + return 'mp4'; + } else { + return false; + } + })(); + + /** + * Boolean + * @name Device.media.webrtc + * @memberof Device + */ + this.media.webrtc = !!(window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection || window.oRTCPeerConnection || window.RTCPeerConnection); + + //====================================================================================// + //===// Graphics //===================================================================// + + this.graphics = {}; + + /** + * Object with WebGL-related information. False if WebGL unsupported. + * @name Device.graphics.webgl + * @memberof Device + * @example + * Device.graphics.webgl.renderer + * Device.graphics.webgl.version + * Device.graphics.webgl.glsl + * Device.graphics.webgl.extensions + * Device.graphics.webgl.gpu + * Device.graphics.webgl.extensions + */ + this.graphics.webgl = (function() { + + let DISABLED = false; + + Object.defineProperty(_this.graphics, 'webgl', { + get: () => { + if (DISABLED) return false; + + if (_this.graphics._webglContext) return _this.graphics._webglContext; + + try { + const names = ['webgl2', 'webgl', 'experimental-webgl']; + const canvas = document.createElement('canvas'); + let gl; + for (let i = 0; i < names.length; i++) { + if (names[i] === 'webgl2' && Utils.query('compat')) continue; + gl = canvas.getContext(names[i]); + if (gl) break; + } + + let output = { gpu: 'unknown' }; + output.renderer = gl.getParameter(gl.RENDERER).toLowerCase(); + output.version = gl.getParameter(gl.VERSION).toLowerCase(); + output.glsl = gl.getParameter(gl.SHADING_LANGUAGE_VERSION).toLowerCase(); + output.extensions = gl.getSupportedExtensions(); + output.webgl2 = output.version.includes(['webgl 2', 'webgl2']); + output.canvas = canvas; + output.context = gl; + + if (_this.system.browser === 'firefox' && _this.system.browserVersion >= 92) { + // WEBGL_debug_renderer_info deprecated in Firefox since 92, with + // “sanitized” gpu moved to the RENDERER parameter. See + // https://bugzil.la/1722782 and https://bugzil.la/1722113 + output.gpu = output.renderer; + } else { + let info = gl.getExtension('WEBGL_debug_renderer_info'); + if (info) { + let gpu = info.UNMASKED_RENDERER_WEBGL; + output.gpu = gl.getParameter(gpu).toLowerCase(); + } + } + + output.detect = function(matches) { + if (output.gpu && output.gpu.toLowerCase().includes(matches)) return true; + if (output.version && output.version.toLowerCase().includes(matches)) return true; + + for (let i = 0; i < output.extensions.length; i++) { + if (output.extensions[i].toLowerCase().includes(matches)) return true; + } + return false; + }; + + if (!output.webgl2 && !output.detect('instance') && !window.AURA) DISABLED = true; + + _this.graphics._webglContext = output; + return output; + } catch(e) { + return false; + } + }, + + set: v => { + if (v === false) DISABLED = true; + } + }); + })(); + + this.graphics.metal = (function() { + if (!window.Metal) return false; + let output = {}; + output.gpu = Metal.device.getName().toLowerCase(); + output.detect = function(matches) { + return output.gpu.includes(matches); + }; + return output; + })(); + + /** + * Abstraction of Device.graphics.webgl to handle different rendering backends + * + * @name Device.graphics.gpu + * @memberof Device + */ + this.graphics.gpu = (function() { + if (!_this.graphics.webgl && !_this.graphics.metal) return false; + let output = {}; + ['metal', 'webgl'].forEach(name => { + if (!!_this.graphics[name] && !output.identifier) { + output.detect = _this.graphics[name].detect; + output.identifier = _this.graphics[name].gpu; + } + }); + return output; + })(); + + /** + * Boolean + * @name Device.graphics.canvas + * @memberof Device + */ + this.graphics.canvas = (function() { + var canvas = document.createElement('canvas'); + return canvas.getContext ? true : false; + })(); + + //==================================================================================// + //===// Styles //===================================================================// + + const checkForStyle = (function() { + let _tagDiv; + return function (prop) { + _tagDiv = _tagDiv || document.createElement('div'); + const vendors = ['Khtml', 'ms', 'O', 'Moz', 'Webkit'] + if (prop in _tagDiv.style) return true; + prop = prop.replace(/^[a-z]/, val => {return val.toUpperCase()}); + for (let i = vendors.length - 1; i >= 0; i--) if (vendors[i] + prop in _tagDiv.style) return true; + return false; + } + })(); + + this.styles = {}; + + /** + * Boolean + * @name Device.styles.filter + * @memberof Device + */ + this.styles.filter = checkForStyle('filter'); + + /** + * Boolean + * @name Device.styles.blendMode + * @memberof Device + */ + this.styles.blendMode = checkForStyle('mix-blend-mode'); + + //=================================================================================// + //===// Tween //===================================================================// + + this.tween = {}; + + /** + * Boolean + * @name Device.tween.transition + * @memberof Device + */ + this.tween.transition = checkForStyle('transition'); + + /** + * Boolean + * @name Device.tween.css2d + * @memberof Device + */ + this.tween.css2d = checkForStyle('transform'); + + /** + * Boolean + * @name Device.tween.css3d + * @memberof Device + */ + this.tween.css3d = checkForStyle('perspective'); + + //==================================================================================// + //===// Social //===================================================================// + + /** + * Boolean + * @name Device.social + * @memberof Device + */ + this.social = (function() { + if (_this.agent.includes('instagram')) return 'instagram'; + if (_this.agent.includes('fban')) return 'facebook'; + if (_this.agent.includes('fbav')) return 'facebook'; + if (_this.agent.includes('fbios')) return 'facebook'; + if (_this.agent.includes('twitter')) return 'twitter'; + if (document.referrer && document.referrer.includes('//t.co/')) return 'twitter'; + return false; + })(); +}, 'Static'); + +/** + * Class structure tool-belt that cleans up after itself upon class destruction. + * @name Component + */ + +Class(function Component() { + Inherit(this, Events); + const _this = this; + const _setters = {}; + const _flags = {}; + const _timers = []; + const _loops = []; + var _onDestroy, _appStateBindings; + + this.classes = {}; + + function defineSetter(_this, prop) { + _setters[prop] = {}; + Object.defineProperty(_this, prop, { + set: function(v) { + if (_setters[prop] && _setters[prop].s) _setters[prop].s.call(_this, v); + v = null; + }, + + get: function() { + if (_setters[prop] && _setters[prop].g) return _setters[prop].g.apply(_this); + } + }); + } + + /** + * @name this.findParent + * @memberof Component + * + * @function + * @param type + */ + this.findParent = function(type) { + let p = _this.parent; + while (p) { + if (!p._cachedName) p._cachedName = Utils.getConstructorName(p); + if (p._cachedName == type) return p; + p = p.parent; + } + } + + /** + * Define setter for class property + * @name this.set + * @memberof Component + * + * @function + * @param {String} prop + * @param {Function} callback + */ + this.set = function(prop, callback) { + if (!_setters[prop]) defineSetter(this, prop); + _setters[prop].s = callback; + }; + + /** + * Define getter for class property + * @name this.get + * @memberof Component + * + * @function + * @param {String} prop + * @param {Function} callback + */ + this.get = function(prop, callback) { + if (!_setters[prop]) defineSetter(this, prop); + _setters[prop].g = callback; + }; + + /** + * Returns true if the current playground is set to this class + * @name this.set + * @memberof Component + * + * @function + */ + this.isPlayground = function(name) { + return Global.PLAYGROUND && Global.PLAYGROUND == (name || Utils.getConstructorName(_this)); + }; + + + /** + * Helper to initialise class and keep reference for automatic cleanup upon class destruction + * @name this.initClass + * @memberof Component + * + * @function + * @param {Function} clss - class to initialise + * @param {*} arguments - All additional arguments passed to class constructor + * @returns {Object} - Instanced child class + * @example + * Class(function BigButton(_color) { + * console.log(`${this.parent} made me ${_color}); //logs [parent object] made me red + * }); + * const bigButton _this.initClass(BigButton, 'red'); + */ + this.initClass = function(clss) { + if (!clss) { + console.trace(); + throw `unable to locate class`; + } + + const args = [].slice.call(arguments, 1); + const child = Object.create(clss.prototype); + child.parent = this; + child.__afterInitClass = []; + clss.apply(child, args); + + // Store reference if child is type Component + if (child.destroy) { + const id = Utils.timestamp(); + this.classes[id] = child; + this.classes[id].__id = id; + } + + // Automatically attach HydraObject elements + if (child.element) { + const last = arguments[arguments.length - 1]; + if (Array.isArray(last) && last.length == 1 && last[0] instanceof HydraObject) last[0].add(child.element); + else if (this.element && this.element.add && last !== null) this.element.add(child.element); + } + + // Automatically attach 3D groups + if (child.group) { + const last = arguments[arguments.length - 1]; + if (this.group && last !== null) this.group.add(child.group); + } + + child.__afterInitClass.forEach(callback => { + callback(); + }); + delete child.__afterInitClass; + + return child; + }; + + /** + * Create timer callback with automatic cleanup upon class destruction + * @name this.delayedCall + * @memberof Component + * + * @function + * @param {Function} callback + * @param {Number} time + * @param {*} [args] - any number of arguments can be passed to callback + */ + this.delayedCall = function(callback, time, scaledTime) { + const timer = Timer.create(() => { + if (!_this || !_this.destroy) return; + callback && callback(); + }, time, scaledTime); + + _timers.push(timer); + + // Limit in case dev using a very large amount of timers, so not to local reference + if (_timers.length > 50) _timers.shift(); + + return timer; + }; + + /** + * Clear all timers linked to this class + * @name this.clearTimers + * @memberof Component + * + * @function + */ + this.clearTimers = function() { + for (let i = _timers.length - 1; i >= 0; i--) clearTimeout(_timers[i]); + _timers.length = 0; + }; + + /** + * Start render loop. Stored for automatic cleanup upon class destruction + * @name this.startRender + * @memberof Component + * + * @function + * @param {Function} callback + * @param {Number} [fps] Limit loop rate to number of frames per second. eg Value of 1 will trigger callback once per second + */ + this.startRender = function(callback, fps, obj) { + if (typeof fps !== 'number') { + obj = fps; + fps = undefined; + } + + for (let i = 0; i < _loops.length; i++) { + if (_loops[i].callback == callback) return; + } + + let flagInvisible = _ => { + if (!_this._invisible) { + _this._invisible = true; + _this.onInvisible && _this.onInvisible(); + } + }; + + let loop = (a, b, c, d) => { + if (!_this.startRender) return false; + + let p = _this; + while (p) { + if (p.visible === false) return flagInvisible(); + if (p.group && p.group.visible === false) return flagInvisible(); + p = p.parent; + } + + if (_this._invisible !== false) { + _this._invisible = false; + _this.onVisible && _this.onVisible(); + } + + callback(a, b, c, d); + return true; + }; + _loops.push({callback, loop}); + + if (obj) { + if (obj == RenderManager.NATIVE_FRAMERATE) Render.start(loop, null, true); + else RenderManager.schedule(loop, obj); + } else { + Render.start(loop, fps); + } + }; + + /** + * Link up to the resize event + * @name this.resize + * @memberof Component + * + * @function + * @param {Function} callback + * @param {Boolean} callInitial + */ + this.onResize = function(callback, callInitial = true) { + if (callInitial) callback(); + + this.events.sub(Events.RESIZE, callback); + } + + /** + * Stop and clear render loop linked to callback + * @name this.stopRender + * @memberof Component + * + * @function + * @param {Function} callback + */ + this.stopRender = function(callback, obj) { + for (let i = 0; i < _loops.length; i++) { + if (_loops[i].callback == callback) { + + let loop = _loops[i].loop; + + if (obj) { + RenderManager.unschedule(loop, obj); + } + + Render.stop(loop); + _loops.splice(i, 1); + } + } + }; + + /** + * Clear all render loops linked to this class + * @name this.clearRenders + * @memberof Component + * + * @function + */ + this.clearRenders = function() { + for (let i = 0; i < _loops.length; i++) { + Render.stop(_loops[i].loop); + } + + _loops.length = 0; + }; + + /** + * Get callback when object key exists. Uses internal render loop so automatically cleaned up. + * @name this.wait + * @memberof Component + * + * @function + * @param {Object} object + * @param {String} key + * @param {Function} [callback] - Optional callback + * @example + * // Using promise syntax + * this.wait(this, 'loaded').then(() => console.log('LOADED')); + * @example + * // Omitting object defaults to checking for a property on `this` + * await this.wait('loaded'); console.log('LOADED'); + * @example + * // Waiting for a property to flip from truthy to falsy + * await this.wait('!busy'); console.log('ready'); + * @example + * // Using callback + * this.wait(this, 'loaded', () => console.log('LOADED')); + * @example + * // Using custom condition + * await this.wait(() => _count > 3); console.log('done'); + * @example + * // Wait for a number of milliseconds + * await this.wait(500); console.log('timeout'); + */ + this.wait = function(object, key, callback) { + const promise = Promise.create(); + let condition; + + if (typeof object === 'string') { + callback = key; + key = object; + object = _this; + } + + if (typeof object === 'number' && arguments.length === 1) { + _this.delayedCall(promise.resolve, object); + return promise; + } + + if (typeof object === 'function' && arguments.length === 1) { + condition = object; + object = _this; + } + + // To catch old format of first param being callback + if (typeof object == 'function' && typeof callback === 'string') { + let _object = object; + object = key; + key = callback; + callback = _object; + } + + callback = callback || promise.resolve; + + if (!condition) { + if (key?.charAt?.(0) === '!') { + key = key.slice(1); + condition = () => !(object[key] || (typeof object.flag === 'function' && object.flag(key))); + } else { + condition = () => !!object[key] || !!(typeof object.flag === 'function' && object.flag(key)); + } + } + + if (condition()) { + callback(); + } else { + Render.start(test); + + function test() { + if (!object || !_this.flag || object.destroy === null) return Render.stop(test); + if (condition()) { + callback(); + Render.stop(test); + } + } + } + + return promise; + }; + + /** + * Bind to an AppState to get your binding automatically cleaned up on destroy + * @name this.bindState + * @memberof Component + * + * @function + * @param {AppState} AppState + * @param {String} [key] Key name + * @param {Any} [rest] - Callback or otherwise second parameter to pass to AppState.bind + */ + this.bindState = async function(appState, key, ...rest) { + if (!!appState.then) appState = await appState; + if (!_appStateBindings) _appStateBindings = []; + // if(!(appState._bind || appState.bind)) console.log(appState) + let fn = (appState._bind || appState.bind).bind(appState); + let binding = fn(key, ...rest); + _appStateBindings.push(binding); + + binding._bindOnDestroy(() => { + _appStateBindings.remove(binding); + }); + + return binding; + } + + /** + * Set or get boolean + * @name this.flag + * @memberof Component + * + * @function + * @param {String} name + * @param {Boolean} [value] if no value passed in, current value returned + * @param {Number} [time] - Optional delay before toggling the value to the opposite of its current value + * @returns {*} Returns with current value if no value passed in + */ + this.flag = function(name, value, time) { + if (typeof value !== 'undefined') { + _flags[name] = value; + + if (time) { + clearTimeout(_flags[name+'_timer']); + _flags[name+'_timer'] = this.delayedCall(() => { + _flags[name] = !_flags[name]; + }, time); + } + } else { + return _flags[name]; + } + }; + + /** + * Destroy class and all of its attachments: events, timers, render loops, children. + * @name this.destroy + * @memberof Component + * + * @function + */ + this.destroy = function() { + if (this.removeDispatch) this.removeDispatch(); + if (this.onDestroy) this.onDestroy(); + if (this.fxDestroy) this.fxDestroy(); + if (_onDestroy) _onDestroy.forEach(cb => cb()); + + for (let id in this.classes) { + var clss = this.classes[id]; + if (clss && clss.destroy) clss.destroy(); + } + this.classes = null; + + this.clearRenders && this.clearRenders(); + this.clearTimers && this.clearTimers(); + if (this.element && window.GLUI && this.element instanceof GLUIObject) this.element.remove(); + + if (this.events) this.events = this.events.destroy(); + if (this.parent && this.parent.__destroyChild) this.parent.__destroyChild(this.__id); + + if (_appStateBindings) { + while (_appStateBindings.length > 0) { + // destroying removes itself from the _appStateBindings array + // so keep looking until there are none left, + // since the indexes of _appStateBindings are changing + _appStateBindings[_appStateBindings.length - 1].destroy?.(); + } + } + + return Utils.nullObject(this); + }; + + this._bindOnDestroy = function(cb) { + if (!_onDestroy) _onDestroy = []; + _onDestroy.push(cb); + } + + this.__destroyChild = function(name) { + delete this.classes[name]; + }; + + this.navigate = function(route) { + let p = _this.parent; + while (p) { + if (p.navigate) p.navigate(route); + p = p.parent; + } + } + +}); + +/** + * Class structure tool-belt that helps with loading and storing data. + * @name Model + */ + +Class(function Model() { + Inherit(this, Component); + Namespace(this); + + const _this = this; + const _storage = {}; + const _requests = {}; + let _data = 0; + let _triggered = 0; + + /** + * @name this.push + * @memberof Model + * + * @function + * @param {String} name + * @param {*} val + */ + this.push = function(name, val) { + _storage[name] = val; + }; + + /** + * @name this.pull + * @memberof Model + * + * @function + * @param {String} name + * @returns {*} + */ + this.pull = function(name) { + return _storage[name]; + }; + + /** + * @name this.promiseData + * @memberof Model + * + * @function + * @param {Number} [num = 1] + */ + this.waitForData = this.promiseData = function(num = 1) { + _data += num; + }; + + /** + * @name this.resolveData + * @memberof Model + * + * @function + */ + this.fulfillData = this.resolveData = function() { + _triggered++; + if (_triggered == _data) { + _this.dataReady = true; + } + }; + + /** + * @name this.ready + * @memberof Model + * + * @function + * @param {Function} [callback] + * @returns {Promise} + */ + this.ready = function(callback) { + let promise = Promise.create(); + if (callback) promise.then(callback); + _this.wait(_this, 'dataReady').then(promise.resolve); + return promise; + }; + + /** + * Calls init() on object member is exists, and then on self once completed. + * @name this.initWithData + * @memberof Model + * + * @function + * @param {Object} data + */ + this.initWithData = function(data) { + _this.STATIC_DATA = data; + + for (var key in _this) { + var model = _this[key]; + var init = false; + + for (var i in data) { + if (i.toLowerCase().replace(/-/g, "") == key.toLowerCase()) { + init = true; + if (model.init) model.init(data[i]); + } + } + + if (!init && model.init) model.init(); + } + + _this.init && _this.init(data); + }; + + /** + * Loads url with salt, then calls initWithData on object received + * @name this.loadData + * @memberof Model + * + * @function + * @param {String} url + * @param {Function} [callback] + * @returns {Promise} + */ + this.loadData = function(url, callback) { + let promise = Promise.create(); + if (!callback) callback = promise.resolve; + + var _this = this; + get(url + '?' + Utils.timestamp()).then( d => { + defer(() => { + _this.initWithData(d); + callback(d); + }); + }); + + return promise; + }; + + /** + * @name this.handleRequest + * @memberof Model + * + * @function + * @param {String} type + * @param {Function} [callback] + */ + this.handleRequest = function(type, callback) { + _requests[type] = callback; + } + + /** + * @name this.makeRequest + * @memberof Model + * + * @function + * @param {String} type + * @param {Object} data? + * @param {Object} mockData? + * @returns {Promise} + */ + this.makeRequest = async function(type, data, mockData = {}) { + if (!_requests[type]) { + console.warn(`Missing data handler for ${type} with mockData`, mockData); + return Array.isArray(mockData) ? new StateArray(mockData) : AppState.createLocal(mockData); + } + let result = await _requests[type](data, mockData); + if (!(result instanceof StateArray) && !result.createLocal) throw `makeRequest ${type} must return either an AppState or StateArray`; + return result; + } + + + this.request = async function(type, data, mockData) { + if (typeof data === 'function') { + mockData = data; + data = null; + } + + if (mockData) mockData = mockData(); + + if (!_requests[type]) { + // console.warn(`Missing data handler for ${type} with mockData`, mockData); + return Array.isArray(mockData) ? new StateArray(mockData) : AppState.createLocal(mockData); + } + let result = await _requests[type](data, mockData); + if (Array.isArray(result)) result = new StateArray(result); + else if (typeof result === 'object') result = AppState.createLocal(result); + + if (!(result instanceof StateArray) && !result.createLocal) throw `makeRequest ${type} must return either an AppState or StateArray`; + return result; + } + +}); + +Class(function Data() { + Inherit(this, Model); + const _this = this; +}, 'static'); +/** + * @name Modules + */ + +Class(function Modules() { + const _modules = {}; + const _constructors = {}; + + //*** Constructor + (function () { + defer(exec); + })(); + + function exec() { + for (let m in _modules) { + for (let key in _modules[m]) { + let module = _modules[m][key]; + if (module._ready) continue; + module._ready = true; + if (module.exec) module.exec(); + } + } + } + + function requireModule(root, path) { + let module = _modules[root]; + if (!module) throw `Module ${root} not found`; + module = module[path]; + + if (!module._ready) { + module._ready = true; + if (module.exec) module.exec(); + } + + return module; + } + + //*** Public methods + + /** + * @name window.Module + * @memberof Modules + * + * @function + * @param {Constructor} module + */ + this.Module = function(module) { + let m = new module(); + + let name = module.toString().slice(0, 100).match(/function ([^\(]+)/); + + if (name) { + m._ready = true; + name = name[1]; + _modules[name] = {index: m}; + _constructors[name] = module; + } else { + if (!_modules[m.module]) _modules[m.module] = {}; + _modules[m.module][m.path] = m; + } + }; + + /** + * @name window.require + * @memberof Modules + * + * @function + * @param {String} path + * @returns {*} + */ + this.require = function(path) { + let root; + if (!path.includes('/')) { + root = path; + path = 'index'; + } else { + root = path.split('/')[0]; + path = path.replace(root+'/', ''); + } + + return requireModule(root, path).exports; + }; + + this.getConstructor = function(name) { + return _constructors[name]; + } + + this.modulesReady = async function () { + let modules = [...arguments].flat(); + await Promise.all( modules.map( name => Modules.moduleReady( name ))); + } + + this.moduleReady = function (name) { + let promise = Promise.create(); + let check = function () { + if ( !_modules[name]) return; + Render.stop( check ); + promise.resolve(); + } + Render.start( check ); + return promise; + } + + window.Module = this.Module; + + if (!window._NODE_) { + window.requireNative = window.require; + window.require = this.require; + } +}, 'Static'); + +Class(function StateWrapper(_array) { + const _this = this; + Inherit(this, Component); + + this.bind = this.listen = function(key, callback) { + _array.forEach(async obj => { + await obj.wait('__ready'); + _this.bindState(obj.state, key, data => { + callback({target: obj, data}); + }); + }); + } + +}); +Class(function StateInitializer(Class, _ref, _params, _stateRef) { + Inherit(this, Component); + const _this = this; + + this.ref = _ref; + + //*** Constructor + (function () { + if (!_stateRef.init) throw `StateInitializer required init parameter`; + if (_stateRef.init) _this.bindState(AppState, _stateRef.init, onInit); + if (_stateRef.init3d) _this.bindState(AppState, _stateRef.init3d, onInit3D); + })(); + + function onInit(bool) { + if (bool) { + _this.parent[_ref] = _this.parent.initClass(Class, _params); + } else { + _this.parent[_ref] = _this.parent[_ref].destroy(); + } + } + + async function onInit3D() { + let ref = _this.parent[_ref]; + await _this.wait(_ => ref); + let next = await Initializer3D.queue(); + await Initializer3D.uploadAllAsync(ref.layout || ref.scene || ref.group); + next(); + } + + //*** Event handlers + + //*** Public methods + this.force = function() { + AppState.set(_stateRef.init, true); + } +}); +Class(function FragUIHelper(_obj, _root) { + Inherit(this, Component); + const _this = this; + + if (!_obj.addTo) _obj.addTo = "$element"; + if (_root) applyValues(_root, _this.parent.element); + create(_obj); + + function isLowerCase(str) { + return str == str.toLowerCase(); + } + + function findStateObject(text) { + return text.match(/\$(.*)\./)[1]; + } + + function getPropByString(obj, propString) { + if (!propString) + return obj; + + var prop, props = propString.split('.'); + + for (var i = 0, iLen = props.length - 1; i < iLen; i++) { + prop = props[i]; + + var candidate = obj[prop]; + if (candidate !== undefined) { + obj = candidate; + } else { + break; + } + } + return obj[props[i]]; + } + + function parseTextBindings(text) { + let binds = []; + while (text.match(/\$(.*)\./)) { + let match = text.match(/\$(.*)\./); + let split = text.split(match[0]); + split[0] = split[0] + '@['; + split[1] = split[1].split(' '); + let name = split[1][0]; + split[1][0] += ']'; + split[1] = split[1].join(' '); + text = split.join(''); + binds.push(name); + } + return [binds, text]; + } + + function parseTextGlobalBindings(text) { + let binds = []; + while (text.match(/\$(\w*)\/(\w*)/)) { + let match = text.match(/\$(\w*)\/(\w*)/); + let split = text.split(match[0]); + split[0] = split[0] + '@['; + split[1] = split[1].split(' '); + let name = match[0].slice(1).trim(); + split[1][0] = name; + split[1][0] += ']'; + split[1] = split[1].join(' '); + text = split.join(''); + binds.push(name); + } + return [binds, text]; + } + + function doConstructor(obj) { + switch (obj._type) { + case 'UI': + return _this.parent.element; + break; + + case 'GLObject': + case 'glObject': + case 'glObj': + if (obj.width && obj.height && obj.bg) { + let tempObj = $gl(Number(obj.width), Number(obj.height), obj.bg); + + // calling GLObject.bg() in applyValues() removes the custom shader. + // didn't happen before because we weren't waiting for parent init before calling + delete obj.bg; + + return tempObj; + } else { + return $gl(); + } + break; + + case 'GLText': + case 'glText': + if (obj._innerText.match?.(/\$(.*)\./)) { + let $text = $glText(obj._innerText, obj.font, Number(obj.fontSize), { color: obj.fontColor }); + let state = findStateObject(obj._innerText); + let ref = state; + if (ref.includes('.')) { + let split = state.split('.'); + ref = split[0]; + split.shift(); + state = split.join('.'); + } + _this.wait(_this.parent, ref).then(_ => { + let [binds, text] = parseTextBindings(obj._innerText); + $obj.text(text); + _this.parent.bindState(ref == state ? _this.parent[ref] : getPropByString(_this.parent[ref], state), binds, $obj); + }); + return $text; + } else if (obj._innerText.match?.(/\$(\w*)\/(\w*)/)) { + let [binds, text] = parseTextGlobalBindings(obj._innerText); + let $text = $glText(text, obj.font, Number(obj.fontSize), { color: obj.fontColor }); + _this.parent.bindState(AppState, binds, $text); + return $text; + } else { + return $glText(obj._innerText, obj.font, Number(obj.fontSize), { color: obj.fontColor }); + } + break; + + default: + let $obj = $(obj.className || obj.refName || 'h', obj._type != 'HydraObject' ? obj._type : 'div'); + if (obj.width && obj.height) $obj.size(obj.width, obj.height); + if (obj.font) $obj.fontStyle(obj.font, Number(obj.fontSize), obj.fontColor); + if (obj._innerText) { + if (obj._innerText.match?.(/\$(.*)\./)) { + let state = findStateObject(obj._innerText); + let ref = state; + if (ref.includes('.')) { + let split = state.split('.'); + ref = split[0]; + split.shift(); + state = split.join('.'); + } + _this.wait(_this.parent, ref).then(_ => { + let [binds, text] = parseTextBindings(obj._innerText); + $obj.text(text); + _this.parent.bindState(ref == state ? _this.parent[ref] : getPropByString(_this.parent[ref], state), binds, $obj); + }); + } else if (obj._innerText.match?.(/\$(\w*)\/(\w*)/)) { + let [binds, text] = parseTextGlobalBindings(obj._innerText); + $obj.text(text); + _this.parent.bindState(AppState, binds, $obj); + } else { + $obj.text(obj._innerText); + } + } + return $obj; + break; + } + } + + function applyValues(obj, $obj) { + const callObjKeyVal = (key) => { + return new Promise(resolve => { + const applyValue = (val) => { + if (typeof $obj[key] === 'function') { + $obj[key](val); + } else { + $obj[key] = val; + } + }; + const callFn = async () => { + let val = isNaN(obj[key]) ? obj[key] : Number(obj[key]); + if (typeof val === 'string') { + if (val.match(/\$(.*)\./)) { + let stateStr = findStateObject(val); + let state = _this.parent[stateStr]; + if (!state) { + await _this.wait(_this.parent, stateStr); + state = _this.parent[stateStr]; + } + if (!!state.then) state = await state; + let [binds] = parseTextBindings(val); + return _this.parent.bindState(state, binds, (dataVal) => applyValue(dataVal)); + } else if (val.match(/\$(\w*)\/(\w*)/)) { + let [binds] = parseTextGlobalBindings(val); + return _this.parent.bindState(AppState, binds, (dataVal) => applyValue(dataVal)); + } + + if (val.startsWith('$') && val != '$element') { + return applyValue(_this.parent[val.slice(1)]); + } + } + + applyValue(val); + }; + if (_this.parent.__afterInitClass) { + return _this.parent.__afterInitClass.push(() => resolve(callFn())); + } + resolve(callFn()); + }); + }; + + for (let key in obj) { + if (key == 'shader') { + let shader = _this.initClass(Shader, obj[key], { + tMap: { value: null } + }); + if (window[shader.vsName]) window[shader.vsName]({}, shader); + $obj.useShader(shader); + // ShaderUIL.add(shader); + } + + if (obj.width && obj.height && $obj.size) { + $obj.size(!isNaN(obj.width) ? Number(obj.width) : obj.width, + !isNaN(obj.height) ? Number(obj.height) : obj.height); + } + + if (key == 'css' || key == 'transform') { + let data = {}; + obj[key].split(',').forEach(param => { + let [a, b] = param.split(':'); + a = a.trim(); + b = b.trim(); + if (!isNaN(b)) b = Number(b); + data[a] = b; + }); + $obj[key](data); + } else if (key == 'onClick' || key == 'onHover') { + _this.wait(_ => !!_this.parent[obj[key].slice(1)]).then(_ => { + const interactHandle = _this.parent[obj[key].slice(1)] + let hoverFn = key === 'onHover' ? interactHandle : null; + let clickFn = key === 'onClick' ? interactHandle : null; + $obj.interact(hoverFn, clickFn, obj["seoLink"], obj["seoText"]); + }) + } else { + if (typeof $obj[key] === 'function') { + if (key == 'size') { + let size = obj.size.split(','); + size.map(x => Number(x)); + $obj.size(size[0], size[1]); + continue; + } + if (obj[key] === 1) obj[key] = undefined; + callObjKeyVal(key); + } else { + callObjKeyVal(key); + } + } + + } + } + + function convertToUsableRef(str) { + if (str.startsWith('$')) str = str.slice(1); + let ref = str; + let state = str; + if (str.includes('.')) { + let split = str.split('.'); + state = split[0]; + split.shift(); + ref = split.join('.'); + } + + return [state, ref]; + } + + async function create(obj, parent) { + if (!isLowerCase(obj._type) && obj._type != 'GLObject' && obj._type != 'HydraObject' && obj._type != 'GLText' && obj._type != 'UI') { + let params = {}; + for (let key in obj) { + if (key == '_type' || key == 'refName' || key == 'children' || key == 'display') continue; + params[key] = obj[key]; + + if (params[key].match?.(/\$(.*)\./)) { + await defer(); + let [state, ref] = convertToUsableRef(params[key]); + params[key] = state == ref ? + _this.parent[state] : + _this.parent[ref] ? + _this.parent[ref] : + getPropByString(_this.parent[state], ref); + } else if (params[key].startsWith('$')) { + await defer(); + params[key] = _this.parent[params[key].slice(1)]; + } + } + + if (obj._type == 'ViewState') { + params.__parent = parent; + } + _this.parent[obj.refName] = _this.parent.initClass(window[obj._type], AppState.createLocal(params), [parent]); + return; + } + + let $obj = doConstructor(obj); + if (obj.addTo) { + let addTo = obj.addTo.includes('.') ? eval(obj.addTo) : _this.parent.element; + addTo.add($obj); + } else { + if (parent) parent.add($obj); + } + + applyValues(obj, $obj); + + $obj.transform?.(); + + if (obj.refName) { + _this.parent[obj.refName] = $obj; + } + + obj.children.forEach(o => create(o, $obj)); + } + +}); +/** + * @name LinkedList + * + * @constructor + */ + +Class(function LinkedList() { + var prototype = LinkedList.prototype; + + /** + * @name length + * @memberof LinkedList + */ + this.length = 0; + this.first = null; + this.last = null; + this.current = null; + this.prev = null; + + if (typeof prototype.push !== 'undefined') return; + + /** + * @name push + * @memberof LinkedList + * + * @function + * @param {*} obj + */ + prototype.push = function(obj) { + if (!this.first) { + this.first = obj; + this.last = obj; + obj.__prev = obj; + obj.__next = obj; + } else { + obj.__next = this.first; + obj.__prev = this.last; + this.last.__next = obj; + this.last = obj; + } + + this.length++; + }; + + /** + * @name remove + * @memberof LinkedList + * + * @function + * @param {*} obj + */ + prototype.remove = function(obj) { + if (!obj || !obj.__next) return; + + if (this.length <= 1) { + this.empty(); + } else { + if (obj == this.first) { + this.first = obj.__next; + this.last.__next = this.first; + this.first.__prev = this.last; + } else if (obj == this.last) { + this.last = obj.__prev; + this.last.__next = this.first; + this.first.__prev = this.last; + } else { + obj.__prev.__next = obj.__next; + obj.__next.__prev = obj.__prev; + } + + this.length--; + } + + obj.__prev = null; + obj.__next = null; + }; + + /** + * @name empty + * @memberof LinkedList + * + * @function + */ + prototype.empty = function() { + this.first = null; + this.last = null; + this.current = null; + this.prev = null; + this.length = 0; + }; + + /** + * @name start + * @memberof LinkedList + * + * @function + * @return {*} + */ + prototype.start = function() { + this.current = this.first; + this.prev = this.current; + return this.current; + }; + + /** + * @name next + * @memberof LinkedList + * + * @function + * @return {*} + */ + prototype.next = function() { + if (!this.current) return; + this.current = this.current.__next; + if (this.length == 1 || this.prev.__next == this.first) return; + this.prev = this.current; + return this.current; + }; + + /** + * @name destroy + * @memberof LinkedList + * + * @function + * @returns {Null} + */ + prototype.destroy = function() { + Utils.nullObject(this); + return null; + }; + +}); +/** + * @name ObjectPool + * + * @constructor + * @param {Constructor} [_type] + * @param {Number} [_number = 10] - Only applied if _type argument exists + */ + +Class(function ObjectPool(_type, _number = 10) { + var _pool = []; + + /** + * Pool array + * @name array + * @memberof ObjectPool + */ + this.array = _pool; + + //*** Constructor + (function() { + if (_type) for (var i = 0; i < _number; i++) _pool.push(new _type()); + })(); + + //*** Public Methods + + /** + * Retrieve next object from pool + * @name get + * @memberof ObjectPool + * + * @function + * @returns {ArrayElement|null} + */ + this.get = function() { + return _pool.shift() || (_type ? new _type() : null); + }; + + /** + * Empties pool array + * @name empty + * @memberof ObjectPool + * + * @function + */ + this.empty = function() { + _pool.length = 0; + }; + + /** + * Place object into pool + * @name put + * @memberof ObjectPool + * + * @function + * @param {Object} obj + */ + this.put = function(obj) { + if (obj && !_pool.includes(obj)) _pool.push(obj); + }; + + /** + * Insert array elements into pool + * @name insert + * @memberof ObjectPool + * + * @function + * @param {Array} array + */ + this.insert = function(array) { + if (typeof array.push === 'undefined') array = [array]; + for (var i = 0; i < array.length; i++) this.put(array[i]); + }; + + /** + * Retrieve pool length + * @name length + * @memberof ObjectPool + * + * @function + * @returns {Number} + */ + this.length = function() { + return _pool.length; + }; + + /** + * Randomize pool + * @memberof ObjectPool + * + * @function + */ + this.randomize = function() { + let array = _pool; + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + } + + /** + * Calls destroy method on all members if exists, then removes reference. + * @name destroy + * @memberof ObjectPool + * + * @function + * @returns {null} + */ + this.destroy = function() { + for (let i = _pool.length - 1; i >= 0; i--) if (_pool[i].destroy) _pool[i].destroy(); + return _pool = null; + }; +}); + +/** + * @name Gate + * + * @constructor + */ + + +Class(function Gate() { + var _this = this; + + var _list = []; + var _map = {}; + + //*** Event handlers + + //*** Public methods + /** + * @name this.create + * @memberof Gate + * + * @function + * @param name + */ + this.create = function(name) { + let promise = Promise.create(); + if (name) _map[name] = promise; + else _list.push(promise); + } + + /** + * @name this.open + * @memberof Gate + * + * @function + * @param name + */ + this.open = function(name) { + if (name) { + if (!_map[name]) _map[name] = Promise.create(); + _map[name].resolve(); + } + + let promise = _list.shift(); + if (promise) promise.resolve(); + } + + /** + * @name this.wait + * @memberof Gate + * + * @function + * @param name + */ + this.wait = function(name) { + if (!_list.length && !name) return Promise.resolve(); + + if (name) { + if (!_map[name]) _map[name] = Promise.create(); + return _map[name]; + } + + return _list[_list.length-1] || Promise.resolve(); + } +}, 'static'); +/** + * @name Assets + */ + +Class(function Assets() { + const _this = this; + const _fetchCors = {mode: 'cors'}; + + this.__loaded = []; + + /** + * Flip bitmap images when decoding. + * @name Assets.FLIPY + * @memberof Assets + * @example + * Assets.FLIPY = false // do not flip when decoding + */ + this.FLIPY = true; + + /** + * Path for Content Distribution Network (eg. Amazon bucket) + * @name Assets.CDN + * @memberof Assets + * @example + * Assets.CDN = '//amazonbucket.com/project/'; + */ + this.CDN = ''; + + /** + * Cross Origin string to apply to images + * @name Assets.CORS + * @memberof Assets + * @example + * Assets.CORS = ''; + */ + this.CORS = 'anonymous'; + + /** + * Storage for all images loaded for easy access + * @name Assets.IMAGES + * @memberof Assets + */ + this.IMAGES = {}; + + /** + * Storage for all videos loaded for easy access + * @name Assets.VIDEOS + * @memberof Assets + */ + this.VIDEOS = {}; + + /** + * Storage for all audios loaded for easy access + * @name Assets.AUDIOS + * @memberof Assets + */ + this.AUDIOS = {}; + + /** + * Storage for all sdf font files loaded for easy access + * @name Assets.SDF + * @memberof Assets + */ + this.SDF = {}; + + /** + * Storage for all JSON files loaded for easy access. Always clones object when retrieved. + * @name Assets.JSON + * @memberof Assets + */ + this.JSON = { + push: function(prop, value) { + this[prop] = value; + Object.defineProperty(this, prop, { + get: () => {return JSON.parse(JSON.stringify(value))}, + }); + } + }; + + Object.defineProperty(this.JSON, 'push', { + enumerable: false, + writable: true + }); + + /** + * Storage for all SVG files loaded for easy access + * @name Assets.SVG + * @memberof Assets + */ + this.SVG = {}; + + /** + * Returns pixel-ratio-appropriate version if exists + * @private + * @param path + * @returns {String} + */ + function parseResolution(path) { + if (!window.ASSETS || !ASSETS.RES) return path; + var res = ASSETS.RES[path]; + var ratio = Math.min(Device.pixelRatio, 3); + if (!res) return path; + if (!res['x' + ratio]) return path; + var split = path.split('/'); + var file = split[split.length-1]; + split = file.split('.'); + return path.replace(file, split[0] + '-' + ratio + 'x.' + split[1]); + } + + /** + * Array extension for manipulating list of assets + * @private + * @param {Array} arr + * @returns {AssetList} + * @constructor + */ + function AssetList(arr) { + arr.__proto__ = AssetList.prototype; + return arr; + } + AssetList.prototype = new Array; + + /** + * Filter asset list to only include those matching the arguments + * @param {String|String[]} items + */ + AssetList.prototype.filter = function(items) { + for (let i = this.length - 1; i >= 0; i--) if (!this[i].includes(items)) this.splice(i, 1); + return this; + }; + + /** + * Filter asset list to exclude those matching the arguments + * @param {String|String[]} items + */ + AssetList.prototype.exclude = function(items) { + for (let i = this.length - 1; i >= 0; i--) if (this[i].includes(items)) this.splice(i, 1); + return this; + }; + + AssetList.prototype.prepend = function(prefix) { + for (let i = this.length - 1; i >= 0; i--) this[i] = prefix + this[i]; + return this; + }; + + AssetList.prototype.append = function(suffix) { + for (let i = this.length - 1; i >= 0; i--) this[i] = this[i] + suffix; + return this; + }; + + /** + * Get compiled list of assets + * @name Assets.list + * @memberof Assets + * + * @function + * @returns {AssetList} + * @example + * const assets = Assets.list(); + * assets.filter(['images', 'geometry']); + * assets.exclude('mobile'); + * assets.append('?' + Utils.timestamp()); + * const loader = _this.initClass(AssetLoader, assets); + */ + this.list = function() { + if (!window.ASSETS) console.warn(`ASSETS list not available`); + return new AssetList(window.ASSETS.slice(0) || []); + }; + + /** + * Wrap path in CDN and get correct resolution file + * @name Assets.getPath + * @memberof Assets + * + * @function + * @param {String} path + * @returns {String} + */ + + this.BASE_PATH = ''; + + this.getPath = function(path) { + + if (path.includes('~')) return _this.BASE_PATH + path.replace('~', ''); + + // If static url, return untouched + if (path.includes('//')) return path; + + // Check if should offer different DPR version + path = parseResolution(path); + + // Check if the asset's path should be replaced with a different path. + // Doesnt use a CDN. + if (_this.replacementPaths) { + for (let pathKey in _this.replacementPaths) { + if(path.startsWith(pathKey)) { + path = path.replace(pathKey, _this.replacementPaths[pathKey]); + return path; + } + } + } + + if (_this.dictionary) { + for (let pathKey in _this.dictionary) { + if (_this.dictionary[pathKey].includes(path.split('?')[0])) return pathKey + path; + } + } + + // Wrap in CDN + if (this.CDN && !~path.indexOf(this.CDN)) path = this.CDN + path; + + return path; + }; + + /** + * Replace an assets path before trying to load it. Ie, if an asset has a url of /dam/content/assets/ and you want it to become /images/, call this function with ('/dam/content/assets/', '/images'); + * This function should only be used as a last resort. In some cases, when integrating in a system like AEM, it's pretty unavoidable. + * @param {string} path + * @param {string} replacedPath + */ + this.registerPathReplacement = function(path, replacedPath) { + if (!_this.replacementPaths) _this.replacementPaths = {}; + _this.replacementPaths[path] = replacedPath; + } + + this.registerPath = function(path, assets) { + if (!_this.dictionary) _this.dictionary = {}; + _this.dictionary[path] = assets; + }; + + /** + * Load image, adding CDN and CORS state and optionally storing in memory + * @name Assets.loadImage + * @memberof Assets + * + * @function + * @param {String} path - path of asset + * @param {Boolean} [isStore] - True if to store in memory under Assets.IMAGES + * @returns {Image} + * @example + * Assets.loadImage('assets/images/cube.jpg', true); + * console.log(Assets.IMAGES['assets/images/cube.jpg']); + */ + this.loadImage = function(path, isStore) { + var img = new Image(); + img.crossOrigin = this.CORS; + img.src = _this.getPath(path); + + img.loadPromise = function() { + let promise = Promise.create(); + img.onload = promise.resolve; + return promise; + }; + + if (isStore) this.IMAGES[path] = img; + + return img; + }; + + /** + * Load and decode an image off the main thread + * @name Assets.decodeImage + * @memberof Assets + * + * @function + * @param {String} path - path of asset + * @param {Boolean} [flipY=Assets.FLIPY] - overwrite global flipY option + * @returns {Promise} + * @example + * Assets.decodeImage('assets/images/cube.jpg').then(imgBmp => {}); + */ + this.decodeImage = function(path, params, promise) { + if ( !promise ) promise = Promise.create(); + let img = _this.loadImage(path); + img.onload = () => promise.resolve(img); + img.onerror = () => _this.decodeImage('assets/images/_scenelayout/uv.jpg', params, promise); + return promise; + }; + + /** + * Detects webp support, returns boolean + * @name Assets.supportsWebP + * @memberof Assets + * + * @function + * @returns {Boolean} + * @example + * let supportsWebP = Assets.supportsWebP(); + * > true + */ + const _supportsWebP = (function () { + try { + let canvas = document.createElement('canvas'); + return canvas.toDataURL('image/webp').indexOf('data:image/webp') == 0; + } catch (e) { + return false; + } + })(); + + this.supportsWebP = function () { + return !!_supportsWebP; + } + + /** + * Detects webp support, returns best image path + * @name Assets.perfImage + * @memberof Assets + * + * @function + * @returns {String} + * @example + * let path = Assets.perfImage('assets/images/cube.jpg'); + * console.log(path); + * > "assets/images/cube.webp" + */ + this.perfImage = function(path) { + let result = path; + if ( _this.supportsWebP() && path.includes(['.jpg', '.png'])) result = `${path.substring(0, path.lastIndexOf('.'))}.webp`; + return result; + } + +}, 'static'); + +/** + * @name AssetLoader + * @example + * const assets = Assets.list()l + * const loader = new AssetLoader(assets); + * _this.events.sub(loader, Events.COMPLETE, complete); + */ + +Class(function AssetLoader(_assets, _callback, ASSETS = Assets) { + Inherit(this, Events); + const _this = this; + + let _total = _assets.length; + let _loaded = 0; + let _lastFiredPercent = 0; + + (function() { + if (!Array.isArray(_assets)) throw `AssetLoader requires array of assets to load`; + _assets = _assets.slice(0).reverse(); + + init(); + })(); + + function init() { + if (!_assets.length) return complete(); + for (let i = 0; i < AssetLoader.SPLIT; i++) { + if (_assets.length) loadAsset(); + } + } + + function loadAsset() { + let path = _assets.splice(_assets.length - 1, 1)[0]; + + const name = path.split('assets/').last().split('.')[0]; + const ext = path.split('.').last().split('?')[0].toLowerCase(); + + let timeout = Timer.create(timedOut, AssetLoader.TIMEOUT, path); + + // Check if asset previously loaded + if (!Assets.preventCache && !!~Assets.__loaded.indexOf(path)) return loaded(); + + // If image, don't use fetch api + if (ext.includes(['jpg', 'jpeg', 'png', 'gif'])) { + let image = ASSETS.loadImage(path); + if (image.complete) return loaded(); + image.onload = loaded; + image.onerror = loaded; + return; + } + + // If video, do manual request and create blob + if (ext.includes(['mp4', 'webm'])) { + fetch(path).then(async response => { + let blob = await response.blob(); + Assets.VIDEOS[name] = URL.createObjectURL(blob); + loaded(); + }).catch(e => { + console.warn(e); + loaded(); + }); + return; + } + + // If audio, do manual request and create blob + if (ext.includes(['mp3'])) { + fetch(path).then(async response => { + let blob = await response.blob(); + Assets.AUDIOS[name] = URL.createObjectURL(blob); + loaded(); + }).catch(e => { + console.warn(e); + loaded(); + }); + return; + } + + get(Assets.getPath(path), Assets.HEADERS).then(data => { + Assets.__loaded.push(path); + if (ext == 'json') ASSETS.JSON.push(name, data); + if (ext == 'svg') ASSETS.SVG[name] = data; + if (ext == 'fnt') ASSETS.SDF[name.split('/')[1]] = data; + if (ext == 'js') window.eval(data); + if (ext.includes(['fs', 'vs', 'glsl']) && window.Shaders) Shaders.parse(data, path); + loaded(); + }).catch(e => { + console.warn(e); + loaded(); + }); + + function loaded() { + if (timeout) clearTimeout(timeout); + increment(); + if (_assets.length) loadAsset(); + } + } + + function increment() { + let percent = Math.max(_lastFiredPercent, Math.min(1, ++_loaded / _total)); + _this.events.fire(Events.PROGRESS, {percent}); + _lastFiredPercent = percent; + + // Defer to get out of promise error catching + if (_loaded >= _total) defer(complete); + } + + function complete() { + if (_this.completed) return; + _this.completed = true; + + // Defer again to allow any code waiting for loaded libs to run first + defer(() => { + _callback && _callback(); + _this.events.fire(Events.COMPLETE); + }); + } + + function timedOut(path) { + console.warn('Asset timed out', path); + } + + this.loadModules = function() { + if (!window._BUILT_) return; + this.add(1); + let module = window._ES5_ ? 'es5-modules' : 'modules'; + let src = 'assets/js/'+module+'.js?' + window._CACHE_; + // Use along with + + + + + diff --git a/Build/sw.js b/Build/sw.js new file mode 100644 index 0000000..5c14890 --- /dev/null +++ b/Build/sw.js @@ -0,0 +1,59 @@ +var _assets; +var _needsCache; +var _channel; + +function initCache() { + _needsCache = false; + caches.open('v1').then(cache => { + return cache.addAll(_assets); + }); +} + +function handleUpload(data) { + _assets = []; + data.assets.forEach(path => { + if (path.includes(data.hostname)) _assets.push(path); + else _assets.push(new Request(path)); + }); + + data.sw.forEach(path => { + _assets.push(new Request(data.cdn + path)); + }); + + if (data.offline) _assets.push('/'); + + if (_needsCache) initCache(); +} + +function clearCache(data) { + caches.delete('v1'); +} + +function emit(event, data = {}) { + data.evt = event; + _channel.postMessage(data); +} + +self.addEventListener('install', function(e) { + self.skipWaiting(); + if (_assets) e.waitUntil(initCache); + else _needsCache = true; +}); + +self.addEventListener('fetch', function(e) { + if (!e.request.url.includes('/assets/')) return; + e.respondWith( + caches.match(e.request).then(response => { + return response || fetch(e.request); + }) + ); +}); + +self.addEventListener('message', function(e) { + _channel = e.ports[0]; + let data = e.data; + switch (data.fn) { + case 'upload': handleUpload(data); break; + case 'clearCache': clearCache(data); break; + } +}); \ No newline at end of file