From 02151694a83cd177ac3c6a221233eedea0eedcc7 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Sat, 24 Jun 2023 18:55:41 +0200 Subject: [PATCH] Initial commit --- .github/images/screenshot.png | Bin 0 -> 33078 bytes README.md | 5 + index.html | 19 +++ script.js | 212 ++++++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 .github/images/screenshot.png create mode 100644 README.md create mode 100644 index.html create mode 100644 script.js diff --git a/.github/images/screenshot.png b/.github/images/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f8bcf262dbcd98a1e5132c5a20265ca9a4b287f5 GIT binary patch literal 33078 zcmcG$cT`hdw=WD>P>QI43J9o((gf+!L{LC_2kA;D^j<@-07{kKn{+~vUPBP1_g<5L z^cq?SB!t|c&-1>|J?}YZeB+Ms{lR7@S$nQoe{-&xwem$xMUIk;o{Wfyh*JLbD-9wd zl6fMc3!koBCcHD>_i2xa=pK>$D=96nnJt*7mzD*lV-KsOcpoz2i+nw3L~ zm~j8+O7e_w`+|t{U)MjR{vy~EuQ0KZZ5spzO0xvYJS;p|n>|EXIzo6R8c_x?XX)Fl zazTaaeqxjm46uY-1f%1#7X}5_!=f#=v5Q*hMo?S|ZedO1@XNq8PH6g*KnjpCLz5gr z?untK_8{IYHORxU@KcwX2%}11yMTId?4tXRCxf;a6Q&x{`R{RMtYu1L0XpSmRG}K9 z>G_-G)6;x%)A{1x_=BvHnAVZ|n~#q^LDx4;Yv9=&0Z0r&qQPfBQooYLB+$3UrX8)i z5fW@!P~fi#T@m+)OFwp~ukg6dA?aJ<@3Rho?dc7u>s}1V4p!fZ^tACpQllbQCC612 zg#n#A59%cxXm{p{#t-s0VDd-~Gb)x#cWZq2Ryc?br|$YGTm zSHI798E~gX0-P~&F9EMEfM9M+Hatrs0}tP;ZE|U9wU2|yCUyW5nU5LOML@V*BV#;h zwq}86d2&-nzgU@}w#TvDGE&H4ikoNls*@Lo!laq5cxhOgHZpf0-OL@}D`s}-Zk~7& zLsj7v^oQ>mMpJI`3;15o9T|h_`GebTow@loZ^}FXH>pdn2W#HHQLf-Hs@SGd|GHKM zBOO{xiuU%D+Xjt;?ML$fQr&y>TXQC70~1sx_~KSTuvH3uK7|@XXQciD9IL2P;6t%f z#s+>K{6GtM8Ku6q3N3hxFl8(euvSwXZg}6ODUZQooZ@EjSE4O+Uo8p&=xYoNgtpWg zHC5H+JWMmClIH4taDk51KUlhl<*nvi(tU!oZoHbI(gH*)LlcYv0CvFqb(9;EGRw`2 zbZs$(879qgc)w$1TvL6)uPB^Eea}n`xEQ4Xt#@?#3C3O!v`7AA?w7h>!lK)HKS(n)8<6}tTf*$l z_VK=Dc;gYH{hkhT6(W|f7Ty~7+R9iTPv;kP!xeTu+vMJ& zpc(ouZhHu4s027o7v!!PFS_G77bAC^MOyiTEVe<69T`W(#*GhNnwjWlRd9xI>3QT! zr^>d=uTCwo9+lk7wuJ zWO+$Tf1^wXBD>z|3mO+7~Y zQB2LJuTb_W#RX~!t)|IEyw9Zs-!&S!gkM0U{X} z;eFOok8vBZZ&(TwKmlf7?L}p|h)UBW4Jk+jMUGah`j;YH8IBDir%SXEDie$#F?n{z z6wdp6uLizeX|LTXeYaRTOr60s5uxBvFpXOb-}qH=0!y_emy}iIrkbNWK#eM=Egv=} zP3C3p21MP-c1n0zx_d`(JF$qGr$G~c;=JhZaPQh& zP4apVv!Cm39slY^f8%3-R%E=w=iQRVTN1Ksr($q5OpOckQ&4!b@0n~k!<=6KuhwO< zFKVW<{-S#oa;WwYn6^M(i~UexI+{D$AbJ|2lp+39ilsqFUCX-Q!OhtML?YC7WtzT68IKdL?U&hVIua=h5 z&M~*F^+u^e-;=r2wG^bmjB6li<(!yQhL5t?f34B|sNZXc zEPQ8JX*lbei1bZ2c(B-}lVP|=lY7yF9;8VPTR8SgZMt(5<%3bS7 ze^5c0p}*v$9M!2#sMsdiglz{Hy5vl=#@YO|;Y*3H^;%)V+5iYd=fq;vtON3f zcBJ9L{CV($fhHTRiTe;i^#nfy!}c?ces)c&Z~j^}kwFwGIVfX!%Y=9wn%&5ChREIe zCV2PjQyJl%0;zl>usouytm7wj(PEf-!feO;nPWa6X1-mtMvlB)m`vT;jvOU=#wT&I zElR7;8xmU55$=^G;Xb;mSmm>P7veqmigB_s>A~G*inWlEnUVQFzX{f@8MS z{gajp{zN<4s>$zF=(^FJ0Jpq)To89hIU3&^Z_SNCq$_}G&CeL~z-9=fsPcvcI;A|z zqKq{#nP0NtxKU=bOsL;(88q;&0ao1-dkk&R^D`-{K;I8u_nQ@uL5*IpeDe+Vh69hq zp1pZgh4VT%D*Dv}w{O|(?wXh~d|us!?-U0m-Ii+5Fq#PRXU19C*9wx!8Jk2`>>lZ?GlQEqjsS3hwPB$SYJk~%HfqLk?9VD?p`fN zKk#6TNSnBqG&!~D_Ns)Q2esrp^Gc;Vu^F;o5Mcs?KRflGw2U6ab(wqjf(}>vc4f~p zX2}$Jskpef@TQMECq7!brR;Ka)ShC$2uN__T8i2E#;0!G&BMZ=$#W(df{z89wd!|L zp9~6=m2e5A_nRcy`pW5?Fcp6`USUR<>r^;!ID9Og%lj3xO~6S?Y(K3 z)}HQUJDp0U-M}A|zKiM0Nbm8VvUOR7z8%<^vc{WPg$lrUd|+HKvuhtFmn_8{2Si&c zxO`LBJ;$+@7E&#aj{dW$QEw%8MdQON(KyR5jh7uuB96*i%4^$=-ZoEFcXIjpVf;h_SCf(;djpS5P?^O4w!yzBF70O*rV|7v(hX zeh(jLtd0St#fB9-xo#K`BRf+pj3s#ckdP?@eB0?8!%BNrY(jqjRLwh5LqvRbZaoHj z8iKOfbUSFd`h-d?BQe;X>+>-QaItF$M5aN2FJ zl;}Q>)2O+4Q0o{1FacjP6)OWI>HK_iFcCk#0=voxKBb6%0;i})%f)P$D8s<^9R=C; zi!X8=7xPy}xZ}_&y|)~L<|JW$e%{n(oM>dtacF<+w`A_ZslKL~-0}nvu7JO2TyZ=<%W?|Ys4+jZH z1T{h>YnGf>V9Mz}l=JX8y_K5g@FLovhrM;8hL+7e6rU-@@)8yGPa;E?w*U@ljkPX5 zwJC$#yNErDz~pAMd1A}Vh8~uOzamPzWG{*q;DZnxTw?yNrr$AbA4WI&!#iNmCh7AS zG>J2){RBFxa5j}SBp>k|(NOgi?~X}|a8W1i0UWA>O^R^XMo zY2{IVIN#=6HK(ZWEpb3lhmNP-wb89G;lAW(+@*F=ih1{{NbNhmzP7HzA14!;Xq$78 zJORB^rmDhdxX8~}I#({zUO4^{0cJlLpBwLAfI=vIdWyO6d*TfJ&=JG9raV|xon;>A z4bO3hVYK-H*#0VYvw&dtl|aRGA)E12rJ9_pD^PD@6uWOjeW9*b^`_?Q&fY#ytrykZ zL~fhe3$$DKtO;LO)17zY|=GZCgo3D)9cb@e8NH{s$ifpUX! z`pVDLcrzj2aNb6Qu+=9W$7xdP7Al`}C#tzK>Z>L^y7pqx%F%)w`)JaOfg@nDy~eqP z(LT%D`o+&PG_|kUC|Fk$I`bxlXs7>dz;GJAlaW=5fiKa$B$9kf6-byn5h8+UZq$=ux<#!oAbOdj=XgNWyl2Hvm3Y(~VVUx{)~TW@dog}a7q zTCeU(jxgSYO$jK5TzRIiy@#7_`=vCxa`jQ4&K^#$^vBA&U>tMGZm~B!N3g;#>uF6I zol~oxWovFiisFFSfZG>JagMd8M#$nj#hcs#NIw43&3U}ulawzzLKB9*h?tc^xicH? zyJKT@TzFa7{=Q@XKmZH9UWSo%@+_{CZKJN+GOaVQ^>9|%Gas+s_BFjWrQVkVq>3;Z zpP6Dcn{HK(VY0E@B81Fxf{D2b!%$Oe6eZ^h#lcR^1=4p=_uKTg$wG=x7lby6cLj8+oc^vl)UUde4zv{b=A;Xj65n4 zz`9g<&U&saC_YO5Lj5>#6w{%5Y&x+pMc)7O3-#|m#LijS^jhHpT^gZ$;b6cP0e+m3 zc#VTdNW2)sz`Qb+GvocZNF|vFe&$$&9@_66_h(q6#Oybv&|BrXi_CW>6mP-p8^lg) z3pL?$#yI;9NRPO1aRm=LzC5@i@#jx#YJK{f+j;GA2rMY+XeAk0YHhal9vf;wXZ5N{ z{_{0nA@DEauS?Fzq<$t%Tmw(FC&GL| zNYN*$w#IdC-;$o~-E~!(fB)2%oZs#A=LpW_m&|0MZbIx>lg@azhjBP?%q3CwOTSWO z-H<}fja~uKJxR;Z+ANae9}Wzp&wHy*P`r;iinh9Hdp`-271mLWvjzAn%S)9`SGc4n z?e}b3+U4&{AuPX<&RO{Pq3B-{mB|u$<|TK=TO@g^aC(Nok^oHP&vLe=kH68;0Z!6- z`bBf5yjIYPA@jy? z-baXHFMGomnOwHgkg}7p;+bv14lgAYOt$5#`C4yql*j?DiQ^@5Yt?}-vI!k}!a2X| zxYx7VU%-a{zN!Q)ZXG^rAe&F8jOlmwmrJpKWcgFmQvQT{ZQAZRcnegwku^2K!enp&aBevTp;B6RYW>Hz;$A^hMpx+X*E zBzdhr{hhEyMT=DLCHjGYW zu!+N){H$faq8f6kP*22qb*7`L^+k?>cA$P3nT;tXqQ1+}chhJkz0pCi7FwO-h5(BO z75-pT%@8(GK;xz^iIyob+c(>0vEq> zczdM3<|g8-(PAS!Pt~g(c)IGx zx;E7{nY0=dsI{gVuIe>+T9?~(XOY(%+|1`msaovx$b$g)jW!i@v=teO2@htH7ajc( zTrk?yg+>qJx#7{d#)7U>6jJ@s7za6o1&`CM-|TLBbWk1Bqq01eoXnYb>kfJ(SPANL zZT`q(GAYUT)N@9ehw--4(x~N49^6sOLjH_caR0+_{20FBDca@WO;KfQ(CV5`O46$5 z&#a-o2JYS1+EEHWm$zl$Z<90@-lhGW(;;O(l5##~1JV6=)OHZar!q z^g0;oB=4J$rXBQj|sU>z2ady30(?6fa-`(b(gzhlp zb_lYz^hOpX6q;uq|4OxH;8$vf0|-O}KC#GF4mi%|`{JuEC^bioS7 z)1@mVIAJq5oIUn)1v>ZqafA+kBR-7U7KDB23uqGK8QZEqtE`wxVeM^p@o(`l%yh4L z#Tx6&+yPMl6}wDY>$#5L&|(H#&Z1^oZY^gtCF)j8x(;Gr&G!YYR-_p*49ire%$2oc zh)1dufoTAt>5?ZqZW5@O>J82D>;OX7qvYp1`r)<(T_h+)421O1E;xa5%`^@gxVUZS zrb}Q80<>}0m1N4B;vT;$yXz!qcsqSc1)P@G^v1+2>6XUuGsO(hA#@K-@N0C0y zcg2Ght|O%Ikq*794=C`W(#46TSbo8WWBaoSLW5`OoD!s^*v>Tf55G=z6hC3Et*zh# zD9vJ_`SUG;c@t%;+ABP+OTwWkw0SGTjn!3Z$&F%GN2Pyi(fkde~CV?vqJBt7cqUCNg`WqZrj5 zbJ!B>>IS2t&8Ml{DnZrWW*RG4!^IIrdT^bT!TlDgf!NV9{Th#$n(aQ_^3KK#Z3h6v zzTVW7_FduJg~xUwn={J&BsIbYZp9>s8YD$*6(Yp4eyqZMludm@!bJ+l74L@yyFi+;KNHlOh=hJ}9iXBdWyD9JRMBb#}LA6>}ymxZr zgpoh#&`=a^smKF3T}+uWclL5~?OVE0+IXp|?F239slS~2wp#hJ9&g%sZlSVC8N1@# zjyq>n-m49VGH+Pqmr(U~4>JHv?%JsjQ;AXi0KPY5I=9U6ZgClSbl7u+V~|s@MiI`) zfSP2&qc^N4HDUCOnC~OkQEd}dDGeQ@XDSb_*M2l$x~&8|%$B(4a<`srRtYT1^(&d=i$@~g)`_hO z16>B$F(QkIArNM z88biweWf;bd@U;fvKXpamif5-VP14vWd#U;v~#{1B0<4)6+y6g{MVeH zJ;sCnpGh-IYpY$nxg{MFPG@s-r#{swV2X3srq(u?i5cAbX~<5h*B%R1#N%1K4`*nQ76 zUV2wMzqZuo3TYa8pDQY{diTjF3kO^{)0<0cj7AR!`AZA$P;e!f&K^zh+GGm=#@@OE zzALQ9?M)1BIwYY(C$#Exy4if4gLG)M7%xvMHLI;YM=8EIt}!Qf{Kj4QQqXjaEqrTK z>Anum!9YdV$UnU7!IBRo?&42|sA@mIC+CUHHot6-Q7VkW}2 zD0Cm6U@ECrBE#F$+ z;@4`1+6j^29&7ZJW%jj+^d+?m90dPCf+pkACJ2Y||Jn`&AKs5UZ6 z&Cl+4{JR?96Gvhi@pkdrHbkD!wn&qfxnw*T%WfMt8pC>+!=8F4pA8FRy^BPNn?CIR z$QI10QfsW<2}V{|IAt}TIgG*`$KnmyAKSWBjK5{>thmTLmpsjMWRlb? zpSV$FpX9gQ2ljfODlDAe8fDA*K;wp>U!;zC6^ZhUE%V=d0eGHVV?4-nQ4*ZU7FI>3 z-_pR{2nz>0JuE3=*@e&9Eo-?{an9U&g`K)jugmFEAbZH^*$)<0#*om%(!JE zxKa2#?QXOk0N$7oeRS5$u2+}~_OLk_*`IV28+-RES%1PDrgtNRVk%KtzSPB27wwbr zrk(UIiQtZ*1+r)f4H4z*#8!M2I6D z6x=WcF1-(*OT|gD9$tYBa5DF_p?YP378Hu%59lwYrlB|-S4$PH_1)QqSLdr-3xC## z(&d5Tdb=S0)k0o0TGs$^`n)#uY5E>+9T2yoX50=4CVY_X@R99T{i>$7?(-y_=21)2TP|aEv(R)+)9(TB5D@%aq6Q|-gC&wEnvEyOto;J z{^8iTkIOst_79Ej&3qGB#C2;&kF4}00b`@y$9>T^8P(Cgi8o7(stJn&16J_a5TF)x z-(Ao?t3$9!ye)o6#s0zvh3*PgOg7Wy?vTyQYs9Ob61MRG8P-7$Zo_6RTk*48jw~aC z_BT>hk&H#o)q>-@H;j6WGS()NmpQ9U+mlD+e8n1%G|M39Xi=X--7=uusyIWn=`<2! zrme1*T_ZfP8wIoVfMi3G^o2c-gB8$ZxaM!k!nhfOA~WoFGAOhv8X+vwi|Sknunje+ z)wS)6(s+0mFq##PKlV8rK|-x2pK(p!G$@i`0k_svHSMy^4cwP^5wyAGG#xI-pi^A5 z^g_3JqGa0eal*}%rtO&j1^t2?3B2TqV<0Ss1mH-;Lq{$;eKO_-z6q4x{^Qtg#zED%w_{2d+==cDo6A7_)- zeOy69`I1W*p5CPmtSS^OSz#e#-SYLh%jNOs9t9oar2{NM2`Ltx;8gAPo;MJrs99rj zPEIV-aE&RJy*fMlkbJR0GH)}|#pGv6K^FZuLc4O%yuC;j8USB7(KPWttlS6BY0q&O z@GsB{G(qvp7t_Nr2&?t|L0e<{vwiCRt96KnHbZaSMNLWSl=~y;iiQ0156J}O?HI#diZjl z8aTe>$&(Ke9*;M2N`E3nk9W$tS~SBf^MxGQC4q+JI;X32z=r=OlLITX{^&LXR^w5x9kk1OUM6aMm z_UWOV8!AWg)VM7qOlKaa4KD%Ngw~$&`45fqSl!i=Yq8LZ-Am-I#~R@D-acHm-L?|m z#7vUxysE7oDev?sI!Q3%G=7``ipIwpr9%%_XsHF*v!fXrmEH^Gf{HtNB;;%Tbq(7B zWi~&Nt7t`g*#dQWG0?88 zo&0nTyu6~gKL3e^(f)|I#rEu>f(HQDk>In?F*=rymK1>06+aggTa;#8^#sNlfFGM= z*&hdRR6IelMDsuQbL!Q2T+SwRBrFth}NjKB$O6Yx%XBhtxw>J z%}s=DBjaF);ZAG`(BVmv+lQH2h5|ynRv)QqkR$1;c5Sj~*R~`E#`1tu z>w@at8&CU9ult>dtleI7PL&k`8iMQ!j-S+>Tzy=a@R73V+BN4wTjUN|xlqEb7fBMy zF4&U>@EQlDeQFskqPh67N%DB+O_;QtubE?$7QGq1lRd5m7y%t~x(OGSCWfn7Eq<{XJg*0d*jlG%07!F<8xQ&Ga zd#7K1)aZG&Y=!>m^{qKugIwyx#GQjP<^$By3?_b^pDH<$G0n+39BuG64O+U>9|4^ z;j8ooN830fyQM9(Q%hC3P<5JD<$yotHpIwBFCxn7V6wqp%N9m`Bm1>M*1iXAfw|)i zGKqezse}g94o4?;`C65iCjev54mh&YY_IdQ?u{@-0S|?7ezyJB=m&^ubuJ4Kkiwm@|BH3O}Ea(-lb65%h3tG7^g?@I`IE!Z4= z&0={8{Oh7c{8q{1wFz>4j;T_e=f{Ja3d3tvQwn)TiW7T}^Sjztj2_Fa(r5UWs~mwU z0u+S&!E)SmU~1Lps*}*CnkjQC)f>~*#Q9->ocmNw*`&dp*fNl2u{{)@8EFF2i!s4O z!ZF7N{YFu_A2{;pr%;Q)h9mq0Mo`+f_o~lYL%NGXg{jII%roq;QEzCee*!q!WawcN z+z`vKo%tZx!!3jrdEir&evFm@+&*@Dwy3>VYPUU=WJ2Ezp1L!B zwsn>MmX~n8v+d*r<;x1qf_WZ8bS!r%c3Z~&l z7G?@nzF159sqg9r6$hN#j5SkI4Xd!n0-`fytx=NJ3-o+G1tdGOY(Na_MnTh~^xXV( zNrRG9ZMz(u&oim0ioAj39M@#Qc?-z}AZifbKeDNt!veXO~|2tpf)au?b0+XMI78aj|PU|lh*xf0v6O2?W@`Ix!W=$+T$r7qbu?G$77 z8OoEZ!jyzuP*En^6*ERvAajzE9SnlH^TXXCA(S^ z3cA@WvgPX42H&z_?LLkjVh!I+w$4^%YafhRoQ7f+LN~|x1wNz8U-Fd-Rnw>E9Yusl zm8tjKLs2}N{?=n-dp9fJzT{cB*D0xTid%Yv>~xVj6<1(;hn5DpJwW`3RT+Ey zh|+#byk+x^OORITqtOY>O=X{@XRI?NZGK|GWsy{4z>MZ%*BPzn%Z1CoIHa1HocfGns}hmrt(> zor8%*QOLoKltBJNC;e~w8G=$QED>K}u#{ZJa?ofe-6-l;b$)>f_*tOHOPZuPsgDB< zGWaBjnu#H-M5R=h(#AD8J+WD%*Z(+j2kMhL;!~r(I3?>cV^`5g&t4<%i(>SyDfM0x z#C`WmGQOjSHB-o992j4!?-fKsI^DMG?r+RIP>;IrAtIeUp0h}l=GP7JF*|a3;)hm1 zMO007Ui(oe+7LPPJ*VZ?f>xOndy(4AODvn<)3{!Zu_1K@N|Cxjnu%o+2%lpv-*X>@ z$qarC;LIaVjt(1_6|_57u7;3R z_g09|(kB(@z#@jbZ^QCdpQn{+o0yac#pMy@>!)FIEV)g-l^GxBbBjNhVN5v&G+0on zyNz;-T5i$$?3PSp7Q^{D9jM6EYi*%g9h0i-J-qCI44T60jYg%PUsdl2O0OE?KkYC~ zR!m@r7W5<|>;^oj4|hIfpgQcWHWROPhfp|kaMk1zzE;%1C#Po(maY}8{aD@HI?@J8 zxo;$P##=8&m!USYh1Xhn+s0}&U0GiRa+;gmdTpJ>U6}2@;~u$@#;j|D+FEt@ubrn1 ziRbP3zD^u`p*DUFVS?K+FceDpSn{PbwZS+>&Y6UkC?NY1zuSdiL6))v_5V*B=hbu* zNzb?5TfewVe7=$X|F7Nn|8;x+?~VOLzpwLGmfv29xE|kr=dMcFI|`zq=P$4?N}Mj9 zKk|=L0DlJm=UIT?rvQllhqD0x+p&Uw68w*21^*=Ye;q6MuY&x8!~f*%zd5JSd1EV# z|Dl;5hzXQRIB!7^mWXs?{A4u&2R!xauDC-@M6`WDs+X_hPSC>ZH=1(R<6pi+)i}gS zOZS}F|GZ+e(#PTp{60&R2^@NE&0N455s>X4&aetj7xPqbT{%A6{y=``_BoATwb#~gkI0@@hWMcX5_E7U#9D! zJUIwZLOc%|Cp^GH^5;>MniQ+eh8FwdPBa>gK;w||kLAnD!)_1-91)W|Q+w=lxQmO* zp_1gnqJgwy%h3a(q0S_K3HY&zwY6a1sOYam|%fxU}P*IT46$pA0y?%S1KZ%5Ow ziHW91=M{Ju-FZuJ0+MG}<}*KhYbJ0tbZpL=o5#+}sw|HS?xBro3Am66Bs@$+N-I#O zOb651`5-}AQM`5p^Lr;QsJtgZ+O^_Ve(tH~oPQ*vH@`Gy^expQG&b(}OFcUNxJtWs zAH@?pndnOi{KUpa=lvX82(cTFiaXc)Z-$5F!I?WS@1A5yMZIx;~ z35FNvUhvl(w^ki=EgN{k9D5UUzol+d?#H)9C$qB)_M_bfVg*DVe0vl5JL=EXN?Ab$ z2qTbfKwA#z&6Jy`hc)J$A)x(vQv(p7T&LzJ&1^1J0{C~p{?_9$M?wBmliAw?>N*%M zi`>DXop4+FrNwi3z315ig>AY;FQ4PhMV|KkVYR25i4B z2MOEf=EMrHQr*e=)u40yUvIGxL5eeqZ)8y&4 zG-LRUNWY0YX%D>=MAz|icp$@5+v3FE#!CN+nPU2vAKvhzJ^b1y!OdQwY-Ms!l`-oR z{DOsRUN!>uK!BF! zhqe^e_}t>rkQ3`6Qx$qW)h5Jx{QWlBzX#|_k@Mk4YD_@;{iV^oimcuulofNk9Z!yo zOZlX~0E4@3+-Uc>d=CBRu6jPHahZ!QmyS*la?R^#d{T95Gs;= z9Q$0<^u1onFUo4a;7`+2lRM@X|D?HdSrsl(s(9Nb*cx;wS>z24MYOvvKj8*^UFJhS zvt|w>K8Ip8slrUDK?B2r?a-C@=M>hH4a<}jhAT`2oc>!BX^r6sE{lPbm6V7VdV={L!UK?Fz9n zsdxr>RUA*)zayZB5R&Jt7G@ISej2dr6rAp5_bxsBctx9HE%7DanWP2^qHE9K{yK;O$Gpe)liJB?c{8VM%0H#rOy4?H1wT9;6oVn$!mkZ-14shSQUgwGACa_JHQIlUA^;vf%y zhwu}#yx$nqTU{?rgRsp!cN>DstJ{lJ^8N9FfxH*z4;|B-gAhjP93g7L^f-E^;rDti zcAU@s_x#9b*zmx(B4NUMvu<%ZPx+4zv-e#iJ3Y1^r;N5^2&Yq@5na5k`mVW#Cv|k* zHFEZ4TilSZ+WPR`XC1~jbmvDu1Ln`|M5PGO@u*k8FSxvVRnGVOyy%a1FY%drmoF83 zesZ0a2?GgxUK^kvLMf(=hw2&%$xOfee9xiDV&Fi6f(>+# zL2w+aSFtxSl-e#ES6C_U6n@XiE2cOvNMG!BBPyhlT=UsE%?JQ~EdM*7%zeJK?Wek; z#8~?2`9}*XN-K-shgv^`?$yQNd}Y^TL|k>bZN7?haRqAj5g>cUL5gZD?Sg!)e60HO$vNt#JVCY>pJwZRY% zb9Ps_0KzSpu2L&Hfe_G7B+*``i2E+G?G=3gwmgkg$ENm9NIRt@y3T?<&unny1=FO2 zOg!ud=HKz4b}```g;|QD2papXVA2Q;ubB8^ikaV-t|>*x-M{rVyMiz1JEWUwep{p9 zu`1Z*(w0)QAGk-%a*jWD--W-S`*+NQ+?y4UU|Qxfs3YX1q#nH*wSAvW=&R}Nkj)7M z`YFMyL)OO>E2iJv|Hm`)zu;;4#V3^tt4usyGh_F9-DTS`Oc!JM47)GLvJ=XfI6|#- zjusqj1({c#H{uPoF;6qML;a)jpF#-{Ii2T9Fc!5BRct?PPtU)QSxfEWZAba?ii8|a zeqa#63S$JX+nI9hfw*f#IuF9RZ@syo4yuQ=s zFOa=NNDlSkNJB`~VRx!nd|!-ng23w%vmNI@MW))h{)P@{R&24YB0UOd2?}d;hae$f z3GwncH$pAjvP6%dxbxyG^Mi)e`+3c;)p~jddBVqYi|^$7d$@JiB@H7!v;CVnEwy`H z9>FgK&~`nQ>w7|I~^%py7WQy zES|C_RZN*sI8_6GsJh(VviZ6!TV=iK%V)JgNPBc#zShtFjmIQXHJe^@J!cZx1i9-u z69l@>%+HHPk<7z)F9@mhlCn|mU)eL3j4V@2-dKfToyrlbA}2Y^k6gkJg`~dtwt2Y# zk_~yU{_z4*TEfVS8BNu_)dZm88@00T{8hhy>o&pHvS&RtO$pIYz^hWLB0H7ae;Coz z{3C2rd*Dp7wF4nXd6XCskg7U3h%*bFNeE2Ystp2ao_o!C4t=r9J`Q7bmzbw9eu{IdjucRPFVimhvZqJo-`T`zqIZG0P9 z!;=UjAe>k$J$n}7I%{qY`=2sO{-2YIXt_oP3)?S~FJ6#k%R;D$w9*g%0rT^md)>Sd z+*v{>TaVyc~zA!sXyx_UQ4Uaap;E^XJ0Sf!x#KJUif>4)>{qF+4U}@~e zIdW;u4*c=l^DIv=+^@Oq9;J-JVgg9S^D4@S%o3t3i?9dDH8MY^n~qlTPmw|LjF#rb z?lxLXm*)wPZ#9DTzydqhs2 z-9|xyG3&hEa-Gs60Mhx2BmV=*74BfWoxRU&X{Y@PI)4@1g_%SjX49276(b`YO?G1l zsb|ApFTWq&6EJ#SC!Wthl)L;_0VUT-NF0Tugo6b6=f%~z+(bv&4|(JW#dNhjA!D71 zqcT^S9{=N6!LR={Q9K|a#N7E3iZJ!i_5aJyResZr$fTOTdUd46J`P{|?SaiCw++t6 z=FD$BMm5J~v>=m6O;JXyB38ulyaXiVy?BLvmxw7MmEY@)T}(k{ToRv*C{o+zo4dbn^;!%IYS*ww!} z5@kBYY*=46fkom!ZGLSNxPbv?o4Pw$1$H&L11)-e56 z=Ax3p?Y{nCd+I)+FS6vp|D1@hs`x-snR%DcOJGYO^;?uhJS>eAcQH?wG_NHrj?a^Y zn&MsJ&a85ONR=gJF!%5N1mdzlfeuevEyY!j2gb1sfbGHO^|G1tBFE%m|(zDH+D9J@O$B25w8iS1VA<1Gkze!)> zmilo%ncKK_Gjw))q+gi-(K(k_=3hWCCsy5?C>$=)><-o64I$cF3ib8P&;z#LeG!7F z=`yd5cZ#BNQtx(LAo{(0EYQuCBQ&Fako@ZnVOWDaPm|ga9sx|?J@1NKr%4RDj>>;Y zy57Utdj_?T=jGJ^6bV>{Elo9^^e5g29^Uxfy1B=;`@5yUXfb#bZndGcKIam zPoQxw;@C~Omf`PYj=RK?8WXYSO~K#e2|d9l&eR0q z{`$p_|FtRikM81sHs$`;hT$KwzuSg?I(vVv|3UD7Z5R?_`2Vq$I`-KjjF;m4_fLkN zJ|j8**^_^?5dYbe{2!)x_D$r@2bND3yqANineLGjmNNu5`oc0#AoS-aR{r0G`d=0P z4=w$t!mqdBp%Agin99-v8GPXeD@-{)=w0yF%nzT5Tfeb|n*X4qb9%c{Hy?(so7=1l zi{Rs*4Brc|n%k{I>LS$-BgoT5lEa?5JY0Xs#qs&(>x;w^SKnWMJ>>R!ZO2Vl7bZy- z_FB4ub9{UphD2}1-ImcX!ilT81ECrI8@hvpUg){;eyj1^tmiL&EARZp`Mdx5;_val zKj}~czideqTaI0_;87fq5El9Mc`5T_bedR9Fq6VWv3zMG_sNcD4B~8}oa6=e?Fgq9 zM_fqT(;Ml9tv_d1SR$34pL7FIb1B=igcU$^iNWKJX76dDnc$-=pc;!+3mwCtw+d?P z=MA|nOk?AYLMin5bd$*XmQsWmx5#R$c}EzbnV9ixoced_NLKLl+|Op-E=q@DwDsn)oVD-%)gFU9{FFd> zK4gZJr0)Mz_nl!)X4}6w7DiOmV?`h=2`wN2qy&g%1eKRy#ad+;JKkSM}(9=V@B7U?`g^Qh=qAr${Th9Kn0~KFGjRGwK=!07i_wvFDsT- ztL>X3nD7^MM|bLglk4kX1kGlxMYky&XWNfQ&dcQ=5eykslj=?M=Zq@_oB_&aB(_>~ z`LCcH*^dgN8@I&mk_RfOW{8?=F;rzutvXol^{+A(G-ah1$w7?tikB2Z}9GUg&F}mAj zJaDo&+r$v6HHIFh=Cjn^4gy8x+h^9Vv)Ool<3|49DVf`^|MKk%mH29qFSY`CM##iQp+)O&mEU6uwf%(&kAB`Eb98){`In``hh;=SG%i zkxOOdgQU+DTw(FYXU4>UcmzB#AuY7h5oAv82#GpU zEYSwXYn*VaXhwR>W*~sSs7K`?xj6DW;ex6z2%0S^&9~|!CoULdJ57F?nmPB5^WfVX zO4w4FZIy|3KVSBL7m83`Y+PUGh1k9m$o@L=rcuw2ssVTq?9Uv^=t(CNFS zR(IAffLar@pC`~UNekB`erf6OX_cV|>Mh=aLj5le9?g|j3b}p~HdM?BW%pu`9PgNY zU}=}m2{=3a;Vh8nmRpj64yDfRA8B=Ap^JUZRVdZDjO|Jwmd_Vhck)*CXtQH7XpZgT zxgrSH4NmaK#L4udg+W9&Zz1xn%E+$qe${akOWP*E&Dp_!KUgnx*z~oz?gl?w;`hs&dn>$G}q@;j3~#dW}RyGA)T zJxl9@q~T^1CeA{2gdEz+HaNdObBUI^40lP~pNgQqZG z3C-w&E$MA^cG}}-C{yG4CavVDyY)5mKj+&9&Jjs=9d9$de&_6H+}UV$R?KQ+LvCEX zFI2-Y!UN$}Asf^*c6Q}A6Rqc=zmEJm^7?sH;a#GIF|oo|U9CTG+s&+*b>BzN4i~9) zs*VewD@@Q1*t=c8uOEsKiV=ztX**(nI$PRaGM~)V9rrAr+XF4v{xRCIuSZ}e^H1Kv zbu=vWkYHBN8RoK@|Vn_Uq<_H()WfWpCIa@;%kz(8R0(|cPm&ambcnDwEe z42i$nG|0=<SBCZzF$6p)x^C2MvjT0yW8!r_J^1gy8&^j$*meOFQtx&EGz9D&b#F#rN819UC)_@0H|Dq>wjCI* z69(4DFwTG|*%tbF;U#KAl7JDPZ(>>q4uHc^AL6CYG6%fMs~^j69G^dH1n}maCwVwmj(ko9`6A#cU>)`+hf2UQrT10tp6_vM7N`ED z4&u>`9}7#Q=~4<42eS>_t^Q3+A#W?XUymlZBKSk>w6-=rE~WsrinhF%{DcfGC}49sKkG81ORr%KF%Ak)i!u zeVw^wSB{=XNR+R&2dhH;uuCNAfe1p zN)L}NXh;$ZbN?aGW@q(UToHU_w4?E|sYUk%Iy|nx;8}f@Xx5C9=g|))r@oQ>*pXvj zf88p}8a9=Y`W(@-l)ES11e^)LQv5l+gc+ub+=K{yg6%z**(n`JA<9_Z~m-Gs;7}NAKX3xaIv5fXbMq8x;P&zRi+<_vG5X z{+^{$9QgbuASc+E`c->E1r9N5Ei>M zy8k<;9?7yWm>+*(K!jLeUH+c`vgrXwM4bGlcGd?|8Lp~Mc+Zln)LNGX2bmh6pS+(Q z%X5BM{qskvSHH1OGM|D!+DDgpR>v7`YPYJ3&6-&)sXR9%Vf>ATKm?xxsbvM{#WA-f zl)Y`qo%nig{>9P_yFvGI?KS>sd$1tKUx?+_gt7*K1kbGhzBA{il@@XBV4Vheapuvn zKnMlB_)OJygm6ipSW}jktr}+9>xx)Awxu z;v)yq;jN#q>f52q(&E_>9;okaK4nY^Wss(EaY$GBR}JBy{FE_RKCy@=5&{d4Yo`41Ttdtv`6#YHWTl z5Jg?ibB(Kaf9Vz~(kFhp^0B1u8r;qLP`c`wmR(shnIuSBYKZAB0i|7}S-&Hc(KL6A zhxHcKJ)_p`7%Og5jqCF?M4Hihfc8JvHEqG*a`yNlpoUn$oS@fpK}7 z&V`EazDJ!L%8(8sKY<4)EIPC1d4uWv0FQ}4j0}J;MRR`3QPOlzC5jYY zWYSN)wQu(3Ji|I52O4C5Wnp1rqKbC0CMN6R#=pBB{Yi^T4IUQ3pv(6$QU>l)2L^gt zqMNL4+1dlrK>PD~XKIVixR4BmaKO^+1V+^L4Np3Ae?IxU9b-Jg$l)V3s?ICspSQP{ zQL`guW@PXd6zE+gbP4y5%$ccUPGe2J+5mkEthdP6J6BFi!UKYBeBI6rMp~tUW|e$o z*Gz{P^p?xesV;#n*@n~qP`6?N+o zSUkRYOzANiCrfBW_!<4d4*SrnHl4uK_hos6yztTj*&acG{LNv zdymsP-nLf$Jw$xZfV9+pg$ZBXXMMHLZ~PnN-k!x-drAJ;HFk%y)PHXqqNYphg?8j# zOB@_Ma$mx&q!+(=4K2Li@CFrE4}Fsc08JmeSI6uE-n+WwDK)GwF=L!tGs@;of9^@b zG0zHDIK6v`Rk+rd+^j@vyCWRa>8N3RMV!)H(mw6m8X(J{D787mpZyiLIZFit+PTMJ0HAgD9^>M0Qym#jL8Gy?}?T z%-&4kzE+YCW|!SKkF4Ys4?lJ+UtQO%aJ4)6e595yE%TQ4c*^iMvkkDBNY<6uo+Y`x z@v)Wr7*PKMFvtm0jFFnC#d_vEYS`b>vBkKuT)GPp);cDYG2O8epUL~Vw`xJFD@R2N zeWT4y^{)Xfy0}4C>68^sigfC6y4FEPZ5qPQW3S>=$NOem4d_{Dj)^yoUAP8SE}rCQ z4xt{C_Y~a^aZ<{fkqUa1c3Hk4QP}2fJ!HFfU()^I-rRlgrO13}wf#!2t%kdCfdIE2 z2k`6tasd-)tP2fHg%Z1ezcI@qZn;l>#hS4yuhyL6vT_4nRrr6=QTqF&oWk6kNP75y znL=_BT{}UK=i$u`KFAow9FFy5ar-d)8eXe#q)gLJbv^w1a5nZimWTT29!-}bCM;#g z%J_$2`hENA1gqeq%yYf30R+im{IBb2NZ5bQ*o9#G(gxGVG=Ey{^$u;oL06V6y5J=OovvyVUsv%s zy#y#FWLk3Dor-8r%KCM0(sz;Nm#Zov{_nC(cx*-ouBcx460Dr-7K=1xbQ}dvQp~KiU5`;A(lr+6*y-85=^j$L3SMm0y@{M zg^;`-@?!{hfOi{|!bkdkn>HP*nQXb^K4M2qtfQsWW)X8!QYEEeNRMc=IY6lS_{UV6|m@<&>Z@%_r)0QYR`lH|>w&fQ<*n!MACVJNUNgb-@0-3(-Hd z`33(rD~@Wpm^&jF8?o%hN1Ly=3_rs3T?GT(`LG~VCcAkZ)+plxNsHcL3LEFOBknl?YsdG}i% zdV}ty)fI`mzP-`wC#fn^60%wFIC_jrdBd7(vpDy)5`LxdYuBSzITM}P+hDY3QNTW} zHK-7iG!6@+VgZbA2NYdd8Av~>HX?H8LW8a^p6ZHNXTy&z176Vc8fzI2JJn?M+4b*@#aZ+GKH5IW_bdRGfIMF55d0D}MgIv{B_1K3QVeZZ#xUPZSY zq$3bm>Z=#|(0s2Vt>gW9TaCqXw+jtT(hN?|5@+)Wa39ovS>9dpe9Yg71|SdS{rUd9 z_{hEfE)<0=HP=h!Wt4xtB$UfipV;W=SznCdMzkC3JwDZ@k})|hnsR2OVsm^Se-6_> zjZ;V&_akcso&fS}gtH8YkT#J|+2P1K3*89{AuC&%Uz*bgSB2UQ_^Al}*8*kQVjo{u zQ1Do#YigN$jRu9(b;hp)L5hoEUFzIc9qQ@%O>5pI!X7pAF?x>gGwI;2)Qo+Wi;Tk3x>i;UM z>3%RjlsiRve(zeTgPFe&_U`#~4f2loNq()sR8Eni26v+b0E{Y;djrP*+J)byvJr%Y zTN>{O%Yxk83NJk!)7bdx^CFmk9<1q8zl_g%e!adfVH)|n@)lrXM!#U|mrd#vl#rg;{}M4SHjmg<=+UB|cR{PfhfOM`+65Pl~3ADP{MJgg;{|7*e+oa+C4 zB>wno!~S^q?;|z7-hJ$uLGyKxcW$}42^4?)*xp)-R+|IO zzir`1SNlGSeW$vh^?!`->-Ml^@FxjP$)?h5VRY z!aK$)gBZp@bTnogK`9BSc5rG6C&y^TNpX)@l}Zny2!aH&`9F(Rii^pm?QvD8ieQ9y zrI!%}Ya|(3&SG|cFDJNnKw~}jv-(k2l-wQmR#l(;!Aca?2rLC%tvs#gbD81@Bdru4jOWM2N=?2iTv4T|0TgG!!{YtCJ%!MxBA6 z0gQMy+9v!lJGkj!e!q%7irtT~(q1RHh~cJit(>}qLs0@j?zd6utsr@L|KZzcty>l) zV*R+pjh+Xd0rZ*%cCYs~cvpw~K38<@t#sm+2bB1WUP*WV)0W(W+f&eGedqqhlM z*5_7nJ&+{>(iX9k)x^|j|5m_qtLZn%DJXTowhG!g#NdW4H_2swQC}V*!@PZ0fb{XL z{)Ox+wZWRQV2M1aj&B#!8dcAp*vz1SVok7_cTcZx7Fkp|lU8VF+o0`n1>T`7$g%%A z5|ww*>$5-SOQghh!EGk7OO|#u)FmzA6Y;?3j^H{SxbfpiNkkWh8OY|@Sx#w@^fk34 z_I+!s;vME&8j4t40_6d*$TX16ALIA%*|WRleJE1~$w~xyX3WAQW`%`W#K}&DY)pp=KLGq!%abO0%R7;aeVX$5bU*yV_>_vCl}oKy?ME9H=VrwR1zB;D5GcEd(FNZ6~c-;}?>X7Yl>jQgu{>GeTT=PP{ED z)TAJz8Yz5xYH5w1rKCjI3GO)2Q^`B{6T0h&WCHd^fJ>!##y#uIc(LZD6gY%NvrW8w z5SdU{Ros3r7KYVsFcH1mdaJ8*lp~P_r6c{Y8ef8fZZUhnpdPgS9Y~wrMAd-lj__^S zg|;fouaKDm=MXWP`Koeh>Wav)FiT_gx1k|wfuKqz&TvUPkbe9Da>fI5cjH(sLhFj0 zb;1?!ZA~)ksq%w!!g7mR6j=%1Ep9W$OZKK8c@w)&5;lhbFuruts@L0|H=Qe*;uU<_gPVm_iXFn89yjnz9EY}ggqA^>ZPUQJJj_j z^!lm~Ay!xlpXN>SHM-(iuaPQ=Kbc>m?TNluI{ToPk?+{m1@?HRnDZ7y6Hp7yuo}t~ zY8d_ERDMZ%sD%d>{aoHVm{BOMntMn7Hv}Rcz0&S9-u9NOvt_R|X_Z&3I=1b^8=acf z-f>SWb4=qAWXad0RPm*u8!qUE10CzYjo+T`7?>4ja5Zilq(1+6EVCmKeW)vp0Lg~2 z3&Ux8N7u9Z8&wC@7b0uV5O>0I$<;B8-jtYdTIT!i7b!fxvZc1grOC3mFoQ)SFtp?hyH+C$LwfEi1Wn$zLiVNQhE~@cq_v-iTus@C zE9tn8Tj6BTKND=Vcj;U(G_O?1BNyYIU79MQkKO7pieWh^3)@hft*6v6-i4$lg~g%~ zjD*_sMPj^w%wVDYkPs!C>Q}-r9`0 zBMe8K3K&3`h$3Kzx>za7hk(*UoGN#;_TqHo%O+bq4$gB$wWu;0tcJ{DPTW)TO{F=j zB)-(@E{%;0OpyP=CiaKww*h8=vpO@|+oG#ZIYIV{&#l;mv8|~nX=iJ14J4DI$cpt~ zA1IaL3!lzv^S!AuY3Ic$_hn*dG99J@9e-JXvhQ!kkO4ZdB9I6*> zH#2C!q?gD~JdufZ8zgKI(Qv-ML)5XkV2f<^g>bs`px;;ppMTAntuUu$@ME=b#uWo(w6@JMKFOZD{CMCDm;vRb}41vDXHFscJS|TE0t1 zZcFhDW(FvUe3)1ffAC3LD_hDhK)Fdp(6_6d4uM>6+FsHIlL|u+B)w-ES3O;qc}+Wb z1?jjxr4Z7m&$HZ!Lo1F|q5kiI^RRW49)$B|&Yb1VD+O@wv|olc903!sxDoPHx5bBY zGa7%D*x3?tLT_5$Yhu!ogQ(It1nJOU_k={J-DA{NZg${8=!$=N*N#T`|N1TWKqSa#guNWU>M4A1nmdt3u(`v|EkBzT?^e7S&N3e6BUD{>bao#di zsiCZe_AjZ1vRbD2d%(f0e?72HuxJVCWcC8>@XXWdM)tA3RMn9a>$c5&PN3S&)z{k3 zIF;9mrl)VTx4ps$o&`tm&OsJ%`i(CX06~JbtlEONGrF*8I>GT$Qrb4mg7hf%Vo!S7+u~*!kry#(>moM0c0!42v{5a6zJWorr&`^4 z?7B_GlFV7|2c=&Ew%OP+aLDO>_7Y5RqFUkX`M36csNLh*blyyL1YJ7F&su{X#5|Jt z(k5u!OU}m5ub+8fFDiZt{xlN&(9EUP>s6rK0^dqr|<*+aN?)WEKKl)`6I4KuT zgr=S**IMeJ^C~C1R>x>&nH%XPo}G(YIs+D*xtEixr=}hzZ`Y~RP-QF~QsD!AsI8AKSG8J|6|+Wer>KcG?phEmH_3nd(+ z0Km4p9DD=nB;qSC2VV?qUsojp7qiR*7X6d`B&?SEwPe8s5!CyDjs(EhGuYD*EHtOJ zl1w8$L5_HLO5CeK>gKV+-r`N$QnWTP!&1{KrJ^GsAxYBF@%*T9piH3?50yA;e~S^Tr?05uN?9sxx83 zfx4?idU>j|grQyVXtg)TtLi$a?W*2937bG(5s!JVF|)GlS`>?8%8f+io7 zY>dzgqXhE*oEhjmylN%ZV@2`VGJ>cvz)BW#2p{D$r!nj-$~3EX#DvLq{ZiI}PhSVV zlp!aD7a{371UopSGvEI2r9?z3bARj&e2NcCUlD(6m75LLcdZhpAWS=T!+I0VgIF<; zZAS&yyCROd;7gE$<@kZs*^fvKSBlC=)?g(f7^gN97UhB;D0U;Nj35Y=1RL(F$@PKs z-qko<2elAknb!oiV3Z{W5pUc5CRGT<{t%zy!njqZSWq6SYpEgZ`yD! zuRojv8c;|+94{UmHL-2OO^26Btd6nNTX-Pj+Fv->Ba%bq9OV?4Ru00rm)7c9Ia9+J zO1%YHs)f~U8DSQAr_9iam8pwFf(iOUC|lo%uAfgapq(B=Fs+-s z)R*f}eBJRTc+-ZPN$z|IK-o4Tm#O@PBl&5d-q20dHMHdcy>8^=1R!u) z@T{+12#KNex#Z#cz=>Yci#jch$vltK$Jy^b`H+?fUQPN4dwY2I`A&RFY7idy#w{NT z9Vx2g5fWP4HBOerh_S?Q0HJ;|`|1T){)k`f)bvVOT`cy6t*|N~Onk_{`!yB|Obj9> zjS$jq3jWnBEo1DnUFfrqDYn<3;n8?l;EV^84Sq#XPQqw;?rw}mKMWL(CBakX6qd~< z8sL5Qy;0D29)mgejRN=9C%`mU?-u9aV|RM@di}Bt00v_3@QJWXj(dmS9NTBdpx3b? zHXdfP?;UqXRSZNl46wa-G+uucOlc=|hu{|;p(Pe^9d3}U$)&6W7@O*-jMt^bwmA0cU%sTQ~ad>+|zZ z$fA@}zMwORr#L=y_9k6sz8BsqQ0Ch+=PkE*T~$2}eaBo54JeC7d{S1^SWxZsZ3$Y! zZ<$Og;GJvu=7HRP=9H+$jpk`EyUs~gktzC^L)=xsUuqwB3IxLHS?C#q?B z9y#M_V)Tn1n(l1&cO!Ka7=#voNDhE{Q;A_mRfgO-rsI4=l~Q#mR!0*ByH4pi=Vgzu zaPg0w7w;La!GxNi*!lpRrhx=Blxr4kWueVCS6}LzO&WNBG#K_`79w;(R;^P4JF9Mg z`K|L6IE9^1ewx&J+TlGMXz}NjlV$KW1Vn{~_jylRHS8R%I}oA;KR40df`n+=U-1Qy zMGVH_V^2*Q$A>lPU8E5rhbg4p&{@r%LszmU(0;RDn*!p1!MeoD2fsdxLeAOGg#Bim z?himRj<~=kAWI0IcsqM30%p!K2AQhEYkN7n?HBdO7LX{73_~;iN%^yW|N5?|HI{1r zRAUB89vKK-{r&6v!^Tp$>?0+bODhUzzAS`>r7@Jg9sqB=Ll_-HkR7!SXW`gOda79T z*iy*qm(^1_hw8xsuB253GI3m;iL{-_<@s0%KeOvLi7z|C5HF5Gc8Gv_S1;orIu!oZ z3VHhYXk7+$#N=zBm8e&!aT8Mj8LQnUa5Hm6BpVOM?(Fhd4oma>z?e6YL!-Q6nfH|> zo-%m#bC%~d=v{U5qw+i_k|c0Q33eU`fL}`i=^al#pLpmjSGSEMmu|s_Pb<%w#RIR8 z>CV0=IZ944dAJ$q#xcqF+qJ1(a6ZimQp~@wE#b4kp;VD=w?829zKicKu1gHOE?qj~;Z2gO5J7^Gar@MaESjmr<{f`xz_t6Udx{Fc>D-(^F9LjO3d{qAx>t& z&>>$61Js-}+6rQ{A(`q_f}pvJ|2{@1UHlVh&nJBdLo1@Ml7sB z>1>>nyUX-~++_VFV|XEC@LdB+r#609;_ic`;^W=8-SD=yz0G@C9v3`slrEhNm!j;G zVsmQkC9?g|VjANOnQotVJnzibD&vFWF#o3m7ZEEL5su+PMV|wGkE2<>{%Fm%h00<9 zZZD-Eq>F26m5|JwPX7CjbaCh%b@aCET2kaY7+a9m@5(9N%v!1YX9QkgGwi)p9vS9KDd40m5Grt6`iO1e*F# zE<4~#KrqxFx~Si;f@65wr_*2!lf;2KjyD=qpren$68{VbW7W9=Wa69dQ z+gUfX8JH}cN#OmXSyJ|xoP%LWya=iN!26ekJH=?yp9N9md6lE@II)YCl@!!Fsfp1S zS}LYugv$z=A_R?BGs}DxoO4KPabd(Ul~IH!#y%4E!sv!tUs$Y&xFzxQzMc*Q89KdD zb=zQG?l$JL`DP{0b^!3l?{Ex30O2sSiUK^*c}i8s0m9?-(0;tN_AZorRK$Fmukm6> zK){&!bR+nFWRqunvaiot32|#pn+jwN(s$GYUvtU}-{8Uc2!mh`7kC?xayX2y-n11i zx`L*5xGY0e38vygX=FhujqzO{1=cPML}uFP2CG`=gLa9$`2kNQ$N~tdn4LM5-puv= zy}d`{`Jz~|*_pzl%rP3#pT(lsQwBOc7+`b0-t_qRUS)zWd^t>bqWs>(c;CIuRA_({ z;VI|sUNI@MkdRQP{vT&8C^7E8JDU757Zo%n7VVmziD#rLas3J}{WB zG5TR?jxYik5ZXj9U_3LWbmV0$N@mH(9j!`7R6#U613HYZ)omF> zo?Yx(VDXD0U2n^m6vo^q;eOMB8ZY|Tajv!Q2Ej6ik}D;}$HlBlo&NCn5O0ACL@!nJ zmk}80=KzKfdQE71*1x*6bfqcCeA}Q&;iTk*jI1DB*w^@* + + + + + + Connect Arrows + + + + + +

Minimal example of arrow connection in d3

+ + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..d25b5fe --- /dev/null +++ b/script.js @@ -0,0 +1,212 @@ +const data = { + nodes: [ + { id: 'a', name: 'A' }, + { id: 'b', name: 'B' }, + { id: 'c', name: 'C' }, + { id: 'd', name: 'D' }, + { id: 'e', name: 'E' }, + { id: 'f', name: 'F' }, + { id: 'g', name: 'G' }, + ], + + // Format: { source: nodeId, target: nodeId, type: String } + links: [], +} + +const LINK_TYPES = { + 'Useful': '#b03d17', + 'Important': '#b8f27a', + 'Necessary': '#32a852', + 'Logically Connected': '#110dde', + 'Generalization': '#00f4fc', + 'Synonym': '#bd00fc', +} + +const NODE_HEIGHT = 100; +const NODE_WIDTH = 100; +const LINK_WIDTH = 8; +const COLOR_CIRCLE_RADIUS = 20; + +const svg = d3.select('#graph'); + +let selectedLinkType = 'Useful'; + +svg.append('g').attr('id', 'links'); +svg.append('g').attr('id', 'startNodes'); +svg.append('g').attr('id', 'endNodes'); +svg.append('g').attr('id', 'linkTypes'); + +const eventPositionToNode = (event) => { + const { x, y } = event; + const nodes = svg.selectAll('.node').nodes(); + const node = nodes.find((node) => { + let { x: nodeX, y: nodeY } = node.getBoundingClientRect(); + nodeX -= svg.node().getBoundingClientRect().x; + nodeY -= svg.node().getBoundingClientRect().y; + return x >= nodeX && x <= nodeX + NODE_WIDTH && y >= nodeY && y <= nodeY + NODE_HEIGHT; + }); + return node; +}; + +const updateLinks = () => { + svg + .select('#links') + .selectAll('line') + .data(data.links) + .join('line') + .attr('x1', (d) => { + const node = d3.select(`#startNode-${d.source}`); + let { x, y } = node.node().getBoundingClientRect(); + x -= svg.node().getBoundingClientRect().x; + return x + NODE_WIDTH / 2; + } + ) + .attr('y1', (d) => { + const node = d3.select(`#startNode-${d.source}`); + let { x, y } = node.node().getBoundingClientRect(); + y -= svg.node().getBoundingClientRect().y; + return y + NODE_HEIGHT / 2; + } + ) + .attr('x2', (d) => { + const node = d3.select(`#endNode-${d.target}`); + let { x, y } = node.node().getBoundingClientRect(); + x -= svg.node().getBoundingClientRect().x; + return x + NODE_WIDTH / 2; + } + ) + .attr('y2', (d) => { + const node = d3.select(`#endNode-${d.target}`); + let { x, y } = node.node().getBoundingClientRect(); + y -= svg.node().getBoundingClientRect().y; + return y + NODE_HEIGHT / 2; + } + ) + .attr("stroke-width", LINK_WIDTH) + .attr('stroke', (d) => LINK_TYPES[d.type]) + .on('click', (_, d) => { + // Remove the link + const index = data.links.findIndex((link) => link.source === d.source && link.target === d.target); + data.links.splice(index, 1); + updateLinks(); + }); +}; + + +const drag_handler = d3.drag() + .on("start", (event) => { + // Add a temporary arrow and let it follow the mouse + svg.append("line") + .attr("x1", event.x) + .attr("y1", event.y) + .attr("x2", event.x) + .attr("y2", event.y) + .attr("stroke-width", LINK_WIDTH) + .attr('stroke', LINK_TYPES[selectedLinkType]) + .attr("marker-end", "url(#arrowhead)") + .attr("id", "temp-arrow"); + + }) + .on("drag", (event) => { + svg.select("#temp-arrow") + .attr("x2", event.x) + .attr("y2", event.y); + }) + .on("end", (event) => { + // 1. Track the node that the arrow is pointing to, if any, and add it to the links + // 2. Remove the temporary arrow + // 3. Redraw the links + const target = eventPositionToNode(event); + if (target) { + const d3Target = d3.select(target); + + // Remove existing link between the two nodes if any. + const existingLink = data.links.findIndex((link) => { + link.source === event.subject.id && link.target === d3Target.datum().id; + }) + if (existingLink !== -1) { + data.links.splice(existingLink, 1); + } + + data.links.push({ + source: event.subject.id, + target: d3Target.datum().id, + type: selectedLinkType, + }); + } + svg.select("#temp-arrow").remove(); + updateLinks(); + }); + + +const startNodes = svg + .select('#startNodes') + .selectAll('g') + .data(data.nodes, d => d.id) + .join("g") + .attr('id', d => `startNode-${d.id}`) + .attr('class', 'node') + .attr('transform', (_d, i) => `translate(100, ${i * (NODE_HEIGHT + 10)})`) + .attr('style', 'cursor: pointer;') + .call((node) => node + .append('rect') + .attr('width', NODE_WIDTH) + .attr('height', NODE_HEIGHT) + .attr('fill', 'red') + ) + .call((node) => node + .append('text') + .attr('style', 'fill: white; font-size: 30px;') + .attr('dy', 60) + .attr('dx', 40) + .text(d => d.name) + ) + .call(drag_handler); + + +const endNodes = svg + .select('#endNodes') + .selectAll('g') + .data(data.nodes, d => d.id) + .join("g") + .attr('id', d => `endNode-${d.id}`) + .attr('class', 'node') + .attr('transform', (_d, i) => `translate(500, ${i * (NODE_HEIGHT + 10)})`) + .call((node) => node + .append('rect') + .attr('width', NODE_WIDTH) + .attr('height', NODE_HEIGHT) + .attr('fill', 'blue') + ) + .call((node) => node + .append('text') + .attr('style', 'fill: white; font-size: 30px;') + .attr('dy', 60) + .attr('dx', 40) + .text(d => d.name) + ); + + +const linkTypes = svg + .select('#linkTypes') + .selectAll('g') + .data(Object.keys(LINK_TYPES)) + .join("g") + .attr('id', d => d) + .call((node) => node + .append('circle') + .attr('cx', (_d, i) => 250 + (i * (COLOR_CIRCLE_RADIUS * 2 + 10))) + .attr('cy', 800) + .attr('r', COLOR_CIRCLE_RADIUS) + .attr('fill', (d) => LINK_TYPES[d]) + .attr('stroke-width', 5) + ) + .on('click', (event, d) => { + console.log('clicked', d); + d3.select('#linkTypes').selectAll('circle').attr('stroke', 'none'); + selectedLinkType = d; + d3.select(event.target).attr('stroke', 'red'); + }); + + +d3.select('#' + selectedLinkType).attr('stroke', 'red'); \ No newline at end of file